diff --git a/README.md b/README.md index 8fd53cfa..fd07c32d 100644 --- a/README.md +++ b/README.md @@ -18,23 +18,34 @@ This is not a fork of the [official client](https://github.com/jellyfin/jellyfin

-0 3 0_home +0_3_5_home ## Features +### User interface + - A navigation drawer for quick access to libraries, search, and settings from almost anywhere in the app +- Option to combine Continue Watching & Next Up rows - Show Movie/TV Show titles when browsing libraries -- Play TV Show theme music, if available -- Plex inspired playback controls, such as: - - Using D-Pad left/right for seeking during playback - - Quickly access video chapters during playback - - Optionally skip back a few seconds when resuming playback -- Other (subjective) enhancements: - - Subtly show playback position along the bottom of the screen while seeking w/ D-Pad - - Force Continue Watching & Next Up TV episodes to use their Series posters +- Play theme music, if available +- Customize layout grids for libraries +- Access all your favorites quickly from the nav drawer +- Multiple app color themes + +### Playback + - Different media playback engines, including: - Default ExoPlayer/Media3 - Experimental MPV +- Plex inspired playback controls, such as: + - Using D-Pad left/right for seeking during playback + - Quickly access video chapters & queue during playback + - Optionally skip back a few seconds when resuming playback +- Live TV & DVR support +- Auto play next episodes with pass out protection +- Other (subjective) enhancements: + - Subtly show playback position along the bottom of the screen while seeking w/ D-Pad + - Force Continue Watching & Next Up TV episodes to use their Series posters ### Roadmap @@ -88,8 +99,11 @@ You can [help translate Wholphin](https://translate.codeberg.org/engage/wholphin ### Movie library browsing 0 3 0_movies +### Movie page +0_3_5_movie + ### Series page -0 3 0_series_overview +0_3_5_series ### Playlist 0 3 0_playlist 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 dd3312c8..ef90f38a 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/MainActivity.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/MainActivity.kt @@ -1,7 +1,6 @@ package com.github.damontecres.wholphin import android.os.Bundle -import android.view.WindowManager import androidx.activity.compose.setContent import androidx.appcompat.app.AppCompatActivity import androidx.compose.foundation.background @@ -10,7 +9,6 @@ import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.size import androidx.compose.material3.CircularProgressIndicator import androidx.compose.runtime.CompositionLocalProvider -import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue @@ -45,7 +43,6 @@ import com.github.damontecres.wholphin.services.UpdateChecker import com.github.damontecres.wholphin.services.hilt.AuthOkHttpClient import com.github.damontecres.wholphin.ui.CoilConfig import com.github.damontecres.wholphin.ui.LocalImageUrlService -import com.github.damontecres.wholphin.ui.isNotNullOrBlank import com.github.damontecres.wholphin.ui.launchIO import com.github.damontecres.wholphin.ui.nav.ApplicationContent import com.github.damontecres.wholphin.ui.nav.Destination @@ -53,8 +50,6 @@ import com.github.damontecres.wholphin.ui.theme.WholphinTheme import com.github.damontecres.wholphin.util.DebugLogTree import dagger.hilt.android.AndroidEntryPoint import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.flow.firstOrNull -import kotlinx.coroutines.runBlocking import kotlinx.coroutines.withContext import okhttp3.OkHttpClient import org.jellyfin.sdk.model.serializer.toUUIDOrNull @@ -146,16 +141,17 @@ class MainActivity : AppCompatActivity() { ) { var isRestoringSession by remember { mutableStateOf(true) } LaunchedEffect(Unit) { - if (appPreferences.signInAutomatically) { - try { - serverRepository.restoreSession( - appPreferences.currentServerId?.toUUIDOrNull(), - appPreferences.currentUserId?.toUUIDOrNull(), - ) - } catch (ex: Exception) { - Timber.e(ex, "Exception restoring session") - } + // TODO PIN-related +// if (appPreferences.signInAutomatically) { + try { + serverRepository.restoreSession( + appPreferences.currentServerId?.toUUIDOrNull(), + appPreferences.currentUserId?.toUUIDOrNull(), + ) + } catch (ex: Exception) { + Timber.e(ex, "Exception restoring session") } +// } isRestoringSession = false } val current by serverRepository.current.observeAsState() @@ -177,32 +173,34 @@ class MainActivity : AppCompatActivity() { ) } } else { - DisposableEffect(Unit) { - onDispose { - if (!appPreferences.signInAutomatically) { - serverRepository.closeSession() - } - } - } + // TODO PIN-related +// DisposableEffect(Unit) { +// onDispose { +// if (!appPreferences.signInAutomatically || current?.user?.hasPin == true) { +// serverRepository.closeSession() +// } +// } +// } key(current?.server?.id, current?.user?.id) { - LaunchedEffect(current?.user?.pin) { - if (current?.user?.pin?.isNotNullOrBlank() == true) { - // If user has a pin, then obscure the window in previews - window?.setFlags( - WindowManager.LayoutParams.FLAG_SECURE, - WindowManager.LayoutParams.FLAG_SECURE, - ) - } else { - window?.clearFlags(WindowManager.LayoutParams.FLAG_SECURE) - } - } + // TODO PIN-related +// LaunchedEffect(current?.user?.pin) { +// if (current?.user?.pin?.isNotNullOrBlank() == true) { +// // If user has a pin, then obscure the window in previews +// window?.setFlags( +// WindowManager.LayoutParams.FLAG_SECURE, +// WindowManager.LayoutParams.FLAG_SECURE, +// ) +// } else { +// window?.clearFlags(WindowManager.LayoutParams.FLAG_SECURE) +// } +// } val initialDestination = when { current != null -> Destination.Home() - !appPreferences.signInAutomatically -> Destination.ServerList + // TODO PIN-related + // !appPreferences.signInAutomatically -> Destination.ServerList - // TODO user list? else -> Destination.ServerList } val backStack = rememberNavBackStack(initialDestination) @@ -249,11 +247,12 @@ class MainActivity : AppCompatActivity() { override fun onRestart() { super.onRestart() - val signInAutomatically = - runBlocking { userPreferencesDataStore.data.firstOrNull()?.signInAutomatically } ?: true - Timber.i("onRestart: signInAutomatically=$signInAutomatically") - if (!signInAutomatically) { - serverRepository.closeSession() - } + // TODO PIN-related +// val signInAutomatically = +// runBlocking { userPreferencesDataStore.data.firstOrNull()?.signInAutomatically } ?: true +// Timber.i("onRestart: signInAutomatically=$signInAutomatically") +// if (!signInAutomatically || serverRepository.currentUser.value?.hasPin == true) { +// serverRepository.closeSession() +// } } } 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 eed4422f..5fc98724 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 @@ -109,6 +109,8 @@ class ServerRepository /** * Restores a session for the given server & user such as when the app reopens + * + * If user has a PIN, this returns false */ suspend fun restoreSession( serverId: UUID?, @@ -124,7 +126,7 @@ class ServerRepository } if (serverAndUsers != null) { val user = serverAndUsers.users.firstOrNull { it.id == userId } - if (user != null) { + if (user != null && !user.hasPin) { changeUser(serverAndUsers.server, user) return true } 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 0b8a3302..d8e40779 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 @@ -52,6 +52,8 @@ data class JellyfinUser( val accessToken: String?, val pin: String? = null, ) { + val hasPin: Boolean get() = pin.isNotNullOrBlank() + override fun toString(): String = "JellyfinUser(rowId=$rowId, id=$id, name=$name, serverId=$serverId, accessToken?=${accessToken.isNotNullOrBlank()}, pin?=${pin.isNotNullOrBlank()})" } diff --git a/app/src/main/java/com/github/damontecres/wholphin/preferences/AppPreference.kt b/app/src/main/java/com/github/damontecres/wholphin/preferences/AppPreference.kt index 4e182e0c..9a196d6b 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/preferences/AppPreference.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/preferences/AppPreference.kt @@ -793,7 +793,8 @@ val basicPreferences = title = R.string.ui_interface, preferences = listOf( - AppPreference.SignInAuto, + // TODO PIN-related + // AppPreference.SignInAuto, AppPreference.HomePageItems, AppPreference.CombineContinueNext, AppPreference.RewatchNextUp, @@ -826,7 +827,8 @@ val basicPreferences = title = R.string.profile_specific_settings, preferences = listOf( - AppPreference.RequireProfilePin, + // TODO PIN-related + // AppPreference.RequireProfilePin, AppPreference.UserPinnedNavDrawerItems, ), ), 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 e0aeb5a1..df0b79ee 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 @@ -119,11 +119,13 @@ fun SwitchUserContent( users = users, currentUser = currentUser, onSwitchUser = { user -> - if (user.pin.isNotNullOrBlank()) { - switchUserWithPin = user - } else { - viewModel.switchUser(user) - } + // TODO PIN-related +// if (user.pin.isNotNullOrBlank()) { +// switchUserWithPin = user +// } else { +// viewModel.switchUser(user) +// } + viewModel.switchUser(user) }, onAddUser = { showAddUser = true }, onRemoveUser = { user ->