diff --git a/app/src/main/java/com/github/damontecres/wholphin/MainActivity.kt b/app/src/main/java/com/github/damontecres/wholphin/MainActivity.kt index c285294c..ac72c3b5 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/MainActivity.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/MainActivity.kt @@ -12,6 +12,7 @@ import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.key +import androidx.compose.runtime.livedata.observeAsState import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue @@ -96,22 +97,19 @@ class MainActivity : AppCompatActivity() { ) { var isRestoringSession by remember { mutableStateOf(true) } LaunchedEffect(Unit) { - if (appPreferences.currentServerId.isNotBlank() && appPreferences.currentUserId.isNotBlank()) { - try { - serverRepository.restoreSession( - appPreferences.currentServerId?.toUUIDOrNull(), - appPreferences.currentUserId?.toUUIDOrNull(), - ) - } catch (ex: Exception) { - Timber.e(ex, "Exception restoring session") - } - Timber.d("MainActivity session restored") + try { + serverRepository.restoreSession( + appPreferences.currentServerId?.toUUIDOrNull(), + appPreferences.currentUserId?.toUUIDOrNull(), + ) + } catch (ex: Exception) { + Timber.e(ex, "Exception restoring session") } isRestoringSession = false } - val server = serverRepository.currentServer - val user = serverRepository.currentUser - val userDto = serverRepository.currentUserDto + val server by serverRepository.currentServer.observeAsState() + val user by serverRepository.currentUser.observeAsState() + val userDto by serverRepository.currentUserDto.observeAsState() val preferences = UserPreferences( @@ -139,8 +137,6 @@ class MainActivity : AppCompatActivity() { val initialDestination = if (server != null && user != null) { Destination.Home() - } else if (server != null) { - Destination.UserList } else { Destination.ServerList } @@ -155,9 +151,6 @@ class MainActivity : AppCompatActivity() { } } } - LaunchedEffect(server, user) { - serverEventListener.init(server, user) - } ApplicationContent( user = user, server = server, diff --git a/app/src/main/java/com/github/damontecres/wholphin/data/ItemPlaybackRepository.kt b/app/src/main/java/com/github/damontecres/wholphin/data/ItemPlaybackRepository.kt index 27a12d13..97537146 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/data/ItemPlaybackRepository.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/data/ItemPlaybackRepository.kt @@ -25,7 +25,7 @@ class ItemPlaybackRepository itemId: UUID, item: BaseItem, ): ChosenStreams? = - serverRepository.currentUser?.let { user -> + serverRepository.currentUser.value?.let { user -> val itemPlayback = itemPlaybackDao.getItem(user = user, itemId = itemId) if (itemPlayback != null) { Timber.v("Got itemPlayback for %s", itemId) @@ -72,7 +72,7 @@ class ItemPlaybackRepository sourceId: UUID, ): ItemPlayback? = withContext(Dispatchers.IO) { - serverRepository.currentUser?.let { user -> + serverRepository.currentUser.value?.let { user -> val itemPlayback = ItemPlayback( userId = user.rowId, @@ -89,7 +89,7 @@ class ItemPlaybackRepository itemPlayback: ItemPlayback?, trackIndex: Int, type: MediaStreamType, - ) = serverRepository.currentUser?.let { user -> + ) = serverRepository.currentUser.value?.let { user -> var toSave = itemPlayback ?: ItemPlayback( userId = user.rowId, @@ -113,7 +113,9 @@ class ItemPlaybackRepository val toSave = if (itemPlayback.userId < 0) { val userRowId = - serverRepository.currentUser?.rowId?.takeIf { it >= 0 } + serverRepository.currentUser.value + ?.rowId + ?.takeIf { it >= 0 } ?: throw IllegalStateException("Trying to save an ItemPlayback without a user, but there is no current user") itemPlayback.copy(userId = userRowId) } else { diff --git a/app/src/main/java/com/github/damontecres/wholphin/data/NavDrawerItemRepository.kt b/app/src/main/java/com/github/damontecres/wholphin/data/NavDrawerItemRepository.kt index 11a81a62..e335cba0 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/data/NavDrawerItemRepository.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/data/NavDrawerItemRepository.kt @@ -27,7 +27,7 @@ class NavDrawerItemRepository val user = serverRepository.currentUser val userViews = api.userViewsApi - .getUserViews(userId = user?.id) + .getUserViews(userId = user.value?.id) .content.items val builtins = listOf(NavDrawerItem.Favorites) @@ -46,7 +46,7 @@ class NavDrawerItemRepository } suspend fun getFilteredNavDrawerItems(items: List): List { - val user = serverRepository.currentUser + val user = serverRepository.currentUser.value val navDrawerPins = user ?.let { diff --git a/app/src/main/java/com/github/damontecres/wholphin/data/ServerRepository.kt b/app/src/main/java/com/github/damontecres/wholphin/data/ServerRepository.kt index 9b910c78..f16f9aab 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/data/ServerRepository.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/data/ServerRepository.kt @@ -2,14 +2,15 @@ package com.github.damontecres.wholphin.data import android.content.Context import android.content.SharedPreferences -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.setValue import androidx.core.content.edit import androidx.datastore.core.DataStore +import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.map import com.github.damontecres.wholphin.data.model.JellyfinServer import com.github.damontecres.wholphin.data.model.JellyfinUser import com.github.damontecres.wholphin.preferences.AppPreferences +import com.github.damontecres.wholphin.ui.setValueOnMain import com.github.damontecres.wholphin.ui.toServerString import dagger.hilt.android.qualifiers.ApplicationContext import kotlinx.coroutines.Dispatchers @@ -41,12 +42,12 @@ class ServerRepository ) { private val sharedPreferences = getServerSharedPreferences(context) - private var _currentServer by mutableStateOf(null) - val currentServer get() = _currentServer - private var _currentUser by mutableStateOf(null) - val currentUser get() = _currentUser - private var _currentUserDto by mutableStateOf(null) - val currentUserDto get() = _currentUserDto + private var _current = MutableLiveData(null) + val current: LiveData = _current + + val currentServer: LiveData get() = _current.map { it?.server } + val currentUser: LiveData get() = _current.map { it?.user } + val currentUserDto: LiveData get() = _current.map { it?.userDto } /** * Adds a server to the app database and updated the [ApiClient] to the server's URL @@ -57,19 +58,8 @@ class ServerRepository withContext(Dispatchers.IO) { serverDao.addOrUpdateServer(server) } - changeServer(server) - } - - /** - * Updates the [ApiClient] to the server's URL - * - * The current user is removed - */ - fun changeServer(server: JellyfinServer) { apiClient.update(baseUrl = server.url, accessToken = null) - _currentServer = server - _currentUser = null - _currentUserDto = null + _current.setValueOnMain(null) } /** @@ -109,9 +99,7 @@ class ServerRepository }.build() } withContext(Dispatchers.Main) { - _currentUserDto = userDto - _currentServer = updatedServer - _currentUser = updatedUser + _current.value = CurrentUser(updatedServer, updatedUser, userDto) } sharedPreferences.edit(true) { putString(SERVER_URL_KEY, updatedServer.url) @@ -127,6 +115,7 @@ class ServerRepository userId: UUID?, ): Boolean { if (serverId == null || userId == null) { + _current.setValueOnMain(null) return false } val serverAndUsers = @@ -186,8 +175,9 @@ class ServerRepository suspend fun removeUser(user: JellyfinUser) { if (currentUser == user) { - _currentUser = null - _currentUserDto = null + withContext(Dispatchers.Main) { + _current.value = null + } userPreferencesDataStore.updateData { it .toBuilder() @@ -204,9 +194,9 @@ class ServerRepository suspend fun removeServer(server: JellyfinServer) { if (currentServer == server) { - _currentServer = null - _currentUser = null - _currentUserDto = null + withContext(Dispatchers.Main) { + _current.value = null + } userPreferencesDataStore.updateData { it .toBuilder() @@ -222,6 +212,18 @@ class ServerRepository } } + suspend fun switchServerOrUser() { + apiClient.update(baseUrl = null, accessToken = null) + userPreferencesDataStore.updateData { + it + .toBuilder() + .apply { + currentServerId = "" + currentUserId = "" + }.build() + } + } + companion object { fun getServerSharedPreferences(context: Context): SharedPreferences = context.getSharedPreferences( @@ -233,3 +235,9 @@ class ServerRepository const val ACCESS_TOKEN_KEY = "current.accessToken" } } + +data class CurrentUser( + val server: JellyfinServer, + val user: JellyfinUser, + val userDto: UserDto, +) diff --git a/app/src/main/java/com/github/damontecres/wholphin/data/model/JellyfinServer.kt b/app/src/main/java/com/github/damontecres/wholphin/data/model/JellyfinServer.kt index af3377cd..c9c2f6bf 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/data/model/JellyfinServer.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/data/model/JellyfinServer.kt @@ -1,3 +1,5 @@ +@file:UseSerializers(UUIDSerializer::class) + package com.github.damontecres.wholphin.data.model import androidx.room.ColumnInfo @@ -8,9 +10,13 @@ import androidx.room.Index import androidx.room.PrimaryKey import androidx.room.Relation import com.github.damontecres.wholphin.ui.isNotNullOrBlank +import kotlinx.serialization.Serializable +import kotlinx.serialization.UseSerializers +import org.jellyfin.sdk.model.serializer.UUIDSerializer import java.util.UUID @Entity(tableName = "servers") +@Serializable data class JellyfinServer( @PrimaryKey val id: UUID, val name: String?, diff --git a/app/src/main/java/com/github/damontecres/wholphin/data/model/Playlist.kt b/app/src/main/java/com/github/damontecres/wholphin/data/model/Playlist.kt index c41a0bbd..a4ab91ad 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/data/model/Playlist.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/data/model/Playlist.kt @@ -145,7 +145,7 @@ class PlaylistCreator name: String, initialItems: List, ): UUID? = - serverRepository.currentUser?.let { user -> + serverRepository.currentUser.value?.let { user -> api.playlistsApi .createPlaylist( CreatePlaylistDto( diff --git a/app/src/main/java/com/github/damontecres/wholphin/hilt/AppModule.kt b/app/src/main/java/com/github/damontecres/wholphin/hilt/AppModule.kt index 98eb1f81..6d55525d 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/hilt/AppModule.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/hilt/AppModule.kt @@ -95,7 +95,7 @@ object AppModule { .addInterceptor { val request = it.request() val newRequest = - serverRepository.currentUser?.accessToken?.let { token -> + serverRepository.currentUser.value?.accessToken?.let { token -> request .newBuilder() .addHeader( @@ -105,7 +105,7 @@ object AppModule { clientVersion = clientInfo.version, deviceId = deviceInfo.id, deviceName = deviceInfo.name, - accessToken = serverRepository.currentUser?.accessToken, + accessToken = token, ), ).build() } @@ -149,7 +149,7 @@ object AppModule { appPreference: DataStore, @IoCoroutineScope scope: CoroutineScope, ) = object : RememberTabManager { - fun key(itemId: String) = "${serverRepository.currentServer?.id}_${serverRepository.currentUser?.id}_$itemId" + fun key(itemId: String) = "${serverRepository.currentServer.value?.id}_${serverRepository.currentUser.value?.id}_$itemId" override fun getRememberedTab( preferences: UserPreferences, diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/components/CollectionFolderGrid.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/components/CollectionFolderGrid.kt index 1622904d..2426c44a 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/components/CollectionFolderGrid.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/components/CollectionFolderGrid.kt @@ -108,7 +108,7 @@ class CollectionFolderViewModel val sortAndDirection = if (initialSortAndDirection == null) { - serverRepository.currentUser?.let { user -> + serverRepository.currentUser.value?.let { user -> libraryDisplayInfoDao.getItem(user, itemId)?.sortAndDirection } ?: SortAndDirection.DEFAULT } else { @@ -123,7 +123,7 @@ class CollectionFolderViewModel recursive: Boolean, filter: GetItemsFilter, ) { - serverRepository.currentUser?.let { user -> + serverRepository.currentUser.value?.let { user -> viewModelScope.launch(Dispatchers.IO) { val libraryDisplayInfo = LibraryDisplayInfo( diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/components/RecommendedMovie.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/components/RecommendedMovie.kt index 132df264..48388419 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/components/RecommendedMovie.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/components/RecommendedMovie.kt @@ -133,7 +133,7 @@ class RecommendedMovieViewModel val suggestionsRequest = GetSuggestionsRequest( - userId = serverRepository.currentUser?.id, + userId = serverRepository.currentUser.value?.id, type = listOf(BaseItemKind.MOVIE), ) val suggestedItems = ApiRequestPager(api, suggestionsRequest, GetSuggestionsRequestHandler, viewModelScope) diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/components/RecommendedTvShow.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/components/RecommendedTvShow.kt index 18cd7fbe..f504c614 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/components/RecommendedTvShow.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/components/RecommendedTvShow.kt @@ -161,7 +161,7 @@ class RecommendedTvShowViewModel val suggestionsRequest = GetSuggestionsRequest( - userId = serverRepository.currentUser?.id, + userId = serverRepository.currentUser.value?.id, type = listOf(BaseItemKind.SERIES), ) val suggestedItems = ApiRequestPager(api, suggestionsRequest, GetSuggestionsRequestHandler, viewModelScope) diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/detail/CollectionFolderLiveTv.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/detail/CollectionFolderLiveTv.kt index cf74a205..d7e364fa 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/detail/CollectionFolderLiveTv.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/detail/CollectionFolderLiveTv.kt @@ -66,7 +66,7 @@ class LiveTvCollectionViewModel viewModelScope.launchIO { val folders = api.liveTvApi - .getRecordingFolders(userId = serverRepository.currentUser?.id) + .getRecordingFolders(userId = serverRepository.currentUser.value?.id) .content.items .map { TabId(it.name ?: "Recordings", it.id) } this@LiveTvCollectionViewModel.recordingFolders.setValueOnMain(folders) diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/detail/DebugPage.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/detail/DebugPage.kt index c5a2d03f..8535c336 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/detail/DebugPage.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/detail/DebugPage.kt @@ -61,7 +61,7 @@ class DebugViewModel init { viewModelScope.launchIO { - serverRepository.currentUser?.rowId?.let { + serverRepository.currentUser.value?.rowId?.let { val results = itemPlaybackDao.getItems(it) withContext(Dispatchers.Main) { itemPlaybacks.value = results diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/detail/movie/MovieDetails.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/detail/movie/MovieDetails.kt index f4cd91b4..5dc0993c 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/detail/movie/MovieDetails.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/detail/movie/MovieDetails.kt @@ -303,7 +303,7 @@ fun MovieDetails( ItemDetailsDialog( info = info, showFilePath = - viewModel.serverRepository.currentUserDto + viewModel.serverRepository.currentUserDto.value ?.policy ?.isAdministrator == true, onDismissRequest = { overviewDialog = null }, diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/detail/movie/MovieViewModel.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/detail/movie/MovieViewModel.kt index 250a8f0b..51c7dafa 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/detail/movie/MovieViewModel.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/detail/movie/MovieViewModel.kt @@ -147,7 +147,7 @@ class MovieViewModel api.libraryApi .getSimilarItems( GetSimilarItemsRequest( - userId = serverRepository.currentUser?.id, + userId = serverRepository.currentUser.value?.id, itemId = itemId, fields = SlimItemFields, limit = 25, diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/detail/series/SeriesOverview.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/detail/series/SeriesOverview.kt index 5a77d3d9..7d18dfec 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/detail/series/SeriesOverview.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/detail/series/SeriesOverview.kt @@ -346,7 +346,7 @@ fun SeriesOverview( ItemDetailsDialog( info = info, showFilePath = - viewModel.serverRepository.currentUserDto + viewModel.serverRepository.currentUserDto.value ?.policy ?.isAdministrator == true, onDismissRequest = { overviewDialog = null }, diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/detail/series/SeriesViewModel.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/detail/series/SeriesViewModel.kt index b5c99411..33a5e476 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/detail/series/SeriesViewModel.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/detail/series/SeriesViewModel.kt @@ -121,7 +121,7 @@ class SeriesViewModel api.libraryApi .getSimilarItems( GetSimilarItemsRequest( - userId = serverRepository.currentUser?.id, + userId = serverRepository.currentUser.value?.id, itemId = itemId, fields = SlimItemFields, limit = 25, diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/main/HomeViewModel.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/main/HomeViewModel.kt index 839e282e..d24062a5 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/main/HomeViewModel.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/main/HomeViewModel.kt @@ -77,7 +77,7 @@ class HomeViewModel ) { Timber.d("init HomeViewModel") - serverRepository.currentUserDto?.let { userDto -> + serverRepository.currentUserDto.value?.let { userDto -> val includedIds = navDrawerItemRepository .getFilteredNavDrawerItems(navDrawerItemRepository.getNavDrawerItems()) diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/nav/ApplicationContent.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/nav/ApplicationContent.kt index e1aff5ae..94cc5e33 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/nav/ApplicationContent.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/nav/ApplicationContent.kt @@ -10,6 +10,7 @@ import androidx.navigation3.ui.NavDisplay import com.github.damontecres.wholphin.data.model.JellyfinServer import com.github.damontecres.wholphin.data.model.JellyfinUser import com.github.damontecres.wholphin.preferences.UserPreferences +import com.github.damontecres.wholphin.ui.components.ErrorMessage import org.jellyfin.sdk.model.api.DeviceProfile /** @@ -45,7 +46,7 @@ fun ApplicationContent( deviceProfile = deviceProfile, modifier = modifier.fillMaxSize(), ) - } else { + } else if (user != null && server != null) { NavDrawer( destination = key, preferences = preferences, @@ -54,6 +55,8 @@ fun ApplicationContent( server = server, modifier = modifier, ) + } else { + ErrorMessage("Trying to go to $key without a user logged in", null) } } }, diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/nav/Destination.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/nav/Destination.kt index 01b262bd..ff2ffdde 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/nav/Destination.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/nav/Destination.kt @@ -6,6 +6,7 @@ import androidx.navigation3.runtime.NavKey import com.github.damontecres.wholphin.data.model.BaseItem import com.github.damontecres.wholphin.data.model.GetItemsFilter import com.github.damontecres.wholphin.data.model.ItemPlayback +import com.github.damontecres.wholphin.data.model.JellyfinServer import com.github.damontecres.wholphin.ui.detail.series.SeasonEpisode import com.github.damontecres.wholphin.ui.detail.series.SeasonEpisodeIds import com.github.damontecres.wholphin.ui.preferences.PreferenceScreenOption @@ -29,7 +30,9 @@ sealed class Destination( data object ServerList : Destination(true) @Serializable - data object UserList : Destination(true) + data class UserList( + val server: JellyfinServer, + ) : Destination(true) @Serializable data class Home( diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/nav/DestinationContent.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/nav/DestinationContent.kt index 099f4816..8ba5f51a 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/nav/DestinationContent.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/nav/DestinationContent.kt @@ -54,7 +54,7 @@ fun DestinationContent( ) Destination.ServerList -> SwitchServerContent(modifier) - Destination.UserList -> SwitchUserContent(modifier) + is Destination.UserList -> SwitchUserContent(destination.server, modifier) is Destination.Settings -> PreferencesPage( diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/nav/NavDrawer.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/nav/NavDrawer.kt index 20b8c031..ce69e8ce 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/nav/NavDrawer.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/nav/NavDrawer.kt @@ -84,7 +84,6 @@ import kotlinx.coroutines.isActive import kotlinx.coroutines.withContext import org.jellyfin.sdk.model.api.CollectionType import org.jellyfin.sdk.model.api.DeviceProfile -import timber.log.Timber import java.time.LocalTime import java.util.UUID import javax.inject.Inject @@ -141,7 +140,7 @@ class NavDrawerViewModel null } } - Timber.v("Found $index => $key") +// Timber.v("Found $index => $key") if (index != null) { selectedIndex.setValueOnMain(index) break @@ -198,8 +197,8 @@ data class ServerNavDrawerItem( fun NavDrawer( destination: Destination, preferences: UserPreferences, - user: JellyfinUser?, - server: JellyfinServer?, + user: JellyfinUser, + server: JellyfinServer, deviceProfile: DeviceProfile, modifier: Modifier = Modifier, viewModel: NavDrawerViewModel = @@ -319,7 +318,11 @@ fun NavDrawer( selected = false, interactionSource = interactionSource, onClick = { - viewModel.navigationManager.navigateTo(Destination.UserList) + viewModel.navigationManager.navigateToFromDrawer( + Destination.UserList( + server, + ), + ) }, modifier = Modifier.animateItem(), ) diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/nav/NavigationManager.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/nav/NavigationManager.kt index 8a9b50a3..d07a9f9b 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/nav/NavigationManager.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/nav/NavigationManager.kt @@ -28,7 +28,12 @@ class NavigationManager */ fun navigateToFromDrawer(destination: Destination) { goToHome() - backStack.add(destination) + if (destination == Destination.ServerList || destination is Destination.UserList) { + backStack.add(destination) + (0.. backStack.removeAt(0) } + } else { + backStack.add(destination) + } log() } diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/playback/PlaybackViewModel.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/playback/PlaybackViewModel.kt index 3cb73b14..82ea4881 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/playback/PlaybackViewModel.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/playback/PlaybackViewModel.kt @@ -268,13 +268,14 @@ class PlaybackViewModel if (itemPlayback != null) { itemPlayback } else { - val user = serverRepository.currentUser!! - itemPlaybackDao.getItem(user, base.id)?.let { - Timber.v("Fetched itemPlayback from DB: %s", it) - if (it.sourceId != null) { - it - } else { - null + serverRepository.currentUser.value?.let { user -> + itemPlaybackDao.getItem(user, base.id)?.let { + Timber.v("Fetched itemPlayback from DB: %s", it) + if (it.sourceId != null) { + it + } else { + null + } } } } diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/preferences/PreferencesViewModel.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/preferences/PreferencesViewModel.kt index e73e0cef..57701e6b 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/preferences/PreferencesViewModel.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/preferences/PreferencesViewModel.kt @@ -49,7 +49,7 @@ class PreferencesViewModel init { viewModelScope.launchIO { - serverRepository.currentUser?.let { user -> + serverRepository.currentUser.value?.let { user -> allNavDrawerItems = navDrawerItemRepository.getNavDrawerItems() val pins = serverPreferencesDao.getNavDrawerPinnedItems(user) val navDrawerPins = allNavDrawerItems.associateWith { pins.isPinned(it.id) } @@ -60,7 +60,7 @@ class PreferencesViewModel fun updatePins(newSelectedItems: List) { viewModelScope.launchIO(ExceptionHandler(true)) { - serverRepository.currentUser?.let { user -> + serverRepository.currentUser.value?.let { user -> val disabledItems = mutableListOf().apply { addAll(allNavDrawerItems) diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/setup/ServerList.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/setup/ServerList.kt index a83cfb6d..827cf44b 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/setup/ServerList.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/setup/ServerList.kt @@ -49,6 +49,7 @@ fun ServerList( servers: List, connectionStatus: Map, onSwitchServer: (JellyfinServer) -> Unit, + onTestServer: (JellyfinServer) -> Unit, onAddServer: () -> Unit, onRemoveServer: (JellyfinServer) -> Unit, allowAdd: Boolean, @@ -61,7 +62,7 @@ fun ServerList( items(servers) { server -> val status = connectionStatus[server.id] ?: ServerConnectionStatus.Pending ListItem( - enabled = status == ServerConnectionStatus.Success, + enabled = true, selected = false, headlineContent = { Text(text = server.name?.ifBlank { null } ?: server.url) }, supportingContent = { if (server.name.isNotNullOrBlank()) Text(text = server.url) }, @@ -77,12 +78,18 @@ fun ServerList( Icon( imageVector = Icons.Default.Warning, contentDescription = status.message, - tint = MaterialTheme.colorScheme.error, + tint = MaterialTheme.colorScheme.errorContainer, ) } } }, - onClick = { onSwitchServer.invoke(server) }, + onClick = { + when (status) { + ServerConnectionStatus.Success -> onSwitchServer.invoke(server) + ServerConnectionStatus.Pending -> {} + is ServerConnectionStatus.Error -> onTestServer.invoke(server) + } + }, onLongClick = { if (allowDelete) { showDeleteDialog = server diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/setup/SwitchServerContent.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/setup/SwitchServerContent.kt index 0dbce500..c90dbe41 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/setup/SwitchServerContent.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/setup/SwitchServerContent.kt @@ -43,9 +43,8 @@ import com.github.damontecres.wholphin.util.LoadingState @Composable fun SwitchServerContent( modifier: Modifier = Modifier, - viewModel: SwitchUserViewModel = hiltViewModel(), + viewModel: SwitchServerViewModel = hiltViewModel(), ) { - val currentServer = viewModel.serverRepository.currentServer val servers by viewModel.servers.observeAsState(listOf()) val serverStatus by viewModel.serverStatus.observeAsState(mapOf()) @@ -90,6 +89,9 @@ fun SwitchServerContent( onSwitchServer = { viewModel.addServer(it.url) }, + onTestServer = { + viewModel.testServer(it) + }, onAddServer = { showAddServer = true }, @@ -142,6 +144,9 @@ fun SwitchServerContent( onSwitchServer = { viewModel.addServer(it.url) }, + onTestServer = { + viewModel.testServer(it) + }, onAddServer = {}, onRemoveServer = {}, allowAdd = false, diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/setup/SwitchServerViewModel.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/setup/SwitchServerViewModel.kt new file mode 100644 index 00000000..9e962992 --- /dev/null +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/setup/SwitchServerViewModel.kt @@ -0,0 +1,214 @@ +package com.github.damontecres.wholphin.ui.setup + +import android.content.Context +import android.widget.Toast +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.github.damontecres.wholphin.R +import com.github.damontecres.wholphin.data.JellyfinServerDao +import com.github.damontecres.wholphin.data.ServerRepository +import com.github.damontecres.wholphin.data.model.JellyfinServer +import com.github.damontecres.wholphin.ui.launchIO +import com.github.damontecres.wholphin.ui.nav.Destination +import com.github.damontecres.wholphin.ui.nav.NavigationManager +import com.github.damontecres.wholphin.ui.showToast +import com.github.damontecres.wholphin.util.LoadingState +import dagger.hilt.android.lifecycle.HiltViewModel +import dagger.hilt.android.qualifiers.ApplicationContext +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.delay +import kotlinx.coroutines.withContext +import org.jellyfin.sdk.Jellyfin +import org.jellyfin.sdk.api.client.HttpClientOptions +import org.jellyfin.sdk.api.client.extensions.quickConnectApi +import org.jellyfin.sdk.api.client.extensions.systemApi +import org.jellyfin.sdk.model.serializer.toUUID +import org.jellyfin.sdk.model.serializer.toUUIDOrNull +import timber.log.Timber +import java.util.UUID +import javax.inject.Inject +import kotlin.time.Duration.Companion.seconds + +@HiltViewModel +class SwitchServerViewModel + @Inject + constructor( + @param:ApplicationContext private val context: Context, + val jellyfin: Jellyfin, + val serverRepository: ServerRepository, + val serverDao: JellyfinServerDao, + val navigationManager: NavigationManager, + ) : ViewModel() { + val servers = MutableLiveData>(listOf()) + val serverStatus = MutableLiveData>(mapOf()) + val serverQuickConnect = MutableLiveData>(mapOf()) + + val discoveredServers = MutableLiveData>(listOf()) + + val addServerState = MutableLiveData(LoadingState.Pending) + + fun clearAddServerState() { + addServerState.value = LoadingState.Pending + } + + init { + init() + } + + fun init() { + viewModelScope.launchIO { + withContext(Dispatchers.Main) { + serverStatus.value = mapOf() + serverQuickConnect.value = mapOf() + } + + val allServers = + serverDao + .getServers() + .map { it.server } + .sortedWith(compareBy { it.name }.thenBy { it.url }) + withContext(Dispatchers.Main) { + servers.value = allServers + } + allServers.forEach { server -> + internalTestServer(server) + } + } + } + + fun testServer(server: JellyfinServer) { + serverStatus.value = + serverStatus.value!!.toMutableMap().apply { + put( + server.id, + ServerConnectionStatus.Pending, + ) + } + viewModelScope.launchIO { + delay(1000) + val result = internalTestServer(server) + if (result == ServerConnectionStatus.Success) { + showToast(context, context.getString(R.string.success), Toast.LENGTH_SHORT) + } else if (result is ServerConnectionStatus.Error) { + showToast(context, result.message ?: "Error", Toast.LENGTH_SHORT) + } + } + } + + private suspend fun internalTestServer(server: JellyfinServer): ServerConnectionStatus = + try { + jellyfin + .createApi( + server.url, + httpClientOptions = + HttpClientOptions( + requestTimeout = 6.seconds, + connectTimeout = 6.seconds, + socketTimeout = 6.seconds, + ), + ).systemApi + .getPublicSystemInfo() + withContext(Dispatchers.Main) { + serverStatus.value = + serverStatus.value!!.toMutableMap().apply { + put( + server.id, + ServerConnectionStatus.Success, + ) + } + } + ServerConnectionStatus.Success + } catch (ex: Exception) { + val status = ServerConnectionStatus.Error(ex.localizedMessage) + Timber.w(ex, "Error checking server ${server.url}") + withContext(Dispatchers.Main) { + serverStatus.value = + serverStatus.value!!.toMutableMap().apply { + put( + server.id, + status, + ) + } + } + status + } + + fun addServer(serverUrl: String) { + addServerState.value = LoadingState.Loading + viewModelScope.launchIO { + try { + val serverInfo by jellyfin + .createApi(serverUrl) + .systemApi + .getPublicSystemInfo() + val id = serverInfo.id?.toUUIDOrNull() + if (id != null && serverInfo.startupWizardCompleted == true) { + val server = + JellyfinServer( + id = id, + name = serverInfo.serverName, + url = serverUrl, + ) + serverRepository.addAndChangeServer(server) + val quickConnect = + jellyfin + .createApi(serverUrl) + .quickConnectApi + .getQuickConnectEnabled() + .content + withContext(Dispatchers.Main) { + serverQuickConnect.value = + serverQuickConnect.value!!.toMutableMap().apply { + put(id, quickConnect) + } + } + withContext(Dispatchers.Main) { + addServerState.value = LoadingState.Success + navigationManager.navigateTo(Destination.UserList(server)) + } + } else { + withContext(Dispatchers.Main) { + addServerState.value = + LoadingState.Error("Server returned invalid response") + } + } + } catch (ex: Exception) { + Timber.w(ex, "Error creating API for $serverUrl") + withContext(Dispatchers.Main) { + addServerState.value = + LoadingState.Error(exception = ex) + } + } + } + } + + fun removeServer(server: JellyfinServer) { + viewModelScope.launchIO { + serverRepository.removeServer(server) + init() + } + } + + fun discoverServers() { + viewModelScope.launchIO { + jellyfin.discovery.discoverLocalServers().collect { server -> + val newServerList = + discoveredServers.value!! + .toMutableList() + .apply { + add( + JellyfinServer( + server.id.toUUID(), + server.name, + server.address, + ), + ) + } + withContext(Dispatchers.Main) { + discoveredServers.value = newServerList + } + } + } + } + } diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/setup/SwitchUserContent.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/setup/SwitchUserContent.kt index 49e9be8a..f747a612 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/setup/SwitchUserContent.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/setup/SwitchUserContent.kt @@ -39,6 +39,7 @@ import androidx.tv.material3.MaterialTheme import androidx.tv.material3.Text import androidx.tv.material3.surfaceColorAtElevation import com.github.damontecres.wholphin.R +import com.github.damontecres.wholphin.data.model.JellyfinServer import com.github.damontecres.wholphin.ui.components.BasicDialog import com.github.damontecres.wholphin.ui.components.CircularProgress import com.github.damontecres.wholphin.ui.components.EditTextBox @@ -49,17 +50,20 @@ import com.github.damontecres.wholphin.util.LoadingState @Composable fun SwitchUserContent( + currentServer: JellyfinServer, modifier: Modifier = Modifier, - viewModel: SwitchUserViewModel = hiltViewModel(), + viewModel: SwitchUserViewModel = + hiltViewModel( + creationCallback = { it.create(currentServer) }, + ), ) { val context = LocalContext.current - val currentServer = viewModel.serverRepository.currentServer - val currentUser = viewModel.serverRepository.currentUser +// val currentServer by viewModel.serverRepository.currentServer.observeAsState() + val currentUser by viewModel.serverRepository.currentUser.observeAsState() val users by viewModel.users.observeAsState(listOf()) - val serverQuickConnect by viewModel.serverQuickConnect.observeAsState(mapOf()) - val quickConnectEnabled = currentServer?.let { serverQuickConnect[it.id] ?: false } ?: false + val quickConnectEnabled by viewModel.serverQuickConnect.observeAsState(false) val quickConnect by viewModel.quickConnectState.observeAsState(null) var showAddUser by remember { mutableStateOf(false) } @@ -109,7 +113,7 @@ fun SwitchUserContent( users = users, currentUser = currentUser, onSwitchUser = { user -> - viewModel.switchUser(server, user) + viewModel.switchUser(user) }, onAddUser = { showAddUser = true }, onRemoveUser = { user -> diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/setup/SwitchUserViewModel.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/setup/SwitchUserViewModel.kt index 4f5a9733..abd7d4c4 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/setup/SwitchUserViewModel.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/setup/SwitchUserViewModel.kt @@ -8,13 +8,18 @@ import com.github.damontecres.wholphin.data.ServerRepository import com.github.damontecres.wholphin.data.model.JellyfinServer import com.github.damontecres.wholphin.data.model.JellyfinUser import com.github.damontecres.wholphin.ui.launchIO -import com.github.damontecres.wholphin.ui.nav.Destination import com.github.damontecres.wholphin.ui.nav.NavigationManager +import com.github.damontecres.wholphin.ui.setValueOnMain +import com.github.damontecres.wholphin.util.ExceptionHandler import com.github.damontecres.wholphin.util.LoadingState +import dagger.assisted.Assisted +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job import kotlinx.coroutines.delay +import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import org.jellyfin.sdk.Jellyfin import org.jellyfin.sdk.api.client.HttpClientOptions @@ -25,42 +30,41 @@ import org.jellyfin.sdk.api.client.extensions.systemApi import org.jellyfin.sdk.api.client.extensions.userApi import org.jellyfin.sdk.model.api.QuickConnectDto import org.jellyfin.sdk.model.api.QuickConnectResult -import org.jellyfin.sdk.model.serializer.toUUID -import org.jellyfin.sdk.model.serializer.toUUIDOrNull import timber.log.Timber -import java.util.UUID -import javax.inject.Inject import kotlin.time.Duration.Companion.seconds -@HiltViewModel +@HiltViewModel(assistedFactory = SwitchUserViewModel.Factory::class) class SwitchUserViewModel - @Inject + @AssistedInject constructor( val jellyfin: Jellyfin, val serverRepository: ServerRepository, val serverDao: JellyfinServerDao, val navigationManager: NavigationManager, + @Assisted val server: JellyfinServer, ) : ViewModel() { - val servers = MutableLiveData>(listOf()) - val serverStatus = MutableLiveData>(mapOf()) - val serverQuickConnect = MutableLiveData>(mapOf()) + @AssistedFactory + interface Factory { + fun create(server: JellyfinServer): SwitchUserViewModel + } + + init { + viewModelScope.launch(Dispatchers.Main + ExceptionHandler()) { + serverRepository.switchServerOrUser() + } + } + + val serverQuickConnect = MutableLiveData(false) val users = MutableLiveData>(listOf()) val quickConnectState = MutableLiveData(null) private var quickConnectJob: Job? = null - val discoveredServers = MutableLiveData>(listOf()) - - val addServerState = MutableLiveData(LoadingState.Pending) val switchUserState = MutableLiveData(LoadingState.Pending) val loginAttempts = MutableLiveData(0) - fun clearAddServerState() { - addServerState.value = LoadingState.Pending - } - fun clearSwitchUserState() { switchUserState.value = LoadingState.Pending } @@ -74,91 +78,47 @@ class SwitchUserViewModel } fun init() { + quickConnectJob?.cancel() viewModelScope.launchIO { - quickConnectJob?.cancel() + users.setValueOnMain(listOf()) + val serverUsers = + serverDao.getServer(server.id)?.users?.sortedBy { it.name } ?: listOf() withContext(Dispatchers.Main) { - users.value = listOf() - serverStatus.value = mapOf() - serverQuickConnect.value = mapOf() - } - - val allServers = - serverDao - .getServers() - .map { it.server } - .sortedWith(compareBy { it.name }.thenBy { it.url }) - withContext(Dispatchers.Main) { - servers.value = allServers - } - allServers.forEach { server -> - try { - jellyfin - .createApi( - server.url, - httpClientOptions = - HttpClientOptions( - requestTimeout = 6.seconds, - connectTimeout = 6.seconds, - socketTimeout = 6.seconds, - ), - ).systemApi - .getPublicSystemInfo() - val quickConnect by - jellyfin - .createApi(server.url) - .quickConnectApi - .getQuickConnectEnabled() - withContext(Dispatchers.Main) { - serverStatus.value = - serverStatus.value!!.toMutableMap().apply { - put( - server.id, - ServerConnectionStatus.Success, - ) - } - serverQuickConnect.value = - serverQuickConnect.value!!.toMutableMap().apply { - put(server.id, quickConnect) - } - } - } catch (ex: Exception) { - Timber.w(ex, "Error checking quick connect for server ${server.url}") - withContext(Dispatchers.Main) { - serverStatus.value = - serverStatus.value!!.toMutableMap().apply { - put( - server.id, - ServerConnectionStatus.Error(ex.localizedMessage), - ) - } - } - } + users.setValueOnMain(serverUsers) } } + viewModelScope.launchIO { - serverRepository.currentServer?.let { - val quickConnect = + try { + jellyfin + .createApi( + server.url, + httpClientOptions = + HttpClientOptions( + requestTimeout = 6.seconds, + connectTimeout = 6.seconds, + socketTimeout = 6.seconds, + ), + ).systemApi + .getPublicSystemInfo() + val quickConnect by jellyfin - .createApi(it.url) + .createApi(server.url) .quickConnectApi .getQuickConnectEnabled() - .content - val serverUsers = serverDao.getServer(it.id)?.users?.sortedBy { it.name } ?: listOf() withContext(Dispatchers.Main) { - serverQuickConnect.value = - serverQuickConnect.value!!.toMutableMap().apply { - put(it.id, quickConnect) - } - users.value = serverUsers + serverQuickConnect.value = quickConnect + } + } catch (ex: Exception) { + Timber.w(ex, "Error checking quick connect for server ${server.url}") + withContext(Dispatchers.Main) { + serverQuickConnect.value = false } } } } - fun switchUser( - server: JellyfinServer, - user: JellyfinUser, - ) { + fun switchUser(user: JellyfinUser) { viewModelScope.launchIO { try { serverRepository.changeUser(server, user) @@ -242,10 +202,7 @@ class SwitchUserViewModel if (ex is InvalidStatusException && ex.status == 401) { withContext(Dispatchers.Main) { quickConnectState.value = null - serverQuickConnect.value = - serverQuickConnect.value!!.toMutableMap().apply { - put(server.id, false) - } + serverQuickConnect.value = false } } setError("Error with Quick Connect", ex) @@ -258,55 +215,6 @@ class SwitchUserViewModel quickConnectState.value = null } - fun addServer(serverUrl: String) { - addServerState.value = LoadingState.Loading - viewModelScope.launchIO { - try { - val serverInfo by jellyfin - .createApi(serverUrl) - .systemApi - .getPublicSystemInfo() - val id = serverInfo.id?.toUUIDOrNull() - if (id != null && serverInfo.startupWizardCompleted == true) { - serverRepository.addAndChangeServer( - JellyfinServer( - id = id, - name = serverInfo.serverName, - url = serverUrl, - ), - ) - val quickConnect = - jellyfin - .createApi(serverUrl) - .quickConnectApi - .getQuickConnectEnabled() - .content - withContext(Dispatchers.Main) { - serverQuickConnect.value = - serverQuickConnect.value!!.toMutableMap().apply { - put(id, quickConnect) - } - } - withContext(Dispatchers.Main) { - addServerState.value = LoadingState.Success - navigationManager.navigateTo(Destination.UserList) - } - } else { - withContext(Dispatchers.Main) { - addServerState.value = - LoadingState.Error("Server returned invalid response") - } - } - } catch (ex: Exception) { - Timber.w(ex, "Error creating API for $serverUrl") - withContext(Dispatchers.Main) { - addServerState.value = - LoadingState.Error(exception = ex) - } - } - } - } - fun removeUser(user: JellyfinUser) { viewModelScope.launchIO { serverRepository.removeUser(user) @@ -318,35 +226,6 @@ class SwitchUserViewModel } } - fun removeServer(server: JellyfinServer) { - viewModelScope.launchIO { - serverRepository.removeServer(server) - init() - } - } - - fun discoverServers() { - viewModelScope.launchIO { - jellyfin.discovery.discoverLocalServers().collect { server -> - val newServerList = - discoveredServers.value!! - .toMutableList() - .apply { - add( - JellyfinServer( - server.id.toUUID(), - server.name, - server.address, - ), - ) - } - withContext(Dispatchers.Main) { - discoveredServers.value = newServerList - } - } - } - } - private suspend fun setError( msg: String? = null, ex: Exception? = null, diff --git a/app/src/main/java/com/github/damontecres/wholphin/util/ServerEventListener.kt b/app/src/main/java/com/github/damontecres/wholphin/util/ServerEventListener.kt index ae36a9b3..f792f2f9 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/util/ServerEventListener.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/util/ServerEventListener.kt @@ -2,13 +2,17 @@ package com.github.damontecres.wholphin.util import android.content.Context import android.widget.Toast +import androidx.appcompat.app.AppCompatActivity +import androidx.lifecycle.DefaultLifecycleObserver +import androidx.lifecycle.LifecycleOwner +import androidx.lifecycle.lifecycleScope +import com.github.damontecres.wholphin.data.ServerRepository import com.github.damontecres.wholphin.data.model.JellyfinServer import com.github.damontecres.wholphin.data.model.JellyfinUser -import com.github.damontecres.wholphin.hilt.IoCoroutineScope import com.github.damontecres.wholphin.ui.launchIO import com.github.damontecres.wholphin.ui.showToast -import dagger.hilt.android.qualifiers.ApplicationContext -import kotlinx.coroutines.CoroutineScope +import dagger.hilt.android.qualifiers.ActivityContext +import dagger.hilt.android.scopes.ActivityScoped import kotlinx.coroutines.Job import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach @@ -20,24 +24,36 @@ import org.jellyfin.sdk.model.api.GeneralCommandType import org.jellyfin.sdk.model.api.MediaType import timber.log.Timber import javax.inject.Inject -import javax.inject.Singleton -@Singleton +@ActivityScoped class ServerEventListener @Inject constructor( - @param:ApplicationContext private val context: Context, + @param:ActivityContext private val context: Context, private val api: ApiClient, - @param:IoCoroutineScope private val scope: CoroutineScope, - ) { + private val serverRepository: ServerRepository, + ) : DefaultLifecycleObserver { + private val activity = (context as AppCompatActivity) + private var listenJob: Job? = null + init { + activity.lifecycle.addObserver(this) + serverRepository.current.observe(activity) { + Timber.d("New user/server: %s", it) + listenJob?.cancel() + if (it != null) { + init(it.server, it.user) + } + } + } + fun init( server: JellyfinServer?, user: JellyfinUser?, ) { if (server != null && user != null && api.baseUrl != null && api.accessToken != null) { - scope.launchIO { + (context as AppCompatActivity).lifecycleScope.launchIO { api.sessionApi.postCapabilities( playableMediaTypes = listOf(MediaType.VIDEO), supportedCommands = @@ -53,6 +69,7 @@ class ServerEventListener } fun setupListeners() { + serverRepository.currentUser Timber.v("Subscribing to WebSocket") listenJob?.cancel() listenJob = @@ -73,6 +90,20 @@ class ServerEventListener .joinToString("\n") showToast(context, toast, Toast.LENGTH_LONG) } - }.launchIn(scope) + }.launchIn(activity.lifecycleScope) + } + + override fun onResume(owner: LifecycleOwner) { + serverRepository.current.value?.let { init(it.server, it.user) } + } + + override fun onPause(owner: LifecycleOwner) { + Timber.v("Cancelling WebSocket") + listenJob?.cancel() + } + + override fun onStop(owner: LifecycleOwner) { + Timber.v("Cancelling WebSocket") + listenJob?.cancel() } } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 7cefe044..ac425f5e 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -147,6 +147,7 @@ Italicize font Font Background + Success %s downloads