mirror of
https://github.com/JustinZeus/Wholphin.git
synced 2026-07-09 08:01:20 +02:00
Merge branch 'main' into fea/jellyseerr
This commit is contained in:
commit
2c6ecc24ea
6 changed files with 79 additions and 58 deletions
34
README.md
34
README.md
|
|
@ -18,23 +18,34 @@ This is not a fork of the [official client](https://github.com/jellyfin/jellyfin
|
|||
</a>
|
||||
</p>
|
||||
|
||||
<img width="1280" height="771" alt="0 3 0_home" src="https://github.com/user-attachments/assets/23be4786-a4c3-45e5-a710-8d1ade2be2a3" />
|
||||
<img width="1280" height="720" alt="0_3_5_home" src="https://github.com/user-attachments/assets/a485c015-ec21-442d-a757-1f18381bf799" />
|
||||
|
||||
## 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
|
||||
<img width="1280" height="771" alt="0 3 0_movies" src="https://github.com/user-attachments/assets/a49829b5-bc2c-4af9-8d5d-2f7d0973ce01" />
|
||||
|
||||
### Movie page
|
||||
<img width="1280" height="720" alt="0_3_5_movie" src="https://github.com/user-attachments/assets/86af5889-6761-426a-8649-422f9d0a1dc0" />
|
||||
|
||||
### Series page
|
||||
<img width="1280" height="771" alt="0 3 0_series_overview" src="https://github.com/user-attachments/assets/2d26f34e-3ee8-4599-ba59-32524637dab7" />
|
||||
<img width="1280" height="720" alt="0_3_5_series" src="https://github.com/user-attachments/assets/2dcb2260-53ce-49d6-9088-72cbd4563c48" />
|
||||
|
||||
### Playlist
|
||||
<img width="1280" height="771" alt="0 3 0_playlist" src="https://github.com/user-attachments/assets/7ca589ab-9c88-483a-b769-35ffb5663d9e" />
|
||||
|
|
|
|||
|
|
@ -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,7 +141,8 @@ class MainActivity : AppCompatActivity() {
|
|||
) {
|
||||
var isRestoringSession by remember { mutableStateOf(true) }
|
||||
LaunchedEffect(Unit) {
|
||||
if (appPreferences.signInAutomatically) {
|
||||
// TODO PIN-related
|
||||
// if (appPreferences.signInAutomatically) {
|
||||
try {
|
||||
serverRepository.restoreSession(
|
||||
appPreferences.currentServerId?.toUUIDOrNull(),
|
||||
|
|
@ -155,7 +151,7 @@ class MainActivity : AppCompatActivity() {
|
|||
} 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()
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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()})"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -119,11 +119,13 @@ fun SwitchUserContent(
|
|||
users = users,
|
||||
currentUser = currentUser,
|
||||
onSwitchUser = { user ->
|
||||
if (user.pin.isNotNullOrBlank()) {
|
||||
switchUserWithPin = user
|
||||
} else {
|
||||
// TODO PIN-related
|
||||
// if (user.pin.isNotNullOrBlank()) {
|
||||
// switchUserWithPin = user
|
||||
// } else {
|
||||
// viewModel.switchUser(user)
|
||||
// }
|
||||
viewModel.switchUser(user)
|
||||
}
|
||||
},
|
||||
onAddUser = { showAddUser = true },
|
||||
onRemoveUser = { user ->
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue