fix: crash when shortcut name is empty

This commit is contained in:
Adam M. Szalkowski 2026-07-04 22:23:25 +02:00
parent 0c9ff66f48
commit ee054da5b9
4 changed files with 137 additions and 1 deletions

View file

@ -6,6 +6,7 @@ import android.content.Intent
import androidx.core.content.pm.ShortcutInfoCompat
import androidx.core.content.pm.ShortcutManagerCompat
import dagger.hilt.android.qualifiers.ApplicationContext
import de.szalkowski.activitylauncher.R
import de.szalkowski.activitylauncher.domain.launcher.IntentSigner
import de.szalkowski.activitylauncher.domain.launcher.ShortcutCreator
import de.szalkowski.activitylauncher.domain.model.ShortcutRequest
@ -39,8 +40,9 @@ class ShortcutCreatorImpl @Inject constructor(
intent.putExtra(ShortcutCreator.INTENT_EXTRA_LAUNCH_PLUGIN, it.flattenToString())
}
val label = request.name.ifBlank { context.getString(R.string.app_name) }
val shortcut = ShortcutInfoCompat.Builder(context, UUID.randomUUID().toString())
.setShortLabel(request.name)
.setShortLabel(label)
.setIcon(request.icon)
.setIntent(intent)
.build()

View file

@ -81,6 +81,10 @@ class ActivityDetailsFragment : Fragment() {
} else {
favoriteItem.setIcon(R.drawable.ic_favorite_border)
}
favoriteItem.isEnabled = viewModel.canFavorite.value
val shareItem = menu.findItem(R.id.action_share)
shareItem.isEnabled = viewModel.canShare.value
}
override fun onMenuItemSelected(menuItem: MenuItem): Boolean {
@ -135,6 +139,30 @@ class ActivityDetailsFragment : Fragment() {
binding.btCreateShortcutChooser.isVisible = isVisible
}
}
launch {
viewModel.canLaunch.collect { isEnabled ->
binding.btLaunch.isEnabled = isEnabled
binding.btLaunchChooser.isEnabled = isEnabled
}
}
launch {
viewModel.canCreateShortcut.collect { isEnabled ->
binding.btCreateShortcut.isEnabled = isEnabled
binding.btCreateShortcutChooser.isEnabled = isEnabled
}
}
launch {
viewModel.canShare.collect { isEnabled ->
binding.btShareShortcut.isEnabled = isEnabled
activity?.invalidateOptionsMenu()
}
}
launch {
viewModel.canFavorite.collect { isEnabled ->
binding.btFavorite.isEnabled = isEnabled
activity?.invalidateOptionsMenu()
}
}
launch {
viewModel.errorMessage.collect { resId ->
Toast.makeText(requireContext(), resId, Toast.LENGTH_SHORT).show()

View file

@ -26,12 +26,15 @@ import de.szalkowski.activitylauncher.domain.usecase.launcher.LaunchActivityUseC
import kotlinx.coroutines.FlowPreview
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.debounce
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
import javax.inject.Inject
@ -70,6 +73,24 @@ class ActivityDetailsViewModel @Inject constructor(
private val _editedIconResourceName = MutableStateFlow("")
val editedIconResourceName: StateFlow<String> = _editedIconResourceName.asStateFlow()
val canLaunch: StateFlow<Boolean> = combine(_editedPackage, _editedClass) { pkg, cls ->
pkg.isNotBlank() && cls.isNotBlank()
}.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), false)
val canCreateShortcut: StateFlow<Boolean> =
combine(_editedName, _editedPackage, _editedClass) { name, pkg, cls ->
name.isNotBlank() && pkg.isNotBlank() && cls.isNotBlank()
}.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), false)
val canShare: StateFlow<Boolean> = combine(_editedPackage, _editedClass) { pkg, cls ->
pkg.isNotBlank() && cls.isNotBlank()
}.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), false)
val canFavorite: StateFlow<Boolean> =
combine(_editedName, _editedPackage, _editedClass) { name, pkg, cls ->
name.isNotBlank() && pkg.isNotBlank() && cls.isNotBlank()
}.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), false)
private val _editedIcon = MutableStateFlow<IconCompat?>(null)
val editedIcon: StateFlow<IconCompat?> = _editedIcon.asStateFlow()

View file

@ -361,4 +361,89 @@ class ActivityDetailsViewModelTest {
job.cancel()
}
@Test
fun `should update canLaunch state based on package and class`() = runTest {
val values = mutableListOf<Boolean>()
val job = launch(UnconfinedTestDispatcher(testScheduler)) {
viewModel.canLaunch.collect { values.add(it) }
}
viewModel.updatePackage("")
viewModel.updateClass("Class")
assertFalse(viewModel.canLaunch.value)
viewModel.updatePackage("pkg")
viewModel.updateClass("")
assertFalse(viewModel.canLaunch.value)
viewModel.updatePackage("pkg")
viewModel.updateClass("Class")
assertTrue(viewModel.canLaunch.value)
job.cancel()
}
@Test
fun `should update canCreateShortcut state based on name package and class`() = runTest {
val values = mutableListOf<Boolean>()
val job = launch(UnconfinedTestDispatcher(testScheduler)) {
viewModel.canCreateShortcut.collect { values.add(it) }
}
viewModel.updateName("")
viewModel.updatePackage("pkg")
viewModel.updateClass("Class")
assertFalse(viewModel.canCreateShortcut.value)
viewModel.updateName("Name")
viewModel.updatePackage("")
viewModel.updateClass("Class")
assertFalse(viewModel.canCreateShortcut.value)
viewModel.updateName("Name")
viewModel.updatePackage("pkg")
viewModel.updateClass("Class")
assertTrue(viewModel.canCreateShortcut.value)
job.cancel()
}
@Test
fun `should update canShare state based on package and class`() = runTest {
val values = mutableListOf<Boolean>()
val job = launch(UnconfinedTestDispatcher(testScheduler)) {
viewModel.canShare.collect { values.add(it) }
}
viewModel.updatePackage("")
viewModel.updateClass("Class")
assertFalse(viewModel.canShare.value)
viewModel.updatePackage("pkg")
viewModel.updateClass("Class")
assertTrue(viewModel.canShare.value)
job.cancel()
}
@Test
fun `should update canFavorite state based on name package and class`() = runTest {
val values = mutableListOf<Boolean>()
val job = launch(UnconfinedTestDispatcher(testScheduler)) {
viewModel.canFavorite.collect { values.add(it) }
}
viewModel.updateName("")
viewModel.updatePackage("pkg")
viewModel.updateClass("Class")
assertFalse(viewModel.canFavorite.value)
viewModel.updateName("Name")
viewModel.updatePackage("pkg")
viewModel.updateClass("Class")
assertTrue(viewModel.canFavorite.value)
job.cancel()
}
}