From 64ad4ca33a6e0162adf85326f1eef000d014fad8 Mon Sep 17 00:00:00 2001 From: Alex Baker Date: Tue, 9 Jul 2024 01:14:40 -0500 Subject: [PATCH] Fix icon null pointer exceptions --- app/build.gradle.kts | 2 +- .../astrid/adapter/FilterViewHolder.kt | 10 +++---- .../activities/BaseListSettingsActivity.kt | 16 +++------- .../tasks/compose/FilterSelectionActivity.kt | 2 +- .../tasks/dialogs/FilterPickerViewModel.kt | 2 +- .../org/tasks/filters/FilterExtensions.kt | 4 +-- .../java/org/tasks/tags/TagPickerActivity.kt | 3 +- .../org/tasks/widget/WidgetChipProvider.kt | 23 +++++++------- .../changelogs/{131100.txt => 131101.txt} | 0 .../tasks/compose/components/IconByLabel.kt | 30 +++++++++++++++++-- .../org/tasks/compose/drawer/DrawerItem.kt | 2 +- .../tasks/compose/drawer/TaskListDrawer.kt | 24 +++++---------- .../tasks/compose/pickers/CheckableIconRow.kt | 23 +++++++++----- .../org/tasks/compose/pickers/IconPicker.kt | 13 ++------ .../compose/pickers/SearchableFilterPicker.kt | 2 +- 15 files changed, 83 insertions(+), 73 deletions(-) rename fastlane/metadata/android/en-US/changelogs/{131100.txt => 131101.txt} (100%) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 4fadd4f7d..191ea3976 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -51,7 +51,7 @@ android { defaultConfig { testApplicationId = "org.tasks.test" applicationId = "org.tasks" - versionCode = 131100 + versionCode = 131101 versionName = "13.11" targetSdk = libs.versions.android.targetSdk.get().toInt() minSdk = libs.versions.android.minSdk.get().toInt() diff --git a/app/src/main/java/com/todoroo/astrid/adapter/FilterViewHolder.kt b/app/src/main/java/com/todoroo/astrid/adapter/FilterViewHolder.kt index d3dc87e07..6d217f48a 100644 --- a/app/src/main/java/com/todoroo/astrid/adapter/FilterViewHolder.kt +++ b/app/src/main/java/com/todoroo/astrid/adapter/FilterViewHolder.kt @@ -5,7 +5,6 @@ import android.view.View import android.widget.CheckedTextView import android.widget.ImageView import android.widget.TextView -import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.ui.graphics.Color import androidx.core.view.isVisible @@ -15,7 +14,7 @@ import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.update import org.tasks.R 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.extensions.formatNumber import org.tasks.filters.CaldavFilter @@ -50,10 +49,9 @@ class FilterViewHolder internal constructor( it.icon.setContent { val label = icon.collectAsStateWithLifecycle().value val tint = color.collectAsStateWithLifecycle().value - Icon( - imageVector = imageVectorByName(label!!)!!, - contentDescription = label, - tint = if (tint == 0) MaterialTheme.colorScheme.onSurface else Color(tint), + TasksIcon( + label = label, + tint = if (tint == 0) MaterialTheme.colorScheme.onSurface else Color(tint) ) } size = it.size diff --git a/app/src/main/java/org/tasks/activities/BaseListSettingsActivity.kt b/app/src/main/java/org/tasks/activities/BaseListSettingsActivity.kt index 2cd871608..566964060 100644 --- a/app/src/main/java/org/tasks/activities/BaseListSettingsActivity.kt +++ b/app/src/main/java/org/tasks/activities/BaseListSettingsActivity.kt @@ -7,7 +7,6 @@ import android.view.View import android.view.ViewGroup import android.widget.TextView import androidx.appcompat.widget.Toolbar -import androidx.compose.foundation.Image import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.width @@ -15,7 +14,6 @@ import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.ColorFilter import androidx.compose.ui.platform.ComposeView import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp @@ -27,7 +25,7 @@ import kotlinx.coroutines.launch import org.tasks.R import org.tasks.compose.IconPickerActivity.Companion.launchIconPicker 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.Companion.newColorPalette import org.tasks.dialogs.ColorPickerAdapter.Palette @@ -69,15 +67,9 @@ abstract class BaseListSettingsActivity : ThemedInjectingAppCompatActivity(), To Row( verticalAlignment = Alignment.CenterVertically, ) { - val name = selectedIcon.collectAsStateWithLifecycle().value - val icon = imageVectorByName(name ?:defaultIcon) - if (icon != null) { - Image( - imageVector = icon, - contentDescription = name ?: defaultIcon, - colorFilter = ColorFilter.tint(MaterialTheme.colorScheme.onSurface), - ) - } + TasksIcon( + label = selectedIcon.collectAsStateWithLifecycle().value ?: defaultIcon + ) Spacer(modifier = Modifier.width(34.dp)) Text( text = "Icon", diff --git a/app/src/main/java/org/tasks/compose/FilterSelectionActivity.kt b/app/src/main/java/org/tasks/compose/FilterSelectionActivity.kt index 668eab8cb..f284349c6 100644 --- a/app/src/main/java/org/tasks/compose/FilterSelectionActivity.kt +++ b/app/src/main/java/org/tasks/compose/FilterSelectionActivity.kt @@ -75,7 +75,7 @@ class FilterSelectionActivity : AppCompatActivity() { filters = if (searching) state.searchResults else state.filters, query = state.query, onQueryChange = { viewModel.onQueryChange(it) }, - getIcon = { imageVectorByName(viewModel.getIcon(it))!! }, + getIcon = { imageVectorByName(viewModel.getIcon(it)) }, getColor = { viewModel.getColor(it) }, selected = selected, onClick = { filter -> diff --git a/app/src/main/java/org/tasks/dialogs/FilterPickerViewModel.kt b/app/src/main/java/org/tasks/dialogs/FilterPickerViewModel.kt index 983ea8374..eed8d707e 100644 --- a/app/src/main/java/org/tasks/dialogs/FilterPickerViewModel.kt +++ b/app/src/main/java/org/tasks/dialogs/FilterPickerViewModel.kt @@ -89,7 +89,7 @@ class FilterPickerViewModel @Inject constructor( localBroadcastManager.broadcastRefreshList() } - fun getIcon(filter: Filter): String = filter.getIcon(inventory) + fun getIcon(filter: Filter): String? = filter.getIcon(inventory) fun getColor(filter: Filter): Int { if (filter.tint != 0) { diff --git a/app/src/main/java/org/tasks/filters/FilterExtensions.kt b/app/src/main/java/org/tasks/filters/FilterExtensions.kt index 0b15db3f1..0e3d14a3d 100644 --- a/app/src/main/java/org/tasks/filters/FilterExtensions.kt +++ b/app/src/main/java/org/tasks/filters/FilterExtensions.kt @@ -3,7 +3,7 @@ package org.tasks.filters import org.tasks.billing.Inventory import org.tasks.themes.TasksIcons -fun Filter.getIcon(inventory: Inventory): String { +fun Filter.getIcon(inventory: Inventory): String? { if (inventory.hasPro) { icon?.takeIf { it.isNotBlank() }?.let { return it } } @@ -14,6 +14,6 @@ fun Filter.getIcon(inventory: Inventory): String { is CustomFilter -> TasksIcons.FILTER_LIST is PlaceFilter -> TasksIcons.PLACE - else -> icon!! + else -> icon } } diff --git a/app/src/main/java/org/tasks/tags/TagPickerActivity.kt b/app/src/main/java/org/tasks/tags/TagPickerActivity.kt index 156d9613a..40ab90003 100644 --- a/app/src/main/java/org/tasks/tags/TagPickerActivity.kt +++ b/app/src/main/java/org/tasks/tags/TagPickerActivity.kt @@ -115,7 +115,8 @@ class TagPickerActivity : ThemedInjectingAppCompatActivity() { 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 */ companion object { diff --git a/app/src/main/java/org/tasks/widget/WidgetChipProvider.kt b/app/src/main/java/org/tasks/widget/WidgetChipProvider.kt index 0da253c83..2ab3e3c7a 100644 --- a/app/src/main/java/org/tasks/widget/WidgetChipProvider.kt +++ b/app/src/main/java/org/tasks/widget/WidgetChipProvider.kt @@ -4,7 +4,6 @@ import android.content.Context import android.widget.RemoteViews import androidx.annotation.ColorInt import com.mikepenz.iconics.IconicsDrawable -import org.tasks.icons.OutlinedGoogleMaterial import com.todoroo.andlib.utility.DateUtilities import dagger.hilt.android.qualifiers.ApplicationContext import org.tasks.BuildConfig @@ -21,6 +20,7 @@ import org.tasks.filters.GtasksFilter import org.tasks.filters.PlaceFilter import org.tasks.filters.TagFilter import org.tasks.filters.getIcon +import org.tasks.icons.OutlinedGoogleMaterial import org.tasks.time.startOfDay import org.tasks.ui.ChipListCache import java.time.format.FormatStyle @@ -113,16 +113,17 @@ class WidgetChipProvider @Inject constructor( private fun newChip(filter: Filter, defaultIcon: Int) = newChip(filter.tint).apply { setTextViewText(R.id.chip_text, filter.title) - if (filter.icon.isNullOrBlank()) { - setImageViewResource(R.id.chip_icon, defaultIcon) - } else { - val icon = filter.getIcon(inventory) - val drawable = IconicsDrawable( - context, - OutlinedGoogleMaterial.getIcon("gmo_$icon") - ) - setImageViewBitmap(R.id.chip_icon, drawable.toBitmap()) - } + filter + .getIcon(inventory) + ?.let { + try { + OutlinedGoogleMaterial.getIcon("gmo_$it") + } catch (_: IllegalArgumentException) { + null + } + } + ?.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 { diff --git a/fastlane/metadata/android/en-US/changelogs/131100.txt b/fastlane/metadata/android/en-US/changelogs/131101.txt similarity index 100% rename from fastlane/metadata/android/en-US/changelogs/131100.txt rename to fastlane/metadata/android/en-US/changelogs/131101.txt diff --git a/kmp/src/commonMain/kotlin/org/tasks/compose/components/IconByLabel.kt b/kmp/src/commonMain/kotlin/org/tasks/compose/components/IconByLabel.kt index 3a3492dc9..5a58e355f 100644 --- a/kmp/src/commonMain/kotlin/org/tasks/compose/components/IconByLabel.kt +++ b/kmp/src/commonMain/kotlin/org/tasks/compose/components/IconByLabel.kt @@ -1,14 +1,40 @@ 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.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.unit.dp 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 { - 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() method.invoke(null, Icons.Outlined) as ImageVector } catch (_: Throwable) { null } +} diff --git a/kmp/src/commonMain/kotlin/org/tasks/compose/drawer/DrawerItem.kt b/kmp/src/commonMain/kotlin/org/tasks/compose/drawer/DrawerItem.kt index 612a11ad6..f655896b0 100644 --- a/kmp/src/commonMain/kotlin/org/tasks/compose/drawer/DrawerItem.kt +++ b/kmp/src/commonMain/kotlin/org/tasks/compose/drawer/DrawerItem.kt @@ -5,7 +5,7 @@ import org.tasks.filters.NavigationDrawerSubheader sealed interface DrawerItem { data class Filter( val title: String, - val icon: String, + val icon: String?, val color: Int = 0, val count: Int = 0, val shareCount: Int = 0, diff --git a/kmp/src/commonMain/kotlin/org/tasks/compose/drawer/TaskListDrawer.kt b/kmp/src/commonMain/kotlin/org/tasks/compose/drawer/TaskListDrawer.kt index f4523febe..bef4c000e 100644 --- a/kmp/src/commonMain/kotlin/org/tasks/compose/drawer/TaskListDrawer.kt +++ b/kmp/src/commonMain/kotlin/org/tasks/compose/drawer/TaskListDrawer.kt @@ -19,7 +19,6 @@ import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.mandatorySystemGestures import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items @@ -56,7 +55,7 @@ import kotlinx.collections.immutable.ImmutableList import org.jetbrains.compose.resources.stringResource import org.tasks.compose.components.Chevron import org.tasks.compose.components.SearchBar -import org.tasks.compose.components.imageVectorByName +import org.tasks.compose.components.TasksIcon import org.tasks.kmp.formatNumber import tasks.kmp.generated.resources.Res import tasks.kmp.generated.resources.help_and_feedback @@ -194,7 +193,13 @@ internal fun FilterItem( .clickable(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)) Text( 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 internal fun HeaderItem( modifier: Modifier = Modifier, diff --git a/kmp/src/commonMain/kotlin/org/tasks/compose/pickers/CheckableIconRow.kt b/kmp/src/commonMain/kotlin/org/tasks/compose/pickers/CheckableIconRow.kt index db0f54e90..737ece49a 100644 --- a/kmp/src/commonMain/kotlin/org/tasks/compose/pickers/CheckableIconRow.kt +++ b/kmp/src/commonMain/kotlin/org/tasks/compose/pickers/CheckableIconRow.kt @@ -6,6 +6,7 @@ import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.Check @@ -44,7 +45,7 @@ fun CheckableIconRow( @Composable fun CheckableIconRow( - icon: ImageVector, + icon: ImageVector?, tint: Color, selected: Boolean, onClick: () -> Unit, @@ -56,12 +57,20 @@ fun CheckableIconRow( .fillMaxWidth() .clickable { onClick() } ) { - Icon( - imageVector = icon, - contentDescription = null, - tint = tint, - modifier = Modifier.padding(start = 16.dp, end = 32.dp, top = 12.dp, bottom = 12.dp), - ) + 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( + imageVector = icon, + contentDescription = null, + tint = tint, + ) + } + } Box(modifier = Modifier.weight(1f)) { content() } diff --git a/kmp/src/commonMain/kotlin/org/tasks/compose/pickers/IconPicker.kt b/kmp/src/commonMain/kotlin/org/tasks/compose/pickers/IconPicker.kt index 8ad29c6a5..97558c2e8 100644 --- a/kmp/src/commonMain/kotlin/org/tasks/compose/pickers/IconPicker.kt +++ b/kmp/src/commonMain/kotlin/org/tasks/compose/pickers/IconPicker.kt @@ -1,7 +1,6 @@ package org.tasks.compose.pickers import androidx.compose.animation.ExperimentalAnimationApi -import androidx.compose.foundation.Image import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement @@ -22,7 +21,6 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.ColorFilter import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.times 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.Chevron 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.requires_pro_subscription import tasks.kmp.generated.resources.search @@ -150,14 +148,7 @@ fun LazyGridScope.iconGrid( ) { items(icons, key = { it.name }) { icon -> IconButton(onClick = { onSelected(icon) }) { - val imageVector = remember (icon.name) { - imageVectorByName(icon.name) - } - Image( - imageVector = imageVector ?: return@IconButton, - contentDescription = icon.name, - colorFilter = ColorFilter.tint(MaterialTheme.colorScheme.onSurface), - ) + TasksIcon(label = icon.name) } } } diff --git a/kmp/src/commonMain/kotlin/org/tasks/compose/pickers/SearchableFilterPicker.kt b/kmp/src/commonMain/kotlin/org/tasks/compose/pickers/SearchableFilterPicker.kt index 806c4c913..658ff8b24 100644 --- a/kmp/src/commonMain/kotlin/org/tasks/compose/pickers/SearchableFilterPicker.kt +++ b/kmp/src/commonMain/kotlin/org/tasks/compose/pickers/SearchableFilterPicker.kt @@ -37,7 +37,7 @@ fun SearchableFilterPicker( onQueryChange: (String) -> Unit, selected: Filter?, onClick: (FilterListItem) -> Unit, - getIcon: @Composable (Filter) -> ImageVector, + getIcon: @Composable (Filter) -> ImageVector?, getColor: (Filter) -> Int, ) { LazyColumn(