mirror of
https://github.com/JustinZeus/Wholphin.git
synced 2026-07-08 23:51:21 +02:00
Fix some crashes due to bad UI state (#617)
## Description Fixes two possible crashes: - When saving view options for a favorite page tab - When there are no results on a grid page/tab
This commit is contained in:
parent
0958578082
commit
83a543ad7d
4 changed files with 203 additions and 186 deletions
|
|
@ -169,6 +169,7 @@ class CollectionFolderViewModel
|
||||||
context.getString(R.string.error_loading_collection, itemId),
|
context.getString(R.string.error_loading_collection, itemId),
|
||||||
) + Dispatchers.IO,
|
) + Dispatchers.IO,
|
||||||
) {
|
) {
|
||||||
|
super.itemId = itemId
|
||||||
itemId.toUUIDOrNull()?.let {
|
itemId.toUUIDOrNull()?.let {
|
||||||
fetchItem(it)
|
fetchItem(it)
|
||||||
}
|
}
|
||||||
|
|
@ -206,7 +207,7 @@ class CollectionFolderViewModel
|
||||||
) {
|
) {
|
||||||
if (collectionFilter.useSavedLibraryDisplayInfo) {
|
if (collectionFilter.useSavedLibraryDisplayInfo) {
|
||||||
serverRepository.currentUser.value?.let { user ->
|
serverRepository.currentUser.value?.let { user ->
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
viewModelScope.launchIO {
|
||||||
val libraryDisplayInfo =
|
val libraryDisplayInfo =
|
||||||
LibraryDisplayInfo(
|
LibraryDisplayInfo(
|
||||||
userId = user.rowId,
|
userId = user.rowId,
|
||||||
|
|
|
||||||
|
|
@ -196,210 +196,224 @@ fun <T : CardGridItem> CardGrid(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var longPressing by remember { mutableStateOf(false) }
|
if (pager.isEmpty()) {
|
||||||
Row(
|
Box(
|
||||||
// horizontalArrangement = Arrangement.spacedBy(8.dp),
|
contentAlignment = Alignment.Center,
|
||||||
modifier =
|
modifier = modifier.fillMaxSize(),
|
||||||
modifier
|
) {
|
||||||
.fillMaxSize()
|
Text(
|
||||||
.onKeyEvent {
|
text = stringResource(R.string.no_results),
|
||||||
if (DEBUG) Timber.d("onKeyEvent: ${it.nativeKeyEvent}")
|
style = MaterialTheme.typography.titleLarge,
|
||||||
if (useBackToJump && it.key == Key.Back && it.nativeKeyEvent.isLongPress) {
|
color = MaterialTheme.colorScheme.onSurface,
|
||||||
longPressing = true
|
textAlign = TextAlign.Center,
|
||||||
val newPosition = previouslyFocusedIndex
|
|
||||||
if (DEBUG) Timber.d("Back long pressed: newPosition=$newPosition")
|
|
||||||
if (newPosition > 0) {
|
|
||||||
focusOn(newPosition)
|
|
||||||
scope.launch(ExceptionHandler()) {
|
|
||||||
gridState.scrollToItem(newPosition, -columns)
|
|
||||||
firstFocus.tryRequestFocus()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return@onKeyEvent true
|
|
||||||
} else if (it.type == KeyEventType.KeyUp) {
|
|
||||||
if (longPressing && it.key == Key.Back) {
|
|
||||||
longPressing = false
|
|
||||||
return@onKeyEvent true
|
|
||||||
}
|
|
||||||
longPressing = false
|
|
||||||
}
|
|
||||||
if (it.type != KeyEventType.KeyUp) {
|
|
||||||
return@onKeyEvent false
|
|
||||||
} else if (useBackToJump && it.key == Key.Back && focusedIndex > 0) {
|
|
||||||
jumpToTop()
|
|
||||||
return@onKeyEvent true
|
|
||||||
} else if (isPlayKeyUp(it)) {
|
|
||||||
val item = pager.getOrNull(focusedIndex)
|
|
||||||
if (item?.playable == true) {
|
|
||||||
Timber.v("Clicked play on ${item.id}")
|
|
||||||
onClickPlay.invoke(focusedIndex, item)
|
|
||||||
}
|
|
||||||
return@onKeyEvent true
|
|
||||||
} else if (useJumpRemoteButtons && isForwardButton(it)) {
|
|
||||||
jump(jump1)
|
|
||||||
return@onKeyEvent true
|
|
||||||
} else if (useJumpRemoteButtons && isBackwardButton(it)) {
|
|
||||||
jump(-jump1)
|
|
||||||
return@onKeyEvent true
|
|
||||||
} else {
|
|
||||||
return@onKeyEvent false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
) {
|
|
||||||
if (showJumpButtons && pager.isNotEmpty()) {
|
|
||||||
JumpButtons(
|
|
||||||
jump1 = jump1,
|
|
||||||
jump2 = jump2,
|
|
||||||
jumpClick = { jump(it) },
|
|
||||||
modifier = Modifier.align(Alignment.CenterVertically),
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
Box(
|
} else {
|
||||||
modifier = Modifier.weight(1f),
|
var longPressing by remember { mutableStateOf(false) }
|
||||||
) {
|
Row(
|
||||||
LazyVerticalGrid(
|
// horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||||
columns = GridCells.Fixed(columns),
|
modifier =
|
||||||
horizontalArrangement = Arrangement.spacedBy(spacing),
|
modifier
|
||||||
verticalArrangement = Arrangement.spacedBy(spacing),
|
.fillMaxSize()
|
||||||
state = gridState,
|
.onKeyEvent {
|
||||||
contentPadding = PaddingValues(16.dp),
|
if (DEBUG) Timber.d("onKeyEvent: ${it.nativeKeyEvent}")
|
||||||
modifier =
|
if (useBackToJump && it.key == Key.Back && it.nativeKeyEvent.isLongPress) {
|
||||||
Modifier
|
longPressing = true
|
||||||
.fillMaxSize()
|
val newPosition = previouslyFocusedIndex
|
||||||
.focusGroup()
|
if (DEBUG) Timber.d("Back long pressed: newPosition=$newPosition")
|
||||||
.focusRestorer(firstFocus)
|
if (newPosition > 0) {
|
||||||
.focusProperties {
|
focusOn(newPosition)
|
||||||
onExit = {
|
scope.launch(ExceptionHandler()) {
|
||||||
// Leaving the grid, so "forget" the position
|
gridState.scrollToItem(newPosition, -columns)
|
||||||
// focusedIndex = -1
|
firstFocus.tryRequestFocus()
|
||||||
}
|
|
||||||
onEnter = {
|
|
||||||
if (focusedIndex < 0 && gridState.firstVisibleItemIndex <= startPosition) {
|
|
||||||
focusedIndex = startPosition
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
return@onKeyEvent true
|
||||||
) {
|
} else if (it.type == KeyEventType.KeyUp) {
|
||||||
items(pager.size) { index ->
|
if (longPressing && it.key == Key.Back) {
|
||||||
val mod =
|
longPressing = false
|
||||||
if ((index == focusedIndex) or (focusedIndex < 0 && index == 0)) {
|
return@onKeyEvent true
|
||||||
if (DEBUG) Timber.d("Adding firstFocus to focusedIndex $index")
|
}
|
||||||
Modifier
|
longPressing = false
|
||||||
.focusRequester(firstFocus)
|
|
||||||
.focusRequester(gridFocusRequester)
|
|
||||||
.focusRequester(alphabetFocusRequester)
|
|
||||||
} else {
|
|
||||||
Modifier
|
|
||||||
}
|
}
|
||||||
val item = pager[index]
|
if (it.type != KeyEventType.KeyUp) {
|
||||||
cardContent(
|
return@onKeyEvent false
|
||||||
item,
|
} else if (useBackToJump && it.key == Key.Back && focusedIndex > 0) {
|
||||||
{
|
jumpToTop()
|
||||||
if (item != null) {
|
return@onKeyEvent true
|
||||||
focusedIndex = index
|
} else if (isPlayKeyUp(it)) {
|
||||||
onClickItem.invoke(index, item)
|
val item = pager.getOrNull(focusedIndex)
|
||||||
|
if (item?.playable == true) {
|
||||||
|
Timber.v("Clicked play on ${item.id}")
|
||||||
|
onClickPlay.invoke(focusedIndex, item)
|
||||||
}
|
}
|
||||||
},
|
return@onKeyEvent true
|
||||||
{ if (item != null) onLongClickItem.invoke(index, item) },
|
} else if (useJumpRemoteButtons && isForwardButton(it)) {
|
||||||
mod
|
jump(jump1)
|
||||||
.ifElse(index == 0, Modifier.focusRequester(zeroFocus))
|
return@onKeyEvent true
|
||||||
.onFocusChanged { focusState ->
|
} else if (useJumpRemoteButtons && isBackwardButton(it)) {
|
||||||
if (DEBUG) {
|
jump(-jump1)
|
||||||
Timber.v(
|
return@onKeyEvent true
|
||||||
"$index isFocused=${focusState.isFocused}",
|
} else {
|
||||||
)
|
return@onKeyEvent false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
) {
|
||||||
|
if (showJumpButtons && pager.isNotEmpty()) {
|
||||||
|
JumpButtons(
|
||||||
|
jump1 = jump1,
|
||||||
|
jump2 = jump2,
|
||||||
|
jumpClick = { jump(it) },
|
||||||
|
modifier = Modifier.align(Alignment.CenterVertically),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Box(
|
||||||
|
modifier = Modifier.weight(1f),
|
||||||
|
) {
|
||||||
|
LazyVerticalGrid(
|
||||||
|
columns = GridCells.Fixed(columns),
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(spacing),
|
||||||
|
verticalArrangement = Arrangement.spacedBy(spacing),
|
||||||
|
state = gridState,
|
||||||
|
contentPadding = PaddingValues(16.dp),
|
||||||
|
modifier =
|
||||||
|
Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.focusGroup()
|
||||||
|
.focusRestorer(firstFocus)
|
||||||
|
.focusProperties {
|
||||||
|
onExit = {
|
||||||
|
// Leaving the grid, so "forget" the position
|
||||||
|
// focusedIndex = -1
|
||||||
}
|
}
|
||||||
if (focusState.isFocused) {
|
onEnter = {
|
||||||
// Focused, so set that up
|
if (focusedIndex < 0 && gridState.firstVisibleItemIndex <= startPosition) {
|
||||||
focusOn(index)
|
focusedIndex = startPosition
|
||||||
positionCallback?.invoke(columns, index)
|
}
|
||||||
} else if (focusedIndex == index) {
|
}
|
||||||
|
},
|
||||||
|
) {
|
||||||
|
items(pager.size) { index ->
|
||||||
|
val mod =
|
||||||
|
if ((index == focusedIndex) or (focusedIndex < 0 && index == 0)) {
|
||||||
|
if (DEBUG) Timber.d("Adding firstFocus to focusedIndex $index")
|
||||||
|
Modifier
|
||||||
|
.focusRequester(firstFocus)
|
||||||
|
.focusRequester(gridFocusRequester)
|
||||||
|
.focusRequester(alphabetFocusRequester)
|
||||||
|
} else {
|
||||||
|
Modifier
|
||||||
|
}
|
||||||
|
val item = pager[index]
|
||||||
|
cardContent(
|
||||||
|
item,
|
||||||
|
{
|
||||||
|
if (item != null) {
|
||||||
|
focusedIndex = index
|
||||||
|
onClickItem.invoke(index, item)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ if (item != null) onLongClickItem.invoke(index, item) },
|
||||||
|
mod
|
||||||
|
.ifElse(index == 0, Modifier.focusRequester(zeroFocus))
|
||||||
|
.onFocusChanged { focusState ->
|
||||||
|
if (DEBUG) {
|
||||||
|
Timber.v(
|
||||||
|
"$index isFocused=${focusState.isFocused}",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if (focusState.isFocused) {
|
||||||
|
// Focused, so set that up
|
||||||
|
focusOn(index)
|
||||||
|
positionCallback?.invoke(columns, index)
|
||||||
|
} else if (focusedIndex == index) {
|
||||||
// savedFocusedIndex = index
|
// savedFocusedIndex = index
|
||||||
// // Was focused on this, so mark unfocused
|
// // Was focused on this, so mark unfocused
|
||||||
// focusedIndex = -1
|
// focusedIndex = -1
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
if (pager.isEmpty()) {
|
||||||
if (pager.isEmpty()) {
|
|
||||||
// focusedIndex = -1
|
// focusedIndex = -1
|
||||||
Box(modifier = Modifier.fillMaxSize()) {
|
Box(modifier = Modifier.fillMaxSize()) {
|
||||||
Text(
|
Text(
|
||||||
text = stringResource(R.string.no_results),
|
text = stringResource(R.string.no_results),
|
||||||
color = MaterialTheme.colorScheme.onBackground,
|
color = MaterialTheme.colorScheme.onBackground,
|
||||||
modifier = Modifier.align(Alignment.Center),
|
modifier = Modifier.align(Alignment.Center),
|
||||||
)
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
if (showFooter) {
|
||||||
if (showFooter) {
|
// Footer
|
||||||
// Footer
|
Box(
|
||||||
Box(
|
modifier =
|
||||||
modifier =
|
Modifier
|
||||||
Modifier
|
.align(Alignment.BottomCenter)
|
||||||
.align(Alignment.BottomCenter)
|
.background(AppColors.TransparentBlack50),
|
||||||
.background(AppColors.TransparentBlack50),
|
) {
|
||||||
) {
|
val index = (focusedIndex + 1).takeIf { it > 0 } ?: "?"
|
||||||
val index = (focusedIndex + 1).takeIf { it > 0 } ?: "?"
|
|
||||||
// if (focusedIndex >= 0) {
|
// if (focusedIndex >= 0) {
|
||||||
// focusedIndex + 1
|
// focusedIndex + 1
|
||||||
// } else {
|
// } else {
|
||||||
// max(savedFocusedIndex, focusedIndexOnExit) + 1
|
// max(savedFocusedIndex, focusedIndexOnExit) + 1
|
||||||
// }
|
// }
|
||||||
Text(
|
Text(
|
||||||
modifier = Modifier.padding(4.dp),
|
modifier = Modifier.padding(4.dp),
|
||||||
color = MaterialTheme.colorScheme.onBackground,
|
color = MaterialTheme.colorScheme.onBackground,
|
||||||
text = "$index / ${pager.size}",
|
text = "$index / ${pager.size}",
|
||||||
)
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
val context = LocalContext.current
|
||||||
val context = LocalContext.current
|
val letters = context.getString(R.string.jump_letters)
|
||||||
val letters = context.getString(R.string.jump_letters)
|
// Letters
|
||||||
// Letters
|
val currentLetter =
|
||||||
val currentLetter =
|
remember(focusedIndex) {
|
||||||
remember(focusedIndex) {
|
pager
|
||||||
pager
|
.getOrNull(focusedIndex)
|
||||||
.getOrNull(focusedIndex)
|
?.sortName
|
||||||
?.sortName
|
?.first()
|
||||||
?.first()
|
?.uppercaseChar()
|
||||||
?.uppercaseChar()
|
?.let {
|
||||||
?.let {
|
if (it >= '0' && it <= '9') {
|
||||||
if (it >= '0' && it <= '9') {
|
'#'
|
||||||
'#'
|
} else if (it >= 'A' && it <= 'Z') {
|
||||||
} else if (it >= 'A' && it <= 'Z') {
|
it
|
||||||
it
|
} else {
|
||||||
} else {
|
null
|
||||||
null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
?: letters[0]
|
|
||||||
}
|
|
||||||
if (showLetterButtons && pager.isNotEmpty()) {
|
|
||||||
AlphabetButtons(
|
|
||||||
letters = letters,
|
|
||||||
currentLetter = currentLetter,
|
|
||||||
modifier =
|
|
||||||
Modifier
|
|
||||||
.align(Alignment.CenterVertically)
|
|
||||||
.padding(end = 16.dp),
|
|
||||||
// Add end padding to push away from edge
|
|
||||||
letterClicked = { letter ->
|
|
||||||
scope.launch(ExceptionHandler()) {
|
|
||||||
val jumpPosition =
|
|
||||||
withContext(Dispatchers.IO) {
|
|
||||||
letterPosition.invoke(letter)
|
|
||||||
}
|
}
|
||||||
Timber.d("Alphabet jump to $jumpPosition")
|
|
||||||
if (jumpPosition >= 0) {
|
|
||||||
pager.getOrNull(jumpPosition)
|
|
||||||
gridState.scrollToItem(jumpPosition)
|
|
||||||
focusOn(jumpPosition)
|
|
||||||
alphabetFocus = true
|
|
||||||
}
|
}
|
||||||
}
|
?: letters[0]
|
||||||
},
|
}
|
||||||
)
|
if (showLetterButtons && pager.isNotEmpty()) {
|
||||||
|
AlphabetButtons(
|
||||||
|
letters = letters,
|
||||||
|
currentLetter = currentLetter,
|
||||||
|
modifier =
|
||||||
|
Modifier
|
||||||
|
.align(Alignment.CenterVertically)
|
||||||
|
.padding(end = 16.dp),
|
||||||
|
// Add end padding to push away from edge
|
||||||
|
letterClicked = { letter ->
|
||||||
|
scope.launch(ExceptionHandler()) {
|
||||||
|
val jumpPosition =
|
||||||
|
withContext(Dispatchers.IO) {
|
||||||
|
letterPosition.invoke(letter)
|
||||||
|
}
|
||||||
|
Timber.d("Alphabet jump to $jumpPosition")
|
||||||
|
if (jumpPosition >= 0) {
|
||||||
|
pager.getOrNull(jumpPosition)
|
||||||
|
gridState.scrollToItem(jumpPosition)
|
||||||
|
focusOn(jumpPosition)
|
||||||
|
alphabetFocus = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,7 @@ abstract class ItemViewModel(
|
||||||
) : ViewModel() {
|
) : ViewModel() {
|
||||||
val item = MutableLiveData<BaseItem?>(null)
|
val item = MutableLiveData<BaseItem?>(null)
|
||||||
lateinit var itemId: String
|
lateinit var itemId: String
|
||||||
lateinit var itemUuid: UUID
|
var itemUuid: UUID? = null
|
||||||
|
|
||||||
suspend fun fetchItem(itemId: UUID): BaseItem =
|
suspend fun fetchItem(itemId: UUID): BaseItem =
|
||||||
withContext(Dispatchers.IO) {
|
withContext(Dispatchers.IO) {
|
||||||
|
|
|
||||||
|
|
@ -155,8 +155,10 @@ class PersonViewModel
|
||||||
|
|
||||||
fun setFavorite(favorite: Boolean) {
|
fun setFavorite(favorite: Boolean) {
|
||||||
viewModelScope.launchIO {
|
viewModelScope.launchIO {
|
||||||
favoriteWatchManager.setFavorite(itemUuid, favorite)
|
itemUuid?.let {
|
||||||
fetchAndSetItem(itemUuid)
|
favoriteWatchManager.setFavorite(it, favorite)
|
||||||
|
fetchAndSetItem(it)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue