Save remembered tabs per user (#57)

If the remember tab setting is enabled, tabs will be remembered per user
profile now
This commit is contained in:
damontecres 2025-10-22 19:24:17 -04:00 committed by GitHub
parent f5ee5eceab
commit b5771db54e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 98 additions and 39 deletions

View file

@ -3,13 +3,23 @@ package com.github.damontecres.wholphin.hilt
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.content.Context import android.content.Context
import android.provider.Settings import android.provider.Settings
import androidx.datastore.core.DataStore
import com.github.damontecres.wholphin.BuildConfig import com.github.damontecres.wholphin.BuildConfig
import com.github.damontecres.wholphin.data.ServerRepository import com.github.damontecres.wholphin.data.ServerRepository
import com.github.damontecres.wholphin.preferences.AppPreferences
import com.github.damontecres.wholphin.preferences.UserPreferences
import com.github.damontecres.wholphin.preferences.updateInterfacePreferences
import com.github.damontecres.wholphin.util.ExceptionHandler
import com.github.damontecres.wholphin.util.RememberTabManager
import dagger.Module import dagger.Module
import dagger.Provides import dagger.Provides
import dagger.hilt.InstallIn import dagger.hilt.InstallIn
import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.android.qualifiers.ApplicationContext
import dagger.hilt.components.SingletonComponent import dagger.hilt.components.SingletonComponent
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.launch
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
import org.jellyfin.sdk.Jellyfin import org.jellyfin.sdk.Jellyfin
import org.jellyfin.sdk.api.client.util.AuthorizationHeaderBuilder import org.jellyfin.sdk.api.client.util.AuthorizationHeaderBuilder
@ -17,6 +27,7 @@ import org.jellyfin.sdk.api.okhttp.OkHttpFactory
import org.jellyfin.sdk.createJellyfin import org.jellyfin.sdk.createJellyfin
import org.jellyfin.sdk.model.ClientInfo import org.jellyfin.sdk.model.ClientInfo
import org.jellyfin.sdk.model.DeviceInfo import org.jellyfin.sdk.model.DeviceInfo
import java.util.UUID
import javax.inject.Qualifier import javax.inject.Qualifier
import javax.inject.Singleton import javax.inject.Singleton
@ -28,6 +39,10 @@ annotation class AuthOkHttpClient
@Retention(AnnotationRetention.BINARY) @Retention(AnnotationRetention.BINARY)
annotation class StandardOkHttpClient annotation class StandardOkHttpClient
@Qualifier
@Retention(AnnotationRetention.BINARY)
annotation class IoCoroutineScope
@Module @Module
@InstallIn(SingletonComponent::class) @InstallIn(SingletonComponent::class)
object AppModule { object AppModule {
@ -118,4 +133,51 @@ object AppModule {
@Provides @Provides
@Singleton @Singleton
fun apiClient(jellyfin: Jellyfin) = jellyfin.createApi() fun apiClient(jellyfin: Jellyfin) = jellyfin.createApi()
/**
* Implementation of [RememberTabManager] which remembers by server, user, & item
*/
@Provides
@Singleton
fun rememberTabManager(
serverRepository: ServerRepository,
appPreference: DataStore<AppPreferences>,
@IoCoroutineScope scope: CoroutineScope,
) = object : RememberTabManager {
fun key(itemId: UUID) = "${serverRepository.currentServer?.id}_${serverRepository.currentUser?.id}_$itemId"
override fun getRememberedTab(
preferences: UserPreferences,
itemId: UUID,
defaultTab: Int,
): Int {
if (preferences.appPreferences.interfacePreferences.rememberSelectedTab) {
return preferences.appPreferences.interfacePreferences
.getRememberedTabsOrDefault(key(itemId), defaultTab)
} else {
return defaultTab
}
}
override fun saveRememberedTab(
preferences: UserPreferences,
itemId: UUID,
tabIndex: Int,
) {
if (preferences.appPreferences.interfacePreferences.rememberSelectedTab) {
scope.launch(ExceptionHandler()) {
appPreference.updateData {
preferences.appPreferences.updateInterfacePreferences {
putRememberedTabs(key(itemId), tabIndex)
}
}
}
}
}
}
@Provides
@Singleton
@IoCoroutineScope
fun ioCoroutineScope(): CoroutineScope = CoroutineScope(SupervisorJob() + Dispatchers.IO)
} }

View file

@ -3,10 +3,8 @@ package com.github.damontecres.wholphin.preferences
import androidx.datastore.core.CorruptionException import androidx.datastore.core.CorruptionException
import androidx.datastore.core.Serializer import androidx.datastore.core.Serializer
import com.google.protobuf.InvalidProtocolBufferException import com.google.protobuf.InvalidProtocolBufferException
import timber.log.Timber
import java.io.InputStream import java.io.InputStream
import java.io.OutputStream import java.io.OutputStream
import java.util.UUID
import javax.inject.Inject import javax.inject.Inject
import kotlin.time.Duration.Companion.hours import kotlin.time.Duration.Companion.hours
import kotlin.time.Duration.Companion.seconds import kotlin.time.Duration.Companion.seconds
@ -108,13 +106,3 @@ inline fun AppPreferences.updateInterfacePreferences(block: InterfacePreferences
update { update {
interfacePreferences = interfacePreferences.toBuilder().apply(block).build() 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

@ -32,7 +32,6 @@ import androidx.tv.material3.Text
import com.github.damontecres.wholphin.data.model.BaseItem import com.github.damontecres.wholphin.data.model.BaseItem
import com.github.damontecres.wholphin.data.model.GetItemsFilter import com.github.damontecres.wholphin.data.model.GetItemsFilter
import com.github.damontecres.wholphin.preferences.UserPreferences import com.github.damontecres.wholphin.preferences.UserPreferences
import com.github.damontecres.wholphin.preferences.rememberTab
import com.github.damontecres.wholphin.ui.components.CollectionFolderGrid import com.github.damontecres.wholphin.ui.components.CollectionFolderGrid
import com.github.damontecres.wholphin.ui.components.ErrorMessage import com.github.damontecres.wholphin.ui.components.ErrorMessage
import com.github.damontecres.wholphin.ui.components.GenreCardGrid import com.github.damontecres.wholphin.ui.components.GenreCardGrid
@ -50,13 +49,8 @@ fun CollectionFolderMovie(
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
preferencesViewModel: PreferencesViewModel = hiltViewModel(), preferencesViewModel: PreferencesViewModel = hiltViewModel(),
) { ) {
val uiPrefs = preferences.appPreferences.interfacePreferences
val rememberedTabIndex = val rememberedTabIndex =
if (uiPrefs.rememberSelectedTab) { remember { preferencesViewModel.getRememberedTab(preferences, destination.itemId, 0) }
uiPrefs.getRememberedTabsOrDefault(destination.itemId.toString(), 0)
} else {
0
}
val tabs = listOf("Recommended", "Library", "Genres") val tabs = listOf("Recommended", "Library", "Genres")
var focusTabIndex by rememberSaveable { mutableIntStateOf(rememberedTabIndex) } var focusTabIndex by rememberSaveable { mutableIntStateOf(rememberedTabIndex) }
@ -66,13 +60,10 @@ fun CollectionFolderMovie(
val firstTabFocusRequester = remember { FocusRequester() } val firstTabFocusRequester = remember { FocusRequester() }
LaunchedEffect(Unit) { firstTabFocusRequester.tryRequestFocus() } LaunchedEffect(Unit) { firstTabFocusRequester.tryRequestFocus() }
if (uiPrefs.rememberSelectedTab) { LaunchedEffect(selectedTabIndex) {
LaunchedEffect(selectedTabIndex) { preferencesViewModel.saveRememberedTab(preferences, destination.itemId, selectedTabIndex)
preferencesViewModel.preferenceDataStore.updateData {
preferences.appPreferences.rememberTab(destination.itemId, selectedTabIndex)
}
}
} }
var showHeader by rememberSaveable { mutableStateOf(true) } var showHeader by rememberSaveable { mutableStateOf(true) }
val onClickItem = { item: BaseItem -> val onClickItem = { item: BaseItem ->

View file

@ -32,7 +32,6 @@ import androidx.tv.material3.Text
import com.github.damontecres.wholphin.data.model.BaseItem import com.github.damontecres.wholphin.data.model.BaseItem
import com.github.damontecres.wholphin.data.model.GetItemsFilter import com.github.damontecres.wholphin.data.model.GetItemsFilter
import com.github.damontecres.wholphin.preferences.UserPreferences import com.github.damontecres.wholphin.preferences.UserPreferences
import com.github.damontecres.wholphin.preferences.rememberTab
import com.github.damontecres.wholphin.ui.components.CollectionFolderGrid import com.github.damontecres.wholphin.ui.components.CollectionFolderGrid
import com.github.damontecres.wholphin.ui.components.ErrorMessage import com.github.damontecres.wholphin.ui.components.ErrorMessage
import com.github.damontecres.wholphin.ui.components.GenreCardGrid import com.github.damontecres.wholphin.ui.components.GenreCardGrid
@ -50,13 +49,8 @@ fun CollectionFolderTv(
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
preferencesViewModel: PreferencesViewModel = hiltViewModel(), preferencesViewModel: PreferencesViewModel = hiltViewModel(),
) { ) {
val uiPrefs = preferences.appPreferences.interfacePreferences
val rememberedTabIndex = val rememberedTabIndex =
if (uiPrefs.rememberSelectedTab) { remember { preferencesViewModel.getRememberedTab(preferences, destination.itemId, 0) }
uiPrefs.getRememberedTabsOrDefault(destination.itemId.toString(), 0)
} else {
0
}
val tabs = listOf("Recommended", "Library", "Genres") val tabs = listOf("Recommended", "Library", "Genres")
var focusTabIndex by rememberSaveable { mutableIntStateOf(rememberedTabIndex) } var focusTabIndex by rememberSaveable { mutableIntStateOf(rememberedTabIndex) }
@ -66,13 +60,10 @@ fun CollectionFolderTv(
val firstTabFocusRequester = remember { FocusRequester() } val firstTabFocusRequester = remember { FocusRequester() }
LaunchedEffect(Unit) { firstTabFocusRequester.tryRequestFocus() } LaunchedEffect(Unit) { firstTabFocusRequester.tryRequestFocus() }
if (uiPrefs.rememberSelectedTab) { LaunchedEffect(selectedTabIndex) {
LaunchedEffect(selectedTabIndex) { preferencesViewModel.saveRememberedTab(preferences, destination.itemId, selectedTabIndex)
preferencesViewModel.preferenceDataStore.updateData {
preferences.appPreferences.rememberTab(destination.itemId, selectedTabIndex)
}
}
} }
val onClickItem = { item: BaseItem -> val onClickItem = { item: BaseItem ->
preferencesViewModel.navigationManager.navigateTo(item.destination()) preferencesViewModel.navigationManager.navigateTo(item.destination())
} }

View file

@ -4,6 +4,7 @@ import androidx.datastore.core.DataStore
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import com.github.damontecres.wholphin.preferences.AppPreferences import com.github.damontecres.wholphin.preferences.AppPreferences
import com.github.damontecres.wholphin.ui.nav.NavigationManager import com.github.damontecres.wholphin.ui.nav.NavigationManager
import com.github.damontecres.wholphin.util.RememberTabManager
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import javax.inject.Inject import javax.inject.Inject
@ -13,4 +14,6 @@ class PreferencesViewModel
constructor( constructor(
val preferenceDataStore: DataStore<AppPreferences>, val preferenceDataStore: DataStore<AppPreferences>,
val navigationManager: NavigationManager, val navigationManager: NavigationManager,
) : ViewModel() val rememberTabManager: RememberTabManager,
) : ViewModel(),
RememberTabManager by rememberTabManager

View file

@ -0,0 +1,24 @@
package com.github.damontecres.wholphin.util
import com.github.damontecres.wholphin.preferences.UserPreferences
import java.util.UUID
interface RememberTabManager {
/**
* If enabled, get the remembered tab index for the given item
*/
fun getRememberedTab(
preferences: UserPreferences,
itemId: UUID,
defaultTab: Int,
): Int
/**
* If enabled, save the remembered tab index for the given item
*/
fun saveRememberedTab(
preferences: UserPreferences,
itemId: UUID,
tabIndex: Int,
)
}