mirror of
https://github.com/JustinZeus/Wholphin.git
synced 2026-07-08 15:50:16 +02:00
Merge branch 'main' into fea/music
This commit is contained in:
commit
2b6c4eafb3
11 changed files with 88 additions and 40 deletions
|
|
@ -9,7 +9,9 @@ import androidx.compose.ui.test.onNodeWithTag
|
|||
import androidx.compose.ui.test.onNodeWithText
|
||||
import androidx.compose.ui.test.performKeyInput
|
||||
import androidx.compose.ui.test.pressKey
|
||||
import androidx.navigation3.runtime.NavBackStack
|
||||
import com.github.damontecres.wholphin.MainContent
|
||||
import com.github.damontecres.wholphin.services.NavigationManager
|
||||
import com.github.damontecres.wholphin.services.ScreensaverService
|
||||
import com.github.damontecres.wholphin.services.ScreensaverState
|
||||
import com.github.damontecres.wholphin.services.SetupDestination
|
||||
|
|
@ -43,16 +45,17 @@ class InstrumentedBasicUiTests {
|
|||
@OptIn(ExperimentalTestApi::class)
|
||||
@Test
|
||||
fun myTest() {
|
||||
val navigationManager = NavigationManager()
|
||||
navigationManager.backStack = NavBackStack(Destination.Home())
|
||||
// Start the app
|
||||
composeTestRule.setContent {
|
||||
WholphinTheme {
|
||||
MainContent(
|
||||
backStack = mutableListOf(SetupDestination.ServerList),
|
||||
navigationManager = mockk(relaxed = true),
|
||||
navigationManager = navigationManager,
|
||||
appPreferences = mockk(relaxed = true),
|
||||
backdropService = mockk(relaxed = true),
|
||||
screensaverService = screensaverService,
|
||||
requestedDestination = Destination.Home(),
|
||||
modifier = Modifier,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ import androidx.datastore.core.DataStore
|
|||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import androidx.navigation3.runtime.NavBackStack
|
||||
import androidx.tv.material3.ExperimentalTvMaterial3Api
|
||||
import com.github.damontecres.wholphin.data.ServerRepository
|
||||
import com.github.damontecres.wholphin.preferences.AppPreference
|
||||
|
|
@ -67,6 +68,7 @@ import kotlinx.coroutines.flow.launchIn
|
|||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import kotlinx.serialization.json.Json
|
||||
import okhttp3.OkHttpClient
|
||||
import org.jellyfin.sdk.model.api.BaseItemKind
|
||||
import org.jellyfin.sdk.model.serializer.toUUIDOrNull
|
||||
|
|
@ -127,6 +129,11 @@ class MainActivity : AppCompatActivity() {
|
|||
|
||||
private var signInAuto = true
|
||||
|
||||
private val json =
|
||||
Json {
|
||||
classDiscriminator = "_type"
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalTvMaterial3Api::class)
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
|
@ -134,6 +141,23 @@ class MainActivity : AppCompatActivity() {
|
|||
Timber.i("MainActivity.onCreate: savedInstanceState is null=${savedInstanceState == null}")
|
||||
lifecycle.addObserver(playbackLifecycleObserver)
|
||||
|
||||
val backStackStr = savedInstanceState?.getString(KEY_BACK_STACK)
|
||||
if (backStackStr != null) {
|
||||
Timber.d("Restoring back stack")
|
||||
var backStack = json.decodeFromString<List<Destination>>(backStackStr)
|
||||
val lastDest = backStack.lastOrNull()
|
||||
if (lastDest is Destination.Playback ||
|
||||
lastDest is Destination.PlaybackList ||
|
||||
lastDest is Destination.Slideshow
|
||||
) {
|
||||
backStack = backStack.toMutableList().apply { removeAt(lastIndex) }
|
||||
}
|
||||
navigationManager.backStack = NavBackStack(*backStack.toTypedArray())
|
||||
} else {
|
||||
val startDestination = intent?.let(::extractDestination) ?: Destination.Home()
|
||||
navigationManager.backStack = NavBackStack(startDestination)
|
||||
}
|
||||
|
||||
viewModel.serverRepository.currentUser.observe(this) { user ->
|
||||
if (user?.hasPin == true) {
|
||||
window?.setFlags(
|
||||
|
|
@ -206,17 +230,12 @@ class MainActivity : AppCompatActivity() {
|
|||
appThemeColors = appPreferences.interfacePreferences.appThemeColors,
|
||||
) {
|
||||
ProvideLocalClock {
|
||||
val requestedDestination =
|
||||
remember(intent) {
|
||||
intent?.let(::extractDestination) ?: Destination.Home()
|
||||
}
|
||||
MainContent(
|
||||
backStack = setupNavigationManager.backStack,
|
||||
navigationManager = navigationManager,
|
||||
appPreferences = appPreferences,
|
||||
backdropService = backdropService,
|
||||
screensaverService = screensaverService,
|
||||
requestedDestination = requestedDestination,
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
)
|
||||
}
|
||||
|
|
@ -287,6 +306,8 @@ class MainActivity : AppCompatActivity() {
|
|||
override fun onSaveInstanceState(outState: Bundle) {
|
||||
super.onSaveInstanceState(outState)
|
||||
Timber.d("onSaveInstanceState")
|
||||
val str = json.encodeToString(navigationManager.backStack.toList())
|
||||
outState.putString(KEY_BACK_STACK, str)
|
||||
}
|
||||
|
||||
override fun onRestoreInstanceState(savedInstanceState: Bundle) {
|
||||
|
|
@ -362,6 +383,8 @@ class MainActivity : AppCompatActivity() {
|
|||
const val INTENT_SEASON_NUMBER = "seaNum"
|
||||
const val INTENT_SEASON_ID = "seaId"
|
||||
|
||||
private const val KEY_BACK_STACK = "backStack"
|
||||
|
||||
lateinit var instance: MainActivity
|
||||
private set
|
||||
}
|
||||
|
|
|
|||
|
|
@ -33,10 +33,8 @@ import com.github.damontecres.wholphin.services.ScreensaverService
|
|||
import com.github.damontecres.wholphin.services.SetupDestination
|
||||
import com.github.damontecres.wholphin.ui.components.AppScreensaver
|
||||
import com.github.damontecres.wholphin.ui.nav.ApplicationContent
|
||||
import com.github.damontecres.wholphin.ui.nav.Destination
|
||||
import com.github.damontecres.wholphin.ui.setup.SwitchServerContent
|
||||
import com.github.damontecres.wholphin.ui.setup.SwitchUserContent
|
||||
import com.github.damontecres.wholphin.ui.util.ProvideLocalClock
|
||||
|
||||
@Composable
|
||||
fun MainContent(
|
||||
|
|
@ -45,7 +43,6 @@ fun MainContent(
|
|||
appPreferences: AppPreferences,
|
||||
backdropService: BackdropService,
|
||||
screensaverService: ScreensaverService,
|
||||
requestedDestination: Destination,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
Surface(
|
||||
|
|
@ -113,7 +110,6 @@ fun MainContent(
|
|||
ApplicationContent(
|
||||
user = current.user,
|
||||
server = current.server,
|
||||
startDestination = requestedDestination,
|
||||
navigationManager = navigationManager,
|
||||
preferences = preferences,
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
|
|
|
|||
|
|
@ -22,9 +22,7 @@ import androidx.savedstate.SavedStateRegistryOwner
|
|||
import androidx.savedstate.setViewTreeSavedStateRegistryOwner
|
||||
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.services.ScreensaverService
|
||||
import com.github.damontecres.wholphin.services.UserPreferencesService
|
||||
import com.github.damontecres.wholphin.ui.components.AppScreensaverContent
|
||||
import com.github.damontecres.wholphin.ui.launchDefault
|
||||
import com.github.damontecres.wholphin.ui.theme.WholphinTheme
|
||||
|
|
@ -33,6 +31,7 @@ import dagger.hilt.android.AndroidEntryPoint
|
|||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.flow.first
|
||||
import org.jellyfin.sdk.model.serializer.toUUIDOrNull
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
import kotlin.time.Duration.Companion.milliseconds
|
||||
|
||||
|
|
@ -61,6 +60,7 @@ class WholphinDreamService :
|
|||
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
Timber.d("onCreate")
|
||||
|
||||
savedStateRegistryController.performRestore(null)
|
||||
lifecycleRegistry.currentState = Lifecycle.State.CREATED
|
||||
|
|
@ -74,6 +74,7 @@ class WholphinDreamService :
|
|||
|
||||
override fun onAttachedToWindow() {
|
||||
super.onAttachedToWindow()
|
||||
Timber.d("onAttachedToWindow")
|
||||
val itemFlow = screensaverService.createItemFlow(lifecycleScope)
|
||||
setContentView(
|
||||
ComposeView(this).apply {
|
||||
|
|
@ -109,11 +110,13 @@ class WholphinDreamService :
|
|||
|
||||
override fun onDreamingStarted() {
|
||||
super.onDreamingStarted()
|
||||
Timber.d("onDreamingStarted")
|
||||
lifecycleRegistry.currentState = Lifecycle.State.STARTED
|
||||
}
|
||||
|
||||
override fun onDreamingStopped() {
|
||||
super.onDreamingStopped()
|
||||
Timber.d("onDreamingStopped")
|
||||
lifecycleRegistry.currentState = Lifecycle.State.DESTROYED
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
package com.github.damontecres.wholphin.services
|
||||
|
||||
import androidx.navigation3.runtime.NavKey
|
||||
import com.github.damontecres.wholphin.ui.nav.Destination
|
||||
import org.acra.ACRA
|
||||
import timber.log.Timber
|
||||
|
|
@ -14,7 +13,7 @@ import javax.inject.Singleton
|
|||
class NavigationManager
|
||||
@Inject
|
||||
constructor() {
|
||||
var backStack: MutableList<NavKey> = mutableListOf()
|
||||
var backStack: MutableList<Destination> = mutableListOf()
|
||||
|
||||
/**
|
||||
* Go to the specified [com.github.damontecres.wholphin.ui.nav.Destination]
|
||||
|
|
|
|||
|
|
@ -132,6 +132,14 @@ fun SeriesDetails(
|
|||
var showPlaylistDialog by remember { mutableStateOf<Optional<UUID>>(Optional.absent()) }
|
||||
var showDeleteDialog by remember { mutableStateOf<BaseItem?>(null) }
|
||||
|
||||
LifecycleResumeEffect(destination.itemId) {
|
||||
viewModel.refresh()
|
||||
|
||||
onPauseOrDispose {
|
||||
viewModel.release()
|
||||
}
|
||||
}
|
||||
|
||||
when (val state = loading) {
|
||||
is LoadingState.Error -> {
|
||||
ErrorMessage(state, modifier)
|
||||
|
|
@ -148,9 +156,7 @@ fun SeriesDetails(
|
|||
LifecycleResumeEffect(destination.itemId) {
|
||||
viewModel.onResumePage()
|
||||
|
||||
onPauseOrDispose {
|
||||
viewModel.release()
|
||||
}
|
||||
onPauseOrDispose {}
|
||||
}
|
||||
|
||||
val played = item.data.userData?.played ?: false
|
||||
|
|
|
|||
|
|
@ -271,9 +271,9 @@ class SeriesViewModel
|
|||
}
|
||||
|
||||
fun onResumePage() {
|
||||
viewModelScope.launchIO {
|
||||
item.value?.let {
|
||||
backdropService.submit(it)
|
||||
item.value?.let { item ->
|
||||
viewModelScope.launchDefault { backdropService.submit(item) }
|
||||
viewModelScope.launchIO {
|
||||
val playThemeSongs =
|
||||
userPreferencesService
|
||||
.getCurrent()
|
||||
|
|
@ -283,6 +283,17 @@ class SeriesViewModel
|
|||
}
|
||||
}
|
||||
|
||||
fun refresh() {
|
||||
item.value?.let { item ->
|
||||
if (loading.value == LoadingState.Success) {
|
||||
viewModelScope.launchIO {
|
||||
val seasons = getSeasons(item, null).await()
|
||||
this@SeriesViewModel.seasons.setValueOnMain(seasons)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun release() {
|
||||
themeSongPlayer.stop()
|
||||
}
|
||||
|
|
@ -292,6 +303,7 @@ class SeriesViewModel
|
|||
seasonNum: Int?,
|
||||
): Deferred<List<BaseItem?>> =
|
||||
viewModelScope.async(Dispatchers.IO) {
|
||||
Timber.v("getSeasons for %s", series.id)
|
||||
val request =
|
||||
GetItemsRequest(
|
||||
parentId = series.id,
|
||||
|
|
|
|||
|
|
@ -4,18 +4,13 @@ import androidx.compose.foundation.layout.Box
|
|||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.saveable.rememberSerializable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import androidx.lifecycle.viewmodel.navigation3.rememberViewModelStoreNavEntryDecorator
|
||||
import androidx.navigation3.runtime.NavBackStack
|
||||
import androidx.navigation3.runtime.NavEntry
|
||||
import androidx.navigation3.runtime.NavKey
|
||||
import androidx.navigation3.runtime.rememberSaveableStateHolderNavEntryDecorator
|
||||
import androidx.navigation3.runtime.serialization.NavBackStackSerializer
|
||||
import androidx.navigation3.runtime.serialization.NavKeySerializer
|
||||
import androidx.navigation3.ui.NavDisplay
|
||||
import androidx.tv.material3.DrawerValue
|
||||
import androidx.tv.material3.rememberDrawerState
|
||||
|
|
@ -53,22 +48,12 @@ class ApplicationContentViewModel
|
|||
fun ApplicationContent(
|
||||
server: JellyfinServer,
|
||||
user: JellyfinUser,
|
||||
startDestination: Destination,
|
||||
navigationManager: NavigationManager,
|
||||
preferences: UserPreferences,
|
||||
modifier: Modifier = Modifier,
|
||||
enableTopScrim: Boolean = true,
|
||||
viewModel: ApplicationContentViewModel = hiltViewModel(),
|
||||
) {
|
||||
val backStack: MutableList<NavKey> =
|
||||
rememberSerializable(
|
||||
server,
|
||||
user,
|
||||
serializer = NavBackStackSerializer(elementSerializer = NavKeySerializer()),
|
||||
) {
|
||||
NavBackStack(startDestination)
|
||||
}
|
||||
navigationManager.backStack = backStack
|
||||
val drawerState = rememberDrawerState(DrawerValue.Closed)
|
||||
Box(
|
||||
modifier = modifier,
|
||||
|
|
|
|||
|
|
@ -1,3 +1,14 @@
|
|||
# FFmpeg ExoPlayer decoder extension module
|
||||
|
||||
Builds the ffmpeg decoder extension module for `ExoPlayer` which supports extra codecs during playback.
|
||||
|
||||
## Usage
|
||||
|
||||
```sh
|
||||
./build_ffmpeg_decoder.sh /path/to/android/ndk
|
||||
```
|
||||
|
||||
Or clean build:
|
||||
```sh
|
||||
./build_ffmpeg_decoder.sh /path/to/android/ndk --clean
|
||||
```
|
||||
|
|
|
|||
|
|
@ -27,6 +27,12 @@ AV1_MODULE_PATH="$MEDIA_PATH/libraries/decoder_av1/src/main"
|
|||
HOST="$(uname -s | tr '[:upper:]' '[:lower:]')"
|
||||
HOST_PLATFORM="$HOST-x86_64"
|
||||
|
||||
if [[ "$2" == "--clean" ]]; then
|
||||
rm -rf ffmpeg_decoder
|
||||
rm -f "$TARGET_PATH/lib-decoder-ffmpeg-release.aar"
|
||||
rm -f "$TARGET_PATH/lib-decoder-av1-release.aar"
|
||||
fi
|
||||
|
||||
mkdir -p "$TARGET_PATH"
|
||||
mkdir -p ffmpeg_decoder
|
||||
|
||||
|
|
@ -38,14 +44,16 @@ pushd ffmpeg_decoder || exit
|
|||
|
||||
if [[ -d media ]]; then
|
||||
pushd media || exit
|
||||
git checkout --force "$media_version"
|
||||
git fetch origin "$media_version" --depth 1
|
||||
git checkout --force FETCH_HEAD
|
||||
else
|
||||
git clone https://github.com/androidx/media.git --depth 1 --single-branch -b "$media_version" media
|
||||
fi
|
||||
|
||||
if [[ -d ffmpeg ]]; then
|
||||
pushd ffmpeg || exit
|
||||
git checkout --force "$FFMPEG_BRANCH"
|
||||
git fetch origin "$FFMPEG_BRANCH" --depth 1
|
||||
git checkout --force FETCH_HEAD
|
||||
else
|
||||
git clone https://github.com/FFmpeg/FFmpeg --depth 1 --single-branch -b "$FFMPEG_BRANCH" ffmpeg
|
||||
fi
|
||||
|
|
@ -68,7 +76,8 @@ pushd "$AV1_MODULE_PATH/jni" || exit
|
|||
|
||||
if [[ -d dav1d ]]; then
|
||||
pushd dav1d || exit
|
||||
git checkout --force "$DAV1D_BRANCH"
|
||||
git fetch origin "$DAV1D_BRANCH" --depth 1
|
||||
git checkout --force FETCH_HEAD
|
||||
else
|
||||
git clone https://code.videolan.org/videolan/dav1d --depth 1 --single-branch -b "$DAV1D_BRANCH" dav1d
|
||||
fi
|
||||
|
|
|
|||
|
|
@ -15,7 +15,8 @@ function clone(){
|
|||
|
||||
if [[ -d "$dir" ]]; then
|
||||
pushd "$dir" || exit
|
||||
git checkout --force "$branch"
|
||||
git fetch origin "$branch" --depth 1
|
||||
git checkout --force FETCH_HEAD
|
||||
popd || exit
|
||||
else
|
||||
git clone "$repo" --depth 1 --single-branch -b "$branch" "$dir" "$@"
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue