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>
|
</a>
|
||||||
</p>
|
</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
|
## Features
|
||||||
|
|
||||||
|
### User interface
|
||||||
|
|
||||||
- A navigation drawer for quick access to libraries, search, and settings from almost anywhere in the app
|
- 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
|
- Show Movie/TV Show titles when browsing libraries
|
||||||
- Play TV Show theme music, if available
|
- Play theme music, if available
|
||||||
- Plex inspired playback controls, such as:
|
- Customize layout grids for libraries
|
||||||
- Using D-Pad left/right for seeking during playback
|
- Access all your favorites quickly from the nav drawer
|
||||||
- Quickly access video chapters during playback
|
- Multiple app color themes
|
||||||
- Optionally skip back a few seconds when resuming playback
|
|
||||||
- Other (subjective) enhancements:
|
### Playback
|
||||||
- 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
|
|
||||||
- Different media playback engines, including:
|
- Different media playback engines, including:
|
||||||
- Default ExoPlayer/Media3
|
- Default ExoPlayer/Media3
|
||||||
- Experimental MPV
|
- 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
|
### Roadmap
|
||||||
|
|
||||||
|
|
@ -88,8 +99,11 @@ You can [help translate Wholphin](https://translate.codeberg.org/engage/wholphin
|
||||||
### Movie library browsing
|
### 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" />
|
<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
|
### 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
|
### Playlist
|
||||||
<img width="1280" height="771" alt="0 3 0_playlist" src="https://github.com/user-attachments/assets/7ca589ab-9c88-483a-b769-35ffb5663d9e" />
|
<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
|
package com.github.damontecres.wholphin
|
||||||
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.WindowManager
|
|
||||||
import androidx.activity.compose.setContent
|
import androidx.activity.compose.setContent
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
|
|
@ -10,7 +9,6 @@ import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.size
|
import androidx.compose.foundation.layout.size
|
||||||
import androidx.compose.material3.CircularProgressIndicator
|
import androidx.compose.material3.CircularProgressIndicator
|
||||||
import androidx.compose.runtime.CompositionLocalProvider
|
import androidx.compose.runtime.CompositionLocalProvider
|
||||||
import androidx.compose.runtime.DisposableEffect
|
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.collectAsState
|
||||||
import androidx.compose.runtime.getValue
|
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.services.hilt.AuthOkHttpClient
|
||||||
import com.github.damontecres.wholphin.ui.CoilConfig
|
import com.github.damontecres.wholphin.ui.CoilConfig
|
||||||
import com.github.damontecres.wholphin.ui.LocalImageUrlService
|
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.launchIO
|
||||||
import com.github.damontecres.wholphin.ui.nav.ApplicationContent
|
import com.github.damontecres.wholphin.ui.nav.ApplicationContent
|
||||||
import com.github.damontecres.wholphin.ui.nav.Destination
|
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 com.github.damontecres.wholphin.util.DebugLogTree
|
||||||
import dagger.hilt.android.AndroidEntryPoint
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.flow.firstOrNull
|
|
||||||
import kotlinx.coroutines.runBlocking
|
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import okhttp3.OkHttpClient
|
import okhttp3.OkHttpClient
|
||||||
import org.jellyfin.sdk.model.serializer.toUUIDOrNull
|
import org.jellyfin.sdk.model.serializer.toUUIDOrNull
|
||||||
|
|
@ -146,7 +141,8 @@ class MainActivity : AppCompatActivity() {
|
||||||
) {
|
) {
|
||||||
var isRestoringSession by remember { mutableStateOf(true) }
|
var isRestoringSession by remember { mutableStateOf(true) }
|
||||||
LaunchedEffect(Unit) {
|
LaunchedEffect(Unit) {
|
||||||
if (appPreferences.signInAutomatically) {
|
// TODO PIN-related
|
||||||
|
// if (appPreferences.signInAutomatically) {
|
||||||
try {
|
try {
|
||||||
serverRepository.restoreSession(
|
serverRepository.restoreSession(
|
||||||
appPreferences.currentServerId?.toUUIDOrNull(),
|
appPreferences.currentServerId?.toUUIDOrNull(),
|
||||||
|
|
@ -155,7 +151,7 @@ class MainActivity : AppCompatActivity() {
|
||||||
} catch (ex: Exception) {
|
} catch (ex: Exception) {
|
||||||
Timber.e(ex, "Exception restoring session")
|
Timber.e(ex, "Exception restoring session")
|
||||||
}
|
}
|
||||||
}
|
// }
|
||||||
isRestoringSession = false
|
isRestoringSession = false
|
||||||
}
|
}
|
||||||
val current by serverRepository.current.observeAsState()
|
val current by serverRepository.current.observeAsState()
|
||||||
|
|
@ -177,32 +173,34 @@ class MainActivity : AppCompatActivity() {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
DisposableEffect(Unit) {
|
// TODO PIN-related
|
||||||
onDispose {
|
// DisposableEffect(Unit) {
|
||||||
if (!appPreferences.signInAutomatically) {
|
// onDispose {
|
||||||
serverRepository.closeSession()
|
// if (!appPreferences.signInAutomatically || current?.user?.hasPin == true) {
|
||||||
}
|
// serverRepository.closeSession()
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
// }
|
||||||
key(current?.server?.id, current?.user?.id) {
|
key(current?.server?.id, current?.user?.id) {
|
||||||
LaunchedEffect(current?.user?.pin) {
|
// TODO PIN-related
|
||||||
if (current?.user?.pin?.isNotNullOrBlank() == true) {
|
// LaunchedEffect(current?.user?.pin) {
|
||||||
// If user has a pin, then obscure the window in previews
|
// if (current?.user?.pin?.isNotNullOrBlank() == true) {
|
||||||
window?.setFlags(
|
// // If user has a pin, then obscure the window in previews
|
||||||
WindowManager.LayoutParams.FLAG_SECURE,
|
// window?.setFlags(
|
||||||
WindowManager.LayoutParams.FLAG_SECURE,
|
// WindowManager.LayoutParams.FLAG_SECURE,
|
||||||
)
|
// WindowManager.LayoutParams.FLAG_SECURE,
|
||||||
} else {
|
// )
|
||||||
window?.clearFlags(WindowManager.LayoutParams.FLAG_SECURE)
|
// } else {
|
||||||
}
|
// window?.clearFlags(WindowManager.LayoutParams.FLAG_SECURE)
|
||||||
}
|
// }
|
||||||
|
// }
|
||||||
val initialDestination =
|
val initialDestination =
|
||||||
when {
|
when {
|
||||||
current != null -> Destination.Home()
|
current != null -> Destination.Home()
|
||||||
|
|
||||||
!appPreferences.signInAutomatically -> Destination.ServerList
|
// TODO PIN-related
|
||||||
|
// !appPreferences.signInAutomatically -> Destination.ServerList
|
||||||
|
|
||||||
// TODO user list?
|
|
||||||
else -> Destination.ServerList
|
else -> Destination.ServerList
|
||||||
}
|
}
|
||||||
val backStack = rememberNavBackStack(initialDestination)
|
val backStack = rememberNavBackStack(initialDestination)
|
||||||
|
|
@ -249,11 +247,12 @@ class MainActivity : AppCompatActivity() {
|
||||||
|
|
||||||
override fun onRestart() {
|
override fun onRestart() {
|
||||||
super.onRestart()
|
super.onRestart()
|
||||||
val signInAutomatically =
|
// TODO PIN-related
|
||||||
runBlocking { userPreferencesDataStore.data.firstOrNull()?.signInAutomatically } ?: true
|
// val signInAutomatically =
|
||||||
Timber.i("onRestart: signInAutomatically=$signInAutomatically")
|
// runBlocking { userPreferencesDataStore.data.firstOrNull()?.signInAutomatically } ?: true
|
||||||
if (!signInAutomatically) {
|
// Timber.i("onRestart: signInAutomatically=$signInAutomatically")
|
||||||
serverRepository.closeSession()
|
// 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
|
* 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(
|
suspend fun restoreSession(
|
||||||
serverId: UUID?,
|
serverId: UUID?,
|
||||||
|
|
@ -124,7 +126,7 @@ class ServerRepository
|
||||||
}
|
}
|
||||||
if (serverAndUsers != null) {
|
if (serverAndUsers != null) {
|
||||||
val user = serverAndUsers.users.firstOrNull { it.id == userId }
|
val user = serverAndUsers.users.firstOrNull { it.id == userId }
|
||||||
if (user != null) {
|
if (user != null && !user.hasPin) {
|
||||||
changeUser(serverAndUsers.server, user)
|
changeUser(serverAndUsers.server, user)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -52,6 +52,8 @@ data class JellyfinUser(
|
||||||
val accessToken: String?,
|
val accessToken: String?,
|
||||||
val pin: String? = null,
|
val pin: String? = null,
|
||||||
) {
|
) {
|
||||||
|
val hasPin: Boolean get() = pin.isNotNullOrBlank()
|
||||||
|
|
||||||
override fun toString(): String =
|
override fun toString(): String =
|
||||||
"JellyfinUser(rowId=$rowId, id=$id, name=$name, serverId=$serverId, accessToken?=${accessToken.isNotNullOrBlank()}, pin?=${pin.isNotNullOrBlank()})"
|
"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,
|
title = R.string.ui_interface,
|
||||||
preferences =
|
preferences =
|
||||||
listOf(
|
listOf(
|
||||||
AppPreference.SignInAuto,
|
// TODO PIN-related
|
||||||
|
// AppPreference.SignInAuto,
|
||||||
AppPreference.HomePageItems,
|
AppPreference.HomePageItems,
|
||||||
AppPreference.CombineContinueNext,
|
AppPreference.CombineContinueNext,
|
||||||
AppPreference.RewatchNextUp,
|
AppPreference.RewatchNextUp,
|
||||||
|
|
@ -826,7 +827,8 @@ val basicPreferences =
|
||||||
title = R.string.profile_specific_settings,
|
title = R.string.profile_specific_settings,
|
||||||
preferences =
|
preferences =
|
||||||
listOf(
|
listOf(
|
||||||
AppPreference.RequireProfilePin,
|
// TODO PIN-related
|
||||||
|
// AppPreference.RequireProfilePin,
|
||||||
AppPreference.UserPinnedNavDrawerItems,
|
AppPreference.UserPinnedNavDrawerItems,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -119,11 +119,13 @@ fun SwitchUserContent(
|
||||||
users = users,
|
users = users,
|
||||||
currentUser = currentUser,
|
currentUser = currentUser,
|
||||||
onSwitchUser = { user ->
|
onSwitchUser = { user ->
|
||||||
if (user.pin.isNotNullOrBlank()) {
|
// TODO PIN-related
|
||||||
switchUserWithPin = user
|
// if (user.pin.isNotNullOrBlank()) {
|
||||||
} else {
|
// switchUserWithPin = user
|
||||||
|
// } else {
|
||||||
|
// viewModel.switchUser(user)
|
||||||
|
// }
|
||||||
viewModel.switchUser(user)
|
viewModel.switchUser(user)
|
||||||
}
|
|
||||||
},
|
},
|
||||||
onAddUser = { showAddUser = true },
|
onAddUser = { showAddUser = true },
|
||||||
onRemoveUser = { user ->
|
onRemoveUser = { user ->
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue