Better tab UI & remember tab setting

This commit is contained in:
Damontecres 2025-10-07 16:22:28 -04:00
parent c642636da7
commit 88038bfcfd
No known key found for this signature in database
7 changed files with 139 additions and 15 deletions

View file

@ -254,6 +254,18 @@ sealed interface AppPreference<T> {
},
)
val RememberSelectedTab =
AppSwitchPreference(
title = R.string.remember_selected_tab,
defaultValue = false,
getter = { it.interfacePreferences.rememberSelectedTab },
setter = { prefs, value ->
prefs.updateInterfacePreferences { rememberSelectedTab = value }
},
summaryOn = R.string.enabled,
summaryOff = R.string.disabled,
)
val InstalledVersion =
AppClickablePreference(
title = R.string.installed_version,
@ -301,6 +313,7 @@ val basicPreferences =
AppPreference.HomePageItems,
AppPreference.RewatchNextUp,
AppPreference.PlayThemeMusic,
AppPreference.RememberSelectedTab,
),
),
PreferenceGroup(

View file

@ -3,8 +3,10 @@ package com.github.damontecres.dolphin.preferences
import androidx.datastore.core.CorruptionException
import androidx.datastore.core.Serializer
import com.google.protobuf.InvalidProtocolBufferException
import timber.log.Timber
import java.io.InputStream
import java.io.OutputStream
import java.util.UUID
import javax.inject.Inject
import kotlin.time.Duration.Companion.seconds
@ -77,3 +79,13 @@ inline fun AppPreferences.updateInterfacePreferences(block: InterfacePreferences
update {
interfacePreferences = interfacePreferences.toBuilder().apply(block).build()
}
fun AppPreferences.rememberTab(
itemId: UUID,
index: Int,
): AppPreferences {
Timber.v("Updating $itemId tab to $index")
return updateInterfacePreferences {
putRememberedTabs(itemId.toString(), index)
}
}

View file

@ -14,17 +14,23 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.focus.focusRestorer
import androidx.compose.ui.focus.onFocusChanged
import androidx.compose.ui.unit.dp
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
import androidx.tv.material3.MaterialTheme
import androidx.tv.material3.Tab
import androidx.tv.material3.TabDefaults
import androidx.tv.material3.TabRow
import androidx.tv.material3.TabRowDefaults
import androidx.tv.material3.Text
import com.github.damontecres.dolphin.preferences.UserPreferences
import com.github.damontecres.dolphin.preferences.rememberTab
import com.github.damontecres.dolphin.ui.components.ErrorMessage
import com.github.damontecres.dolphin.ui.components.RecommendedMovie
import com.github.damontecres.dolphin.ui.ifElse
import com.github.damontecres.dolphin.ui.nav.Destination
import com.github.damontecres.dolphin.ui.nav.NavigationManager
import com.github.damontecres.dolphin.ui.preferences.PreferencesViewModel
import com.github.damontecres.dolphin.ui.tryRequestFocus
@Composable
@ -33,29 +39,71 @@ fun CollectionFolderMovie(
navigationManager: NavigationManager,
destination: Destination.MediaItem,
modifier: Modifier = Modifier,
preferencesViewModel: PreferencesViewModel = hiltViewModel(),
) {
val uiPrefs = preferences.appPreferences.interfacePreferences
val rememberedTabIndex =
if (uiPrefs.rememberSelectedTab) {
uiPrefs.getRememberedTabsOrDefault(destination.itemId.toString(), 0)
} else {
0
}
val tabs = listOf("Recommended", "Library")
var selectedTabIndex by rememberSaveable { mutableIntStateOf(0) }
var focusTabIndex by rememberSaveable { mutableIntStateOf(rememberedTabIndex) }
var selectedTabIndex by rememberSaveable { mutableIntStateOf(rememberedTabIndex) }
val focusRequester = remember { FocusRequester() }
val firstTabFocusRequester = remember { FocusRequester() }
LaunchedEffect(Unit) { firstTabFocusRequester.tryRequestFocus() }
if (uiPrefs.rememberSelectedTab) {
LaunchedEffect(selectedTabIndex) {
preferencesViewModel.preferenceDataStore.updateData {
preferences.appPreferences.rememberTab(destination.itemId, selectedTabIndex)
}
}
}
LaunchedEffect(Unit) { focusRequester.tryRequestFocus() }
Column(
modifier = modifier,
) {
TabRow(
selectedTabIndex = selectedTabIndex,
selectedTabIndex = focusTabIndex,
modifier =
Modifier
.padding(start = 32.dp, top = 16.dp)
.focusRestorer(firstTabFocusRequester),
.padding(start = 32.dp, top = 16.dp, bottom = 16.dp)
.focusRestorer(firstTabFocusRequester)
.onFocusChanged {
if (!it.isFocused) {
focusTabIndex = selectedTabIndex
}
},
indicator =
@Composable { tabPositions, doesTabRowHaveFocus ->
tabPositions.getOrNull(focusTabIndex)?.let { currentTabPosition ->
// TabRowDefaults.PillIndicator(
// currentTabPosition = currentTabPosition,
// doesTabRowHaveFocus = doesTabRowHaveFocus,
// )
TabRowDefaults.UnderlinedIndicator(
currentTabPosition = currentTabPosition,
doesTabRowHaveFocus = doesTabRowHaveFocus,
activeColor = MaterialTheme.colorScheme.border,
)
}
},
tabs = {
tabs.forEachIndexed { index, title ->
Tab(
selected = selectedTabIndex == index,
selected = focusTabIndex == index,
onClick = { selectedTabIndex = index },
onFocus = {},
onFocus = { focusTabIndex = index },
colors =
TabDefaults.pillIndicatorTabColors(
focusedContentColor = MaterialTheme.colorScheme.onSurfaceVariant,
),
) {
Text(
text = title,
@ -64,7 +112,7 @@ fun CollectionFolderMovie(
Modifier
.padding(8.dp)
.ifElse(
index == 0,
index == selectedTabIndex,
Modifier.focusRequester(firstTabFocusRequester),
),
)

View file

@ -14,17 +14,23 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.focus.focusRestorer
import androidx.compose.ui.focus.onFocusChanged
import androidx.compose.ui.unit.dp
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
import androidx.tv.material3.MaterialTheme
import androidx.tv.material3.Tab
import androidx.tv.material3.TabDefaults
import androidx.tv.material3.TabRow
import androidx.tv.material3.TabRowDefaults
import androidx.tv.material3.Text
import com.github.damontecres.dolphin.preferences.UserPreferences
import com.github.damontecres.dolphin.preferences.rememberTab
import com.github.damontecres.dolphin.ui.components.ErrorMessage
import com.github.damontecres.dolphin.ui.components.RecommendedTvShow
import com.github.damontecres.dolphin.ui.ifElse
import com.github.damontecres.dolphin.ui.nav.Destination
import com.github.damontecres.dolphin.ui.nav.NavigationManager
import com.github.damontecres.dolphin.ui.preferences.PreferencesViewModel
import com.github.damontecres.dolphin.ui.tryRequestFocus
@Composable
@ -33,29 +39,71 @@ fun CollectionFolderTv(
navigationManager: NavigationManager,
destination: Destination.MediaItem,
modifier: Modifier = Modifier,
preferencesViewModel: PreferencesViewModel = hiltViewModel(),
) {
val uiPrefs = preferences.appPreferences.interfacePreferences
val rememberedTabIndex =
if (uiPrefs.rememberSelectedTab) {
uiPrefs.getRememberedTabsOrDefault(destination.itemId.toString(), 0)
} else {
0
}
val tabs = listOf("Recommended", "Library")
var selectedTabIndex by rememberSaveable { mutableIntStateOf(0) }
var focusTabIndex by rememberSaveable { mutableIntStateOf(rememberedTabIndex) }
var selectedTabIndex by rememberSaveable { mutableIntStateOf(rememberedTabIndex) }
val focusRequester = remember { FocusRequester() }
val firstTabFocusRequester = remember { FocusRequester() }
LaunchedEffect(Unit) { firstTabFocusRequester.tryRequestFocus() }
if (uiPrefs.rememberSelectedTab) {
LaunchedEffect(selectedTabIndex) {
preferencesViewModel.preferenceDataStore.updateData {
preferences.appPreferences.rememberTab(destination.itemId, selectedTabIndex)
}
}
}
LaunchedEffect(Unit) { focusRequester.tryRequestFocus() }
Column(
modifier = modifier,
) {
TabRow(
selectedTabIndex = selectedTabIndex,
selectedTabIndex = focusTabIndex,
modifier =
Modifier
.padding(start = 32.dp, top = 16.dp)
.focusRestorer(firstTabFocusRequester),
.padding(start = 32.dp, top = 16.dp, bottom = 16.dp)
.focusRestorer(firstTabFocusRequester)
.onFocusChanged {
if (!it.isFocused) {
focusTabIndex = selectedTabIndex
}
},
indicator =
@Composable { tabPositions, doesTabRowHaveFocus ->
tabPositions.getOrNull(focusTabIndex)?.let { currentTabPosition ->
// TabRowDefaults.PillIndicator(
// currentTabPosition = currentTabPosition,
// doesTabRowHaveFocus = doesTabRowHaveFocus,
// )
TabRowDefaults.UnderlinedIndicator(
currentTabPosition = currentTabPosition,
doesTabRowHaveFocus = doesTabRowHaveFocus,
activeColor = MaterialTheme.colorScheme.border,
)
}
},
tabs = {
tabs.forEachIndexed { index, title ->
Tab(
selected = selectedTabIndex == index,
selected = focusTabIndex == index,
onClick = { selectedTabIndex = index },
onFocus = {},
onFocus = { focusTabIndex = index },
colors =
TabDefaults.pillIndicatorTabColors(
focusedContentColor = MaterialTheme.colorScheme.onSurfaceVariant,
),
) {
Text(
text = title,
@ -64,7 +112,7 @@ fun CollectionFolderTv(
Modifier
.padding(8.dp)
.ifElse(
index == 0,
index == selectedTabIndex,
Modifier.focusRequester(firstTabFocusRequester),
),
)

View file

@ -68,7 +68,7 @@ data class HomeRow(
fun HomePage(
preferences: UserPreferences,
navigationManager: NavigationManager,
modifier: Modifier,
modifier: Modifier = Modifier,
viewModel: HomeViewModel = hiltViewModel(),
) {
OneTimeLaunchedEffect {

View file

@ -30,6 +30,8 @@ enum ThemeSongVolume {
message InterfacePreferences {
ThemeSongVolume play_theme_songs = 1;
bool remember_selected_tab = 2;
map<string, int32> remembered_tabs = 3;
}
message AppPreferences {

View file

@ -59,5 +59,6 @@
<string name="pref_key_update_last_check_threshold">preference.update.threshold</string>
<string name="pref_key_update_last_check">preference.update.lastTimestamp</string>
<string name="confirm">Confirm</string>
<string name="remember_selected_tab">Remember selected tabs</string>
</resources>