Compare commits

...

878 Commits

Author SHA1 Message Date
Niels van Velzen
f9eeae6bf0 Fix crash when checking whether a person is a folder or not
(cherry picked from commit efc6be8d3d)
2024-02-25 13:25:58 +01:00
Niels van Velzen
c2e33b9b13 Fix crash in SocketHandler.onPlayMessage when user id is not set
(cherry picked from commit 8b87dafb26)
2024-02-25 13:25:58 +01:00
Niels van Velzen
9c4b121574 Fix DPAD_CENTER on screensaver not handled
(cherry picked from commit fb25e79233)
2024-02-25 13:25:58 +01:00
Niels van Velzen
ddb1be18bc Replace CoroutineUtils.runBlocking with new CoroutineUtils.runOnLifecycle
This will prevent the main thread from blocking, which in extreme cases can kill the app

(cherry picked from commit bd704c4ac9)
2024-02-25 13:25:57 +01:00
Niels van Velzen
4a4c1ed6ca Fix crash when updating clock in FullDetailsFragment while item information is unavailable
(cherry picked from commit 68b578057d)
2024-02-25 13:25:57 +01:00
Niels van Velzen
c28759e165 Update app/src/main/java/org/jellyfin/androidtv/util/profile/MediaCodecCapabilitiesTest.kt
(cherry picked from commit d466420858)
2024-02-18 09:57:52 +01:00
lawadr
45a5adbc0b Fix up
(cherry picked from commit f5125e937a)
2024-02-18 09:57:42 +01:00
Niels van Velzen
118eacfa90 Specify BUILD_TOOLS_VERSION for r0adkll/sign-android-release step in publish workflow 2024-02-13 13:45:10 +01:00
MichaelR416
91ccf32f77 Fix media details tag for multiple versions support
(cherry picked from commit 9ec9de0ba1)
2024-02-11 16:14:29 +01:00
Niels van Velzen
065e738773 Only show PG-13 content in screensaver
(cherry picked from commit 5ff02c7e5d)
2024-02-11 16:12:35 +01:00
MichaelR416
65a7bbf501 Add if else statement for audio downmixing support
(cherry picked from commit d5c5bcc41c)
2024-02-11 16:12:30 +01:00
renovate[bot]
d661c2faf3 fix(deps): update dependency org.jellyfin.media3:media3-ffmpeg-decoder to v1.2.1+1
(cherry picked from commit 60eac66d90)
2024-02-11 16:12:14 +01:00
renovate[bot]
8175dbd1c0 fix(deps): update androidx.media3 to v1.2.1
(cherry picked from commit abf46d42f5)
2024-02-11 16:12:13 +01:00
lawadr
f068be6945 Widen support for HEVC Main 10
Previously the HEVC codec profile only flagged support for HEVC Main 10
when MediaCodec advertised its support for level 5 (main tier) or above.
This meant devices that support only levels 4 or 4.1 (such as the
Chromecast with Google TV HD) would transcode HEVC Main 10 content even
when supported.

This change lowers the requirement from level 5 to level 4 and also adds
additional profiles specifically for HEVC Main and Main 10 with
conditions on the level so that levels beyond what is supported by
MediaCodec get transcoded.

The result is that HEVC Main 10 level 5 videos get transcoded on devices
supporting only 4.1, but HEVC Main 10 level 4.1 content plays back
directly.

(cherry picked from commit 1fb3e0399c)
2024-02-11 16:11:41 +01:00
MichaelR416
fe465db020 Use LTR for default subtitle direction
(cherry picked from commit 17b76e1536)
2024-02-11 16:11:36 +01:00
Gwilherm Folliot
d6fe265b38 Fix crash when quitting player after two plays
If you play a title from a device and play another without quitting the player and then quit the player the app crash.

Its because the NavigationRepository stacks two players and when coming to the last one, the app crash because the last player is terminated.

My fix just replace the last destination if an fragment is active

java.lang.NullPointerException: Parameter specified as non-null is null: method androidx.activity.OnBackPressedDispatcher.addCallback, parameter onBackPressedCallback
	at androidx.activity.OnBackPressedDispatcher.addCallback(Unknown Source:3)
	at org.jellyfin.androidtv.ui.playback.CustomPlaybackOverlayFragment.onActivityCreated(CustomPlaybackOverlayFragment.java:346)
	at androidx.fragment.app.Fragment.performActivityCreated(Fragment.java:3156)
	at androidx.fragment.app.FragmentStateManager.activityCreated(FragmentStateManager.java:619)
	at androidx.fragment.app.FragmentStateManager.moveToExpectedState(FragmentStateManager.java:275)
	at androidx.fragment.app.FragmentManager.executeOpsTogether(FragmentManager.java:1934)
	at androidx.fragment.app.FragmentManager.removeRedundantOperationsAndExecute(FragmentManager.java:1839)
	at androidx.fragment.app.FragmentManager.execPendingActions(FragmentManager.java:1782)
	at androidx.fragment.app.FragmentManager$5.run(FragmentManager.java:565)
	at android.os.Handler.handleCallback(Handler.java:942)
	at android.os.Handler.dispatchMessage(Handler.java:99)
	at android.os.Looper.loopOnce(Looper.java:201)
	at android.os.Looper.loop(Looper.java:288)
	at android.app.ActivityThread.main(ActivityThread.java:7884)
	at java.lang.reflect.Method.invoke(Native Method)
	at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:548)
	at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:936)

(cherry picked from commit 6867c319bb)
2024-02-11 16:11:29 +01:00
Niels van Velzen
8eecfab5d7 Disable tunneling 2024-01-07 14:53:05 +01:00
Niels van Velzen
0f9ffcacb9 Defer home initialization until user info is available
(cherry picked from commit d721482127)
2024-01-06 13:50:56 +01:00
Niels van Velzen
aa7c45d462 Disable crossfading for CardLegacyImageView to fix scaling issue
(cherry picked from commit 1fbc19740b)
2024-01-05 19:54:19 +01:00
Gwilherm Folliot
156f5ecd3c Fix crash when DISPLAY_CONTENT while playing
When a video is playing and on another device you browse libraries on a casting device it cause the app to crash

The onDisplayContent method is trying to read ExoPlayer state but from the socket thread and not the main thread

java.lang.IllegalStateException: Player is accessed on the wrong thread.
Current thread: 'DefaultDispatcher-worker-4'
Expected thread: 'main'
See https://developer.android.com/guide/topics/media/issues/player-accessed-on-wrong-thread
	at androidx.media3.exoplayer.ExoPlayerImpl.verifyApplicationThread(ExoPlayerImpl.java:2842)
	at androidx.media3.exoplayer.ExoPlayerImpl.getPlaybackState(ExoPlayerImpl.java:514)
	at androidx.media3.common.BasePlayer.isPlaying(BasePlayer.java:124)
	at org.jellyfin.androidtv.ui.playback.VideoManager.isPlaying(VideoManager.java:341)
	at org.jellyfin.androidtv.ui.playback.PlaybackController.isPlaying(PlaybackController.java:262)
	at org.jellyfin.androidtv.data.eventhandling.SocketHandler.onDisplayContent(SocketHandler.kt:184)
	at org.jellyfin.androidtv.data.eventhandling.SocketHandler.lambda$13$lambda$8(SocketHandler.kt:103)
	at org.jellyfin.androidtv.data.eventhandling.SocketHandler$$ExternalSyntheticLambda3.onReceive(Unknown Source:4)
	at org.jellyfin.androidtv.data.eventhandling.SocketHandler$_init_$lambda$13$$inlined$addGeneralCommandsListener$default$1.onReceive(ListenerRegistrationExtensions.kt:68)
	at org.jellyfin.sdk.api.sockets.helper.ListenerHelper.forwardMessage(ListenerHelper.kt:37)
	at org.jellyfin.sdk.api.sockets.SocketInstance.forwardMessage(SocketInstance.kt:283)
	at org.jellyfin.sdk.api.sockets.SocketInstance.access$forwardMessage(SocketInstance.kt:35)
	at org.jellyfin.sdk.api.sockets.SocketInstance$connectAndBind$2.invokeSuspend(SocketInstance.kt:220)
	at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
	at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:108)
	at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:584)
	at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.executeTask(CoroutineScheduler.kt:793)
	at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.runWorker(CoroutineScheduler.kt:697)
	at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:684)

(cherry picked from commit 0116530794)
2024-01-05 19:54:18 +01:00
Niels van Velzen
e56fd0d024 Set TsExtractor TimestampSearchBytes parameter based on device memory capabilities
(cherry picked from commit 76acd17d81)
2024-01-05 19:54:18 +01:00
Niels van Velzen
06e245fe48 Enable tunneling in VideoManager
(cherry picked from commit e646192196)
2024-01-05 19:54:17 +01:00
Niels van Velzen
9f0fa644a7 Enable tunneling in ExoPlayerBackend
(cherry picked from commit 1f0345f16f)
2024-01-05 19:54:17 +01:00
Niels van Velzen
5b213199dd Add <queries> element to Manifest for remote trailers
(cherry picked from commit b91fd1a0f5)
2024-01-05 19:54:16 +01:00
Niels van Velzen
b4f6273ede Disable SocketHandler when using RewriteMediaManager
(cherry picked from commit 332b6af3da)
2024-01-05 19:54:16 +01:00
Niels van Velzen
66c624e16c Replace ExoPlayer with androidx.media3
(cherry picked from commit bd204fdf87)
2024-01-05 19:54:15 +01:00
Niels van Velzen
e47916f1c2 Fix crash and inconsistent ordering when using ShuffleOrderIndexProvider
(cherry picked from commit b9e19b2515)
2024-01-05 19:54:15 +01:00
Niels van Velzen
ab7875cbb3 Fix opening collections via search results showing empty screen
(cherry picked from commit 2bbbb93dbf)
2024-01-05 19:54:14 +01:00
Niels van Velzen
5f15615a96 Fix playing audio from search queues entire row
(cherry picked from commit 8b43402192)
2024-01-05 19:54:14 +01:00
Niels van Velzen
9cda86ab84 Fix focus issue when pressing back in Fire TV soft keyboard
(cherry picked from commit c70f814064)
2024-01-05 19:54:14 +01:00
Niels van Velzen
1873497fec Fix RewriteMediaManager never used in production builds
(cherry picked from commit 75b6b12147)
2023-12-18 22:21:02 +01:00
Niels van Velzen
32849f5279 Fix crash when starting player where playback controller may be null 2023-12-17 15:56:02 +01:00
queeup
0c1aac96d7 Translated using Weblate (Turkish)
Currently translated at 99.5% (497 of 499 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/tr/
2023-12-17 08:30:20 -05:00
queeup
7fefb735d9 Translated using Weblate (Turkish)
Currently translated at 99.5% (497 of 499 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/tr/
2023-12-16 19:30:20 -05:00
queeup
00c59f8e58 Translated using Weblate (Turkish)
Currently translated at 99.5% (497 of 499 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/tr/
2023-12-16 15:30:20 -05:00
renovate[bot]
c87121502c chore(deps): update gradle/gradle-build-action action to v2.11.0 2023-12-15 19:04:04 +01:00
renovate[bot]
636facc8b2 fix(deps): update dependency androidx.activity:activity-ktx to v1.8.2 2023-12-15 19:03:20 +01:00
renovate[bot]
98ac4d3eda chore(deps): update github/codeql-action action to v3 2023-12-15 19:01:31 +01:00
renovate[bot]
d3cd69530a chore(deps): update actions/upload-artifact action to v4 2023-12-15 19:01:06 +01:00
queeup
9937ad3ddd Translated using Weblate (Turkish)
Currently translated at 99.5% (497 of 499 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/tr/
2023-12-14 19:30:20 -05:00
queeup
9a6fc333a1 Translated using Weblate (Turkish)
Currently translated at 99.5% (497 of 499 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/tr/
2023-12-13 21:30:19 -05:00
xdlivecat
5811b99b35 Translated using Weblate (Croatian)
Currently translated at 77.1% (385 of 499 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/hr/
2023-12-13 21:30:19 -05:00
queeup
72c97635e3 Translated using Weblate (Turkish)
Currently translated at 99.5% (497 of 499 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/tr/
2023-12-12 18:30:19 -05:00
Alexander Gramiak
afddc96789 util: Don't use TZ offset for media Premiere Date
TimeUtils.getDate internally calls Date.format, which translates the UTC
timestamp into local TZ timestamp. If a user is in a TZ with negative offset,
then the Premiere Date will be presented as a day earlier.

Example with TZ of UTC-6:

item.getPremiereDate()                    => 2023-12-09T00:00
TimeUtils.getDate(item.getPremiereDate()) => Sat Dec 08 18:00:00 CST 2023

Fixes #3190.
2023-12-12 15:27:24 +01:00
guiweber
a8ef52bca9 Update InfoLayoutHelper.java
Keep displaying codec for HDR content
2023-12-12 15:24:59 +01:00
Lukáš Kucharczyk
a0c81df329 Translated using Weblate (Czech)
Currently translated at 100.0% (499 of 499 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/cs/
2023-12-12 08:30:19 -05:00
renovate[bot]
c4db326da6 Update dependency com.vanniktech:blurhash to v0.2.0 2023-12-12 11:53:25 +01:00
queeup
7d53fee677 Translated using Weblate (Turkish)
Currently translated at 99.5% (497 of 499 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/tr/
2023-12-11 17:30:18 -05:00
Niels van Velzen
fa10019f8b Fix rare case where splash screen could get stuck 2023-12-11 14:35:29 +01:00
queeup
b4fbd82c21 Translated using Weblate (Turkish)
Currently translated at 99.5% (497 of 499 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/tr/
2023-12-10 20:30:18 -05:00
renovate[bot]
db6c23877f Update dependency io.gitlab.arturbosch.detekt to v1.23.4 2023-12-10 15:10:20 +01:00
Niels van Velzen
f8fd9765ac Use androidx.work:work-runtime (non ktx version) 2023-12-10 15:10:10 +01:00
renovate[bot]
8b5ad52fb9 Update dependency androidx.work:work-runtime-ktx to v2.9.0 2023-12-10 15:10:10 +01:00
renovate[bot]
90f2623c9a Update dependency androidx.compose.compiler:compiler to v1.5.6 2023-12-10 15:03:37 +01:00
renovate[bot]
9d2dacdac3 Update Kotlin 2023-12-10 15:03:29 +01:00
renovate[bot]
174ed2e281 Update dependency androidx.media3:media3-session to v1.2.0 2023-12-10 15:00:36 +01:00
renovate[bot]
8a0b97619b Update coil to v2.5.0 2023-12-10 14:59:23 +01:00
renovate[bot]
3d2c2c7363 Update kotest to v5.8.0 2023-12-10 14:59:13 +01:00
renovate[bot]
b2fb409605 Update dependency com.android.tools.build:gradle to v8.2.0 2023-12-10 14:58:44 +01:00
renovate[bot]
f14b8ed97b Update actions/stale action to v9 2023-12-10 14:57:12 +01:00
renovate[bot]
92e91ab217 Update actions/setup-java action to v4 2023-12-10 14:57:00 +01:00
renovate[bot]
4dd363b0cf Update CI dependencies 2023-12-10 14:56:15 +01:00
renovate[bot]
a083dfcc04 Update dependency androidx.window:window to v1.2.0 2023-12-10 14:48:39 +01:00
renovate[bot]
d3dba03ee0 Update dependency com.android.tools:desugar_jdk_libs to v2.0.4 2023-12-10 14:45:11 +01:00
renovate[bot]
56d6661906 Update dependency androidx.fragment:fragment-ktx to v1.6.2 2023-12-10 14:44:44 +01:00
renovate[bot]
2fd2efa581 Update dependency androidx.activity:activity-ktx to v1.8.1 2023-12-10 14:43:51 +01:00
renovate[bot]
d866b1bce1 Update aboutlibraries to v10.9.2 2023-12-10 14:42:05 +01:00
renovate[bot]
238a9d27fd Update dependency gradle to v8.5 2023-12-10 14:36:40 +01:00
Niels van Velzen
004b39e89b Close video player on resume with invalid state
In some cases the video player may be stopped when the activity is paused. This ends up crashing the app on resume. Check for this kind of invalid state and close the video player to prevent crashing.
2023-12-10 14:35:52 +01:00
Niels van Velzen
d9eb849038 Fix system screensaver starting during video playback 2023-12-10 14:35:43 +01:00
Oatavandi
67581d1cf0 Translated using Weblate (Tamil)
Currently translated at 100.0% (4 of 4 strings)

Translation: Jellyfin Android/Jellyfin Android TV Fastlane
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv-fastlane/ta/
2023-12-04 15:30:17 -05:00
Oatavandi
7c1a07fcfc Translated using Weblate (Tamil)
Currently translated at 100.0% (499 of 499 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/ta/
2023-12-04 15:30:17 -05:00
Weblate
d733b254c4 Update translation files
Updated by "Remove blank strings" hook in Weblate.

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/
2023-12-03 18:30:18 -05:00
kpupp
2a1579ad6e Translated using Weblate (Japanese)
Currently translated at 55.5% (277 of 499 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/ja/
2023-12-03 18:30:17 -05:00
renovate[bot]
665d0b5946 Update dependency org.jellyfin.sdk:jellyfin-core to v1.4.6 2023-12-02 22:56:56 +01:00
queeup
2d246de948 Translated using Weblate (Turkish)
Currently translated at 99.5% (497 of 499 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/tr/
2023-12-02 12:30:15 -05:00
queeup
358df48f50 Translated using Weblate (Turkish)
Currently translated at 99.5% (497 of 499 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/tr/
2023-12-02 08:30:15 -05:00
queeup
c672d42ae1 Translated using Weblate (Turkish)
Currently translated at 99.5% (497 of 499 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/tr/
2023-12-01 05:30:15 -05:00
Kristofer P
bbb6270832 Translated using Weblate (Estonian)
Currently translated at 9.4% (47 of 499 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/et/
2023-11-30 19:30:15 -05:00
queeup
fc1ddedfb1 Translated using Weblate (Turkish)
Currently translated at 99.5% (497 of 499 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/tr/
2023-11-30 19:30:15 -05:00
queeup
d7bba7c026 Translated using Weblate (Turkish)
Currently translated at 99.1% (495 of 499 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/tr/
2023-11-29 16:30:14 -05:00
EfeDevirgen
ec5c433c84 Translated using Weblate (Turkish)
Currently translated at 98.7% (493 of 499 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/tr/
2023-11-29 12:30:14 -05:00
queeup
b2e99509a0 Translated using Weblate (Turkish)
Currently translated at 100.0% (4 of 4 strings)

Translation: Jellyfin Android/Jellyfin Android TV Fastlane
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv-fastlane/tr/
2023-11-27 19:30:15 -05:00
queeup
7b2eaf7df4 Translated using Weblate (Turkish)
Currently translated at 96.7% (483 of 499 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/tr/
2023-11-27 19:30:15 -05:00
renovate[bot]
fec6ba7257 Update dependency org.jellyfin.sdk:jellyfin-core to v1.4.5 2023-11-27 15:26:28 +01:00
EfeDevirgen
0d82280814 Translated using Weblate (Turkish)
Currently translated at 83.3% (416 of 499 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/tr/
2023-11-26 16:30:14 -05:00
queeup
dd929f2212 Translated using Weblate (Turkish)
Currently translated at 83.3% (416 of 499 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/tr/
2023-11-26 16:30:14 -05:00
EfeDevirgen
3e733a21f1 Translated using Weblate (Turkish)
Currently translated at 68.1% (340 of 499 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/tr/
2023-11-26 10:30:14 -05:00
queeup
3eb53b8bcd Translated using Weblate (Turkish)
Currently translated at 68.1% (340 of 499 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/tr/
2023-11-26 10:30:14 -05:00
Simas Šimas
75fecefc4b Translated using Weblate (Lithuanian)
Currently translated at 100.0% (4 of 4 strings)

Translation: Jellyfin Android/Jellyfin Android TV Fastlane
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv-fastlane/lt/
2023-11-12 14:25:50 -05:00
Troja
4302c6503f Translated using Weblate (Belarusian)
Currently translated at 25.2% (126 of 499 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/be/
2023-11-09 09:25:49 -05:00
Troja
b69b6094fb Translated using Weblate (Belarusian)
Currently translated at 100.0% (4 of 4 strings)

Translation: Jellyfin Android/Jellyfin Android TV Fastlane
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv-fastlane/be/
2023-11-09 05:25:49 -05:00
Troja
74b732cab7 Translated using Weblate (Belarusian)
Currently translated at 23.6% (118 of 499 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/be/
2023-11-09 05:25:49 -05:00
Qeynos
e61bc77d02 Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (499 of 499 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/zh_Hans/
2023-11-09 05:25:48 -05:00
Luis Fonseca
2021b52d0a Translated using Weblate (Portuguese (Portugal))
Currently translated at 100.0% (4 of 4 strings)

Translation: Jellyfin Android/Jellyfin Android TV Fastlane
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv-fastlane/pt_PT/
2023-11-08 11:25:49 -05:00
Luis Fonseca
a5b7c4d1de Translated using Weblate (Portuguese (Portugal))
Currently translated at 78.3% (391 of 499 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/pt_PT/
2023-11-08 11:25:49 -05:00
Troja
780e4c0126 Translated using Weblate (Belarusian)
Currently translated at 18.2% (91 of 499 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/be/
2023-11-08 08:25:49 -05:00
hoanghuy309
d6753f7135 Translated using Weblate (Vietnamese)
Currently translated at 96.7% (483 of 499 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/vi/
2023-11-07 12:25:48 -05:00
DaiCae
9102878741 Translated using Weblate (Chinese (Simplified))
Currently translated at 99.5% (497 of 499 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/zh_Hans/
2023-11-06 12:25:48 -05:00
Retrial
c6bf2a3a7f Translated using Weblate (Greek)
Currently translated at 100.0% (499 of 499 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/el/
2023-11-06 12:25:48 -05:00
shay zaman
c9c3e53402 Translated using Weblate (Persian)
Currently translated at 64.7% (323 of 499 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/fa/
2023-11-04 18:25:49 -04:00
Oskari Lavinto
3b400b3796 Translated using Weblate (Finnish)
Currently translated at 100.0% (499 of 499 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/fi/
2023-11-03 01:25:48 -04:00
Sami Mäkelä
c39c6a662d Translated using Weblate (Finnish)
Currently translated at 99.7% (498 of 499 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/fi/
2023-11-02 16:25:48 -04:00
Philip Karlsson
45a46b19cc Translated using Weblate (Swedish)
Currently translated at 100.0% (4 of 4 strings)

Translation: Jellyfin Android/Jellyfin Android TV Fastlane
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv-fastlane/sv/
2023-10-30 07:25:48 -04:00
Philip Karlsson
4594c48f93 Translated using Weblate (Swedish)
Currently translated at 100.0% (499 of 499 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/sv/
2023-10-30 07:25:48 -04:00
Bas
46bd415b7f Translated using Weblate (Dutch)
Currently translated at 100.0% (499 of 499 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/nl/
2023-10-29 18:25:48 -04:00
Nahidxoy
5ce01985f6 Translated using Weblate (Bengali (Bangladesh))
Currently translated at 19.0% (95 of 499 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/bn_BD/
2023-10-28 02:25:48 -04:00
palap
44f2c6cc57 Translated using Weblate (Russian)
Currently translated at 98.9% (494 of 499 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/ru/
2023-10-27 07:25:48 -04:00
SGT
7ae05ba2b1 Translated using Weblate (Spanish (Latin America))
Currently translated at 100.0% (499 of 499 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/es_419/
2023-10-25 12:25:49 -04:00
SGT
8ef810fa27 Translated using Weblate (Spanish (Argentina))
Currently translated at 100.0% (499 of 499 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/es_AR/
2023-10-25 12:25:48 -04:00
SGT
61012c41d4 Translated using Weblate (Spanish)
Currently translated at 100.0% (499 of 499 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/es/
2023-10-25 12:25:48 -04:00
SGT
8e685d2a86 Translated using Weblate (Spanish (Mexico))
Currently translated at 84.9% (424 of 499 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/es_MX/
2023-10-25 12:25:48 -04:00
SGT
79deb6e611 Translated using Weblate (Spanish (Latin America))
Currently translated at 100.0% (499 of 499 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/es_419/
2023-10-24 18:25:48 -04:00
alison2033
8e263c4df4 Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (499 of 499 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/pt_BR/
2023-10-23 22:25:48 -04:00
Vijay
cb644a6e5e Translated using Weblate (Tamil)
Currently translated at 94.9% (474 of 499 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/ta/
2023-10-23 16:25:49 -04:00
Niels van Velzen
f464828766 Implement queue management in RewriteMediaManager 2023-10-22 17:12:31 +02:00
Niels van Velzen
8edc261805 Support custom areItemsTheSame and areContentsTheSame functions in MutableObjectAdapter 2023-10-22 17:12:31 +02:00
renovate[bot]
4bb27dff2d Update acra to v5.11.3 2023-10-22 09:50:07 +02:00
renovate[bot]
28bdc6f808 Update github/codeql-action action to v2.22.4 2023-10-20 22:39:24 +02:00
Niels van Velzen
37112e53e3 Fix GridButtonPresenter focus on old Android platforms 2023-10-20 22:39:10 +02:00
renovate[bot]
9478e7ea93 Update dependency androidx.compose.material:material to v1.5.4 2023-10-18 22:06:37 +02:00
renovate[bot]
7e127ad0a4 Update dependency androidx.recyclerview:recyclerview to v1.3.2 2023-10-18 22:00:15 +02:00
renovate[bot]
5c1848e665 Update dependency androidx.compose.ui:ui-tooling to v1.5.4 2023-10-18 21:57:18 +02:00
renovate[bot]
127a1d142d Update dependency androidx.compose.foundation:foundation to v1.5.4 2023-10-18 21:48:18 +02:00
renovate[bot]
6772860589 Update actions/checkout action to v4.1.1 2023-10-18 21:48:03 +02:00
Andrijan Jovanovski
d6d81181bd Translated using Weblate (Macedonian)
Currently translated at 100.0% (4 of 4 strings)

Translation: Jellyfin Android/Jellyfin Android TV Fastlane
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv-fastlane/mk/
2023-10-17 19:25:44 -04:00
Andrijan Jovanovski
0e5318ef0b Translated using Weblate (Macedonian)
Currently translated at 100.0% (499 of 499 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/mk/
2023-10-17 19:25:43 -04:00
Acrotos
9f2831ad7b Translated using Weblate (Romanian)
Currently translated at 100.0% (499 of 499 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/ro/
2023-10-17 10:25:36 -04:00
renovate[bot]
536959c7b4 Update github/codeql-action action to v2.22.3 2023-10-15 15:01:05 +02:00
Edu MN
41fce8afe2 Translated using Weblate (Catalan)
Currently translated at 100.0% (499 of 499 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/ca/
2023-10-15 08:25:34 -04:00
Fadi Chamieh
516a679866 Translated using Weblate (Arabic)
Currently translated at 100.0% (499 of 499 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/ar/
2023-10-14 08:25:34 -04:00
Niels van Velzen
3f911f1720 Reset preferredDisplayModeId back to zero when closing video player fragment 2023-10-13 21:23:02 +02:00
pgk83
f6a4560192 Translated using Weblate (Hungarian)
Currently translated at 100.0% (499 of 499 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/hu/
2023-10-12 02:30:10 -04:00
Niels van Velzen
655b9ce736 Support remux/transcode fallback in new music player 2023-10-11 21:44:43 +02:00
nextlooper42
d18922f7df Translated using Weblate (Slovak)
Currently translated at 100.0% (499 of 499 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/sk/
2023-10-11 06:25:32 -04:00
SaddFox
9e76dc63a6 Translated using Weblate (Slovenian)
Currently translated at 100.0% (4 of 4 strings)

Translation: Jellyfin Android/Jellyfin Android TV Fastlane
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv-fastlane/sl/
2023-10-09 19:25:33 -04:00
SaddFox
cbbfc4c9f2 Translated using Weblate (Slovenian)
Currently translated at 100.0% (499 of 499 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/sl/
2023-10-09 19:25:32 -04:00
renovate[bot]
d15f255f19 Update dependency gradle to v8.4 2023-10-09 21:39:57 +02:00
renovate[bot]
6eedc81a13 Update dependency androidx.activity:activity-ktx to v1.8.0 2023-10-09 21:39:37 +02:00
renovate[bot]
237e5ac0a4 Update CI dependencies 2023-10-09 21:39:23 +02:00
Retrial
ec7391a1ac Translated using Weblate (Greek)
Currently translated at 99.5% (497 of 499 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/el/
2023-10-09 05:48:55 -04:00
Horizon1250
a9cec1eede fix embedded subtitles not loading when subtitle filter on library 2023-10-07 14:03:33 +02:00
Ayla Ounce
eb68107e81 Enable support for Opus and FLAC in ExoPlayer 2023-10-07 14:01:46 +02:00
edbert khovey
51320a21ce Translated using Weblate (Indonesian)
Currently translated at 71.9% (359 of 499 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/id/
2023-10-06 15:31:33 -04:00
Sun Zizhuo
adb53287e8 Translated using Weblate (Chinese (Simplified))
Currently translated at 99.3% (496 of 499 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/zh_Hans/
2023-10-06 10:31:32 -04:00
renovate[bot]
9eca8d676b Update dependency androidx.compose.material:material to v1.5.3 2023-10-05 22:02:26 +02:00
Niels van Velzen
21fd22141b Fix playback stop reported twice 2023-10-05 21:51:37 +02:00
renovate[bot]
bbeb707328 Update dependency androidx.compose.foundation:foundation to v1.5.3 2023-10-05 21:49:48 +02:00
renovate[bot]
37eed3f64a Update dependency androidx.compose.ui:ui-tooling to v1.5.3 2023-10-05 21:49:21 +02:00
TowyTowy
8fd45865bd Translated using Weblate (Danish)
Currently translated at 50.0% (2 of 4 strings)

Translation: Jellyfin Android/Jellyfin Android TV Fastlane
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv-fastlane/da/
2023-10-04 23:31:32 -04:00
TowyTowy
9180b909e4 Translated using Weblate (Danish)
Currently translated at 100.0% (499 of 499 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/da/
2023-10-04 23:31:32 -04:00
Ben
ebe79bca9f Translated using Weblate (French)
Currently translated at 100.0% (4 of 4 strings)

Translation: Jellyfin Android/Jellyfin Android TV Fastlane
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv-fastlane/fr/
2023-10-03 11:41:09 -04:00
Fiscally Hash
c14ecad2cf Translated using Weblate (Spanish)
Currently translated at 100.0% (499 of 499 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/es/
2023-10-03 04:41:09 -04:00
Bas
19d1984366 Translated using Weblate (Dutch)
Currently translated at 100.0% (499 of 499 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/nl/
2023-10-02 17:41:09 -04:00
FrankJeager
022985cd4e Translated using Weblate (French)
Currently translated at 100.0% (499 of 499 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/fr/
2023-10-02 10:41:09 -04:00
Tejas Sharma
62543301e7 Translated using Weblate (English (United Kingdom))
Currently translated at 100.0% (499 of 499 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/en_GB/
2023-10-01 21:41:09 -04:00
grunjol
b38a333b3b Translated using Weblate (Spanish (Argentina))
Currently translated at 100.0% (4 of 4 strings)

Translation: Jellyfin Android/Jellyfin Android TV Fastlane
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv-fastlane/es_AR/
2023-09-30 14:41:10 -04:00
grunjol
27fb959c4c Translated using Weblate (Spanish (Argentina))
Currently translated at 100.0% (499 of 499 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/es_AR/
2023-09-30 14:41:10 -04:00
Guy Porat
9d7b108b92 Translated using Weblate (Hebrew (Israel))
Currently translated at 12.2% (61 of 499 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/he_IL/
2023-09-30 08:41:09 -04:00
hoanghuy309
d6af6e3822 Translated using Weblate (Vietnamese)
Currently translated at 96.1% (480 of 499 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/vi/
2023-09-29 09:41:09 -04:00
Niels van Velzen
1c28045bf4 Use layer-list for app_banner drawable 2023-09-29 08:56:43 +02:00
renovate[bot]
04ef180024 Update dependency androidx.compose.material:material to v1.5.2 2023-09-28 09:21:17 +02:00
renovate[bot]
b9026f5f1e Update aboutlibraries to v10.9.1 2023-09-28 09:19:02 +02:00
renovate[bot]
dbf5f0c6e6 Update CI dependencies 2023-09-28 09:18:41 +02:00
renovate[bot]
84847f5d10 Update dependency androidx.compose.foundation:foundation to v1.5.2 2023-09-28 09:13:06 +02:00
renovate[bot]
6095450864 Update dependency androidx.compose.ui:ui-tooling to v1.5.2 2023-09-28 09:12:56 +02:00
h-risto
a2c2079595 Translated using Weblate (Estonian)
Currently translated at 6.2% (31 of 499 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/et/
2023-09-27 18:41:09 -04:00
badmast
f8e103fc36 Translated using Weblate (Persian)
Currently translated at 64.5% (322 of 499 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/fa/
2023-09-26 07:41:09 -04:00
Rasmus Jansson
6992e5c8df Translated using Weblate (Swedish)
Currently translated at 98.7% (493 of 499 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/sv/
2023-09-25 07:40:59 -04:00
Kristjan Knez
d7b1a7ef6c Translated using Weblate (Italian)
Currently translated at 100.0% (499 of 499 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/it/
2023-09-25 07:40:57 -04:00
Niels van Velzen
a373d8be38 Use ignorePathParameters in AudioMediaStreamResolver 2023-09-24 11:21:35 +02:00
renovate[bot]
600ce9b870 Update dependency org.jellyfin.sdk:jellyfin-core to v1.4.4 2023-09-24 11:21:35 +02:00
renovate[bot]
bf31c1584d Update actions/checkout action to v4.1.0 2023-09-24 11:07:37 +02:00
renovate[bot]
55c85cbfff Update aboutlibraries to v10.9.0 2023-09-24 09:59:36 +02:00
Kevin Orel Edry
9087f3b1d6 Translated using Weblate (Hebrew (Israel))
Currently translated at 7.8% (39 of 499 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/he_IL/
2023-09-22 17:40:56 -04:00
xOrb1t
b3031b0a41 Translated using Weblate (Russian)
Currently translated at 98.9% (494 of 499 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/ru/
2023-09-22 13:40:56 -04:00
Sverre
686a6b00ed Translated using Weblate (Norwegian Bokmål)
Currently translated at 100.0% (4 of 4 strings)

Translation: Jellyfin Android/Jellyfin Android TV Fastlane
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv-fastlane/nb_NO/
2023-09-22 04:40:58 -04:00
Sverre
8619c16320 Translated using Weblate (Norwegian Bokmål)
Currently translated at 95.7% (478 of 499 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/nb_NO/
2023-09-22 04:40:58 -04:00
Johannes Loher
0758f6b1db Translated using Weblate (German)
Currently translated at 100.0% (499 of 499 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/de/
2023-09-21 18:40:55 -04:00
Niels van Velzen
bfecd1cc5f Add initial playback capability testing 2023-09-21 18:00:41 +02:00
renovate[bot]
db316bc47c Update actions/setup-java action to v3.13.0 2023-09-21 15:08:15 +02:00
Niels van Velzen
dfdd605fbd Use vignette effect in screensaver to darken background image 2023-09-21 14:45:18 +02:00
Niels van Velzen
a2fba5b386 Fix progress bar positioning in view_row_details 2023-09-20 22:19:25 +02:00
Niels van Velzen
eda2c4ec52 Migrate ExternalPlayerActivity (mostly) to SDK 2023-09-20 22:19:17 +02:00
Niels van Velzen
e8d71314d9 Fix watched indicator 2023-09-20 22:14:11 +02:00
Niels van Velzen
da92b42809 Use series poster in latest row when "prefer series thumbnails" is enabled 2023-09-20 22:14:11 +02:00
Niels van Velzen
1633c37d71 Fix crash during media stream resolving for corrupt files 2023-09-19 18:00:29 +02:00
Niels van Velzen
194bad38f2 Restore session on MainActivity stop 2023-09-19 15:39:38 +02:00
DoggoOfSpeed
b10061b583 Fix user image being too cropped in (#3005) 2023-09-19 14:38:55 +02:00
DoggoOfSpeed
f7f51a6914 Grab background images no larger than the screen (#3020) 2023-09-19 14:36:35 +02:00
DoggoOfSpeed
dec5b3a6d3 Fix blurry images 2023-09-19 14:30:29 +02:00
renovate[bot]
16d48d317e Update github/codeql-action action to v2.21.8 2023-09-19 14:25:44 +02:00
João Vitor Borges
086d79cd92 Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (499 of 499 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/pt_BR/
2023-09-18 17:40:54 -04:00
renovate[bot]
9ad77f8d74 Update koin to v3.5.0 2023-09-18 21:34:14 +02:00
Oskari Lavinto
db2303557e Translated using Weblate (Finnish)
Currently translated at 100.0% (4 of 4 strings)

Translation: Jellyfin Android/Jellyfin Android TV Fastlane
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv-fastlane/fi/
2023-09-17 04:40:54 -04:00
Oskari Lavinto
ff7e5da123 Translated using Weblate (Finnish)
Currently translated at 99.3% (496 of 499 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/fi/
2023-09-17 04:40:53 -04:00
stanol
46d246d829 Translated using Weblate (Ukrainian)
Currently translated at 100.0% (499 of 499 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/uk/
2023-09-16 11:40:53 -04:00
Kityn
0f986c1de9 Translated using Weblate (Polish)
Currently translated at 100.0% (499 of 499 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/pl/
2023-09-16 07:40:53 -04:00
Lukáš Kucharczyk
4af843e1cf Translated using Weblate (Czech)
Currently translated at 100.0% (499 of 499 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/cs/
2023-09-16 04:40:53 -04:00
Tycho Brouwer
beaa95e9ba Translated using Weblate (Dutch)
Currently translated at 100.0% (499 of 499 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/nl/
2023-09-15 21:40:53 -04:00
Weblate
658d22592b Update translation files
Updated by "Cleanup translation files" hook in Weblate.

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/
2023-09-15 16:26:28 -04:00
Niels van Velzen
8c7175613c Fix lbl_because_you_watched string 2023-09-15 22:26:22 +02:00
Niels van Velzen
2ba63a73bf Migrate SuggestedMoviesFragment to SDK 2023-09-15 22:26:22 +02:00
hcharbonnier
1aba1b2e4c Translated using Weblate (French)
Currently translated at 100.0% (4 of 4 strings)

Translation: Jellyfin Android/Jellyfin Android TV Fastlane
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv-fastlane/fr/
2023-09-14 18:40:52 -04:00
renovate[bot]
86f59167e2 Update github/codeql-action action to v2.21.7 2023-09-14 21:32:22 +02:00
renovate[bot]
41d2827eb4 Update github/codeql-action action to v2.21.6 2023-09-14 10:02:36 +02:00
Niels van Velzen
7e91c3d6f1 Fully migrate ItemLauncher to SDK 2023-09-14 07:52:27 +02:00
Niels van Velzen
61d9048299 Migrate ItemListView to SDK 2023-09-14 07:52:03 +02:00
Niels van Velzen
6507a4f300 Remove old user preference migrations (before 0.15) 2023-09-12 22:10:15 +02:00
Niels van Velzen
83ead8f448 Fix ExternalPlayerActivity not using PreferenceStore.reset 2023-09-12 22:10:00 +02:00
Bas
cd7f759d78 Translated using Weblate (Dutch)
Currently translated at 100.0% (499 of 499 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/nl/
2023-09-12 10:40:52 -04:00
Kirill
ee2968bc2a Translated using Weblate (Russian)
Currently translated at 99.1% (495 of 499 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/ru/
2023-09-09 09:40:50 -04:00
Niels van Velzen
6c8a1684e2 Fix compose deprecations 2023-09-09 13:37:19 +02:00
DoggoOfSpeed
ebf8c5c0ae Fix grid getting focus when empty (#3003) 2023-09-09 11:43:36 +02:00
renovate[bot]
0878fc963a Update dependency androidx.compose.material:material to v1.5.1 2023-09-09 09:46:23 +02:00
renovate[bot]
9b44793df0 Update dependency androidx.compose.foundation:foundation to v1.5.1 2023-09-09 09:39:15 +02:00
renovate[bot]
12e94ce74a Update dependency androidx.compose.ui:ui-tooling to v1.5.1 2023-09-09 09:38:48 +02:00
renovate[bot]
1e2adaade8 Update dependency androidx.core:core-ktx to v1.12.0 2023-09-09 09:38:26 +02:00
Niels van Velzen
c793771b2d Fix Koin deprecation warnings 2023-09-08 20:47:34 +02:00
Niels van Velzen
3327c8c7f2 Update StrokeTextView.onDraw signature 2023-09-08 20:47:34 +02:00
Niels van Velzen
4cdc555b6d Use Android 14 SDK (API 34) 2023-09-08 20:47:34 +02:00
renovate[bot]
d6d9f157c6 Update kotest to v5.7.2 2023-09-08 20:32:20 +02:00
renovate[bot]
14db997ac1 Update actions/upload-artifact action to v3.1.3 2023-09-08 20:32:04 +02:00
renovate[bot]
e08faa0c2a Update androidx.lifecycle to v2.6.2 2023-09-08 20:31:49 +02:00
Bas
523360fa65 Translated using Weblate (Dutch)
Currently translated at 100.0% (499 of 499 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/nl/
2023-09-07 18:40:49 -04:00
Oskari Lavinto
cf732655a6 Translated using Weblate (Finnish)
Currently translated at 100.0% (4 of 4 strings)

Translation: Jellyfin Android/Jellyfin Android TV Fastlane
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv-fastlane/fi/
2023-09-07 12:40:49 -04:00
Niels van Velzen
404223b46b Fix sync issue in ItemRowAdapterHelper.kt 2023-09-05 21:53:08 +02:00
renovate[bot]
4b95a37204 Update acra to v5.11.2 2023-09-05 14:34:47 +02:00
renovate[bot]
0652af8b95 Update kotest to v5.7.1 2023-09-05 14:34:17 +02:00
renovate[bot]
ff87eb1a83 Update actions/checkout action to v4 2023-09-05 14:33:58 +02:00
Sam Karanja
4c3c73c2f4 Added translation using Weblate (Swahili) 2023-09-04 15:23:32 -04:00
Lukáš Kucharczyk
7a45a26735 Translated using Weblate (Czech)
Currently translated at 100.0% (499 of 499 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/cs/
2023-09-04 04:40:48 -04:00
Kityn
c654cdef0e Translated using Weblate (Polish)
Currently translated at 100.0% (499 of 499 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/pl/
2023-09-03 20:40:48 -04:00
queeup
4161728637 Translated using Weblate (Turkish)
Currently translated at 59.9% (299 of 499 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/tr/
2023-09-02 19:40:48 -04:00
Juan Perez
ae2af51bb6 Translated using Weblate (Spanish (Latin America))
Currently translated at 100.0% (4 of 4 strings)

Translation: Jellyfin Android/Jellyfin Android TV Fastlane
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv-fastlane/es_419/
2023-08-31 18:40:47 -04:00
renovate[bot]
f92437aad5 Update dependency gradle to v8.3 2023-08-31 21:55:17 +02:00
queeup
197f320cb4 Translated using Weblate (Turkish)
Currently translated at 59.7% (298 of 499 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/tr/
2023-08-31 14:40:47 -04:00
queeup
f6ce3b643e Translated using Weblate (Turkish)
Currently translated at 59.1% (295 of 499 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/tr/
2023-08-31 09:40:47 -04:00
DoggoOfSpeed
8b91fb9d7c turn buttons into radio buttons 2023-08-30 20:28:30 +02:00
renovate[bot]
cd847c4fde Update Kotlin 2023-08-30 09:21:34 +02:00
renovate[bot]
410e270bd4 Update dependency androidx.compose.compiler:compiler to v1.5.3 2023-08-30 09:21:16 +02:00
DoggoOfSpeed
9c554644f3 Folder improvements (#2979) 2023-08-29 14:11:27 +02:00
renovate[bot]
58418f8927 Update CI dependencies 2023-08-29 07:28:57 +02:00
Daivy
343d107ee6 Translated using Weblate (Italian)
Currently translated at 100.0% (4 of 4 strings)

Translation: Jellyfin Android/Jellyfin Android TV Fastlane
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv-fastlane/it/
2023-08-28 11:40:46 -04:00
renovate[bot]
2c0ed89fbe Update dependency org.jellyfin.sdk:jellyfin-core to v1.4.3 2023-08-28 08:49:34 +02:00
Niels van Velzen
8e8f99e9f6 Report playMethod and repeatMode in PlaySessionService 2023-08-27 21:54:51 +02:00
Niels van Velzen
f59488ae7d Add MediaConversionMethod to MediaStream 2023-08-27 21:54:51 +02:00
Niels van Velzen
ed0ae5094a Support remuxing and transcoding in AudioMediaStreamResolver 2023-08-27 21:54:51 +02:00
Niels van Velzen
228142a9a0 Remove Thread.sleep usages 2023-08-27 12:00:43 +02:00
DoggoOfSpeed
3d8d8c3437 Alignment changes to audio player (#2995) 2023-08-25 19:59:59 +02:00
DoggoOfSpeed
4ac190540f Synochronise music timers (#2996) 2023-08-25 19:54:53 +02:00
sleepycatcoding
1ac3408d51 Translated using Weblate (Estonian)
Currently translated at 6.0% (30 of 499 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/et/
2023-08-24 19:40:43 -04:00
renovate[bot]
1401330573 Update actions/checkout action to v3.6.0 2023-08-24 21:21:21 +02:00
renovate[bot]
15751c920a Update dependency androidx.compose.compiler:compiler to v1.5.2 2023-08-23 21:41:31 +02:00
renovate[bot]
da161f2edb Update dependency com.android.tools.build:gradle to v8.1.1 2023-08-22 13:52:22 +02:00
Niels van Velzen
1c427e82c3 Always use user provided fast forward and rewind values 2023-08-20 18:48:53 +02:00
renovate[bot]
51938cd283 Update dependency io.mockk:mockk to v1.13.7 2023-08-20 09:10:19 +02:00
renovate[bot]
93b2ca8bf3 Update CI dependencies 2023-08-20 09:09:55 +02:00
Niels van Velzen
3b963c9b2f Fix disabling subtitles not always working 2023-08-20 08:24:45 +02:00
DoggoOfSpeed
835d0522c4 Add margin to media grid (#2964) 2023-08-17 21:49:00 +02:00
renovate[bot]
b74cf235df Update exoplayer 2023-08-17 07:18:54 +02:00
renovate[bot]
34116bf283 Update dependency androidx.media3:media3-session to v1.1.1 2023-08-16 17:43:51 +02:00
NunzioArdi
8d9105a13b Translated using Weblate (French)
Currently translated at 100.0% (4 of 4 strings)

Translation: Jellyfin Android/Jellyfin Android TV Fastlane
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv-fastlane/fr/
2023-08-15 16:34:17 -04:00
Niels van Velzen
1182dc27be Remove recordings from search results 2023-08-15 16:05:24 +02:00
Marco Dura
03e0cf9edc Translated using Weblate (Slovenian)
Currently translated at 76.9% (384 of 499 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/sl/
2023-08-15 08:34:17 -04:00
renovate[bot]
3f4d6ace44 Update dependency org.jellyfin.exoplayer:exoplayer-ffmpeg-extension to v2.19.0+1 2023-08-15 08:37:57 +02:00
Zoe
4dae34a59c Translated using Weblate (Norwegian Bokmål)
Currently translated at 58.5% (292 of 499 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/nb_NO/
2023-08-13 18:34:16 -04:00
Niels van Velzen
f8a02ec460 Fix crash when same item appears in queue multiple times 2023-08-13 22:21:50 +02:00
Niels van Velzen
083e0d9549 Fix ExoPlayerBackend not triggering onMediaStreamEnd correctly 2023-08-13 22:21:40 +02:00
Bas
1d7371524f Translated using Weblate (Dutch)
Currently translated at 100.0% (499 of 499 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/nl/
2023-08-13 08:34:16 -04:00
plafreniere
066727854c Translated using Weblate (French)
Currently translated at 100.0% (499 of 499 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/fr/
2023-08-11 11:34:15 -04:00
newton181
d44d1a1f1a Translated using Weblate (Spanish (Latin America))
Currently translated at 100.0% (499 of 499 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/es_419/
2023-08-10 21:34:15 -04:00
renovate[bot]
754fdf2026 Update acra to v5.11.1 2023-08-10 19:34:36 +02:00
HansUden17
a59881f3fb Translated using Weblate (Danish)
Currently translated at 50.0% (2 of 4 strings)

Translation: Jellyfin Android/Jellyfin Android TV Fastlane
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv-fastlane/da/
2023-08-10 12:34:16 -04:00
HansUden17
f6546cabf3 Translated using Weblate (Danish)
Currently translated at 100.0% (499 of 499 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/da/
2023-08-10 12:34:16 -04:00
Niels van Velzen
9ad2a1de6e Improve BaseRowItem refreshing
- Fix race conditions where selected item would change during refresh
- Catch API exceptions
- Remove item from adapter when refresh fails
- Format some files
2023-08-09 22:11:01 +02:00
renovate[bot]
abf3bb019c Update github/codeql-action action to v2.21.3 2023-08-09 22:10:48 +02:00
renovate[bot]
0fd188177a Update dependency androidx.preference:preference-ktx to v1.2.1 2023-08-09 21:44:31 +02:00
Kityn
c3320a3a0c Translated using Weblate (Polish)
Currently translated at 100.0% (499 of 499 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/pl/
2023-08-07 20:34:14 -04:00
Kityn
4c8eb6ee5b Translated using Weblate (Polish)
Currently translated at 100.0% (499 of 499 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/pl/
2023-08-07 17:34:14 -04:00
Manuel Triñanes
aaaaf3dffc Translated using Weblate (Spanish (Argentina))
Currently translated at 91.7% (458 of 499 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/es_AR/
2023-08-03 20:34:12 -04:00
Niels van Velzen
216d7ccabe Fix UI corruption when resuming the FullDetailsFragment 2023-08-03 20:49:50 +02:00
Alfonso Scarpino
1cdbcac1a0 Translated using Weblate (Italian)
Currently translated at 100.0% (499 of 499 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/it/
2023-08-03 11:34:12 -04:00
Niels van Velzen
60b3fa1e91 Add (PlaySession-)identifier to MediaStream (#2935)
* Get play session id from backend

* Add AudioMediaStreamResolver

* Rewrite MediaStreamService as MediaStreamState

* Use playSessionId in PlaySessionService

* Fix ExoPlayerBackend not always playing requested stream
2023-08-03 09:00:09 +02:00
Niels van Velzen
74e6e8c7e2 Fix crash when reusing actions in MyDetailsOverviewRowPresenter
This is a bit of a hack but the FullDetailsFragment is too messy to fix it properly right now...
2023-08-03 08:59:21 +02:00
Niels van Velzen
e81c14d975 Always use child fragment manager instead of activity fragment manager 2023-08-03 08:59:09 +02:00
Niels van Velzen
af725f6380 Fix wrong lifecycle state in NextUpFragment 2023-08-03 08:58:55 +02:00
luckkmaxx
1f009a11db Translated using Weblate (Spanish)
Currently translated at 100.0% (499 of 499 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/es/
2023-07-31 20:34:12 -04:00
Niels van Velzen
3f896e61f1 Fix crash when location type is null in CardPresenter 2023-07-31 16:32:32 +02:00
Lieba Natur Brilian
9bb2ced71d Translated using Weblate (Indonesian)
Currently translated at 100.0% (4 of 4 strings)

Translation: Jellyfin Android/Jellyfin Android TV Fastlane
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv-fastlane/id/
2023-07-31 08:34:11 -04:00
Niels van Velzen
daea0986c9 Use rounded corner for item details image 2023-07-30 22:38:08 +02:00
Niels van Velzen
f80c631252 Simplify defaultRewindAmount and defaultFastForwardAmount options 2023-07-30 15:52:10 +02:00
Niels van Velzen
9d60ca6a67 Add animation labels for Compose 2023-07-30 15:52:00 +02:00
renovate[bot]
479eec0937 Update dependency io.gitlab.arturbosch.detekt to v1.23.1 2023-07-30 09:50:26 +02:00
Niels van Velzen
9720179f44 Replace flow.collect usages with onEach & launchIn (#2925) 2023-07-30 09:49:14 +02:00
Niels van Velzen
b466a8d158 Don't clear ApiClient credentials when destroying a session
This fixes various crashes where API calls would fail after a session was destroyed. Most notable was the crash that occurred when stopping music playback when switching users
2023-07-30 09:48:56 +02:00
Niels van Velzen
49f5adadc6 Stop media playback when switching users
Previously we only stopped playback after the user is changed, with this change we also stop media immediately after usiong the switch users button in the home toolbar
2023-07-30 09:48:56 +02:00
renovate[bot]
7b0b345d8b Update dependency com.google.android.exoplayer:exoplayer to v2.19.0 2023-07-29 22:48:53 +02:00
Niels van Velzen
6cfcbb2b90 Add music progress to screensaver 2023-07-29 16:35:23 +02:00
renovate[bot]
f86458b303 Update github/codeql-action action to v2.21.2 2023-07-29 16:31:03 +02:00
renovate[bot]
b4d5ee4bed Update koin 2023-07-29 16:24:38 +02:00
Niels van Velzen
2d9bc7a918 Use data object in saled interfaces 2023-07-29 16:12:33 +02:00
Dennis
c5ec08c7f5 Translated using Weblate (Turkish)
Currently translated at 58.9% (294 of 499 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/tr/
2023-07-27 05:34:10 -04:00
renovate[bot]
a1b889a669 Update github/codeql-action action to v2.21.1 2023-07-26 22:19:30 +02:00
renovate[bot]
48bdefd995 Update dependency androidx.recyclerview:recyclerview to v1.3.1 2023-07-26 22:18:48 +02:00
renovate[bot]
4c00772cb9 Update dependency com.android.tools.build:gradle to v8.1.0 2023-07-26 22:14:56 +02:00
renovate[bot]
7a5c675764 Update dependency androidx.compose.compiler:compiler to v1.5.1 2023-07-26 22:13:58 +02:00
renovate[bot]
9c824d29d2 Update dependency androidx.fragment:fragment-ktx to v1.6.1 2023-07-26 22:13:47 +02:00
renovate[bot]
d3ab0d63fa Update Kotlin to v1.7.3 2023-07-26 09:22:13 +02:00
renovate[bot]
c1b6676f12 Update acra to v5.11.0 2023-07-25 21:03:45 +02:00
renovate[bot]
b4691e440d Update appleboy/ssh-action action to v1 2023-07-25 21:03:31 +02:00
renovate[bot]
22e912eadb Update CI dependencies 2023-07-25 21:03:15 +02:00
hoanghuy309
98fd3ef582 Translated using Weblate (Vietnamese)
Currently translated at 96.1% (480 of 499 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/vi/
2023-07-23 01:42:00 -04:00
Bas
44db5a042d Translated using Weblate (Dutch)
Currently translated at 100.0% (499 of 499 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/nl/
2023-07-22 10:41:59 -04:00
Achim Walz
a889055328 Translated using Weblate (German)
Currently translated at 100.0% (499 of 499 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/de/
2023-07-20 18:41:58 -04:00
The Re
13e5d61af7 Translated using Weblate (Hungarian)
Currently translated at 100.0% (499 of 499 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/hu/
2023-07-20 15:41:59 -04:00
Achim Walz
0a9a902c1a Translated using Weblate (German)
Currently translated at 100.0% (499 of 499 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/de/
2023-07-20 15:41:58 -04:00
Manuel Triñanes
5ee22bf93a Translated using Weblate (Spanish (Argentina))
Currently translated at 91.7% (458 of 499 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/es_AR/
2023-07-19 00:41:53 -04:00
Manuel Triñanes
7f230f7eab Translated using Weblate (Spanish)
Currently translated at 99.3% (496 of 499 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/es/
2023-07-19 00:41:52 -04:00
Manuel Triñanes
e174dca7db Translated using Weblate (Spanish (Mexico))
Currently translated at 85.1% (425 of 499 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/es_MX/
2023-07-19 00:41:52 -04:00
Moritz
9a98c2cadf Translated using Weblate (German)
Currently translated at 100.0% (499 of 499 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/de/
2023-07-18 14:41:52 -04:00
renovate[bot]
e1820e6e42 Update Kotlin to v1.9.0 2023-07-18 19:26:29 +02:00
renovate[bot]
c71c25245e Update dependency androidx.compose.compiler:compiler to v1.5.0 2023-07-18 19:26:12 +02:00
renovate[bot]
ed4e98e1ce Update aboutlibraries to v10.8.3 2023-07-18 16:11:47 +02:00
renovate[bot]
390f5eef54 Update gradle/gradle-build-action action to v2.6.1 2023-07-18 16:11:37 +02:00
Mohamed Akram
c69df824ce Improve codec info 2023-07-18 16:09:56 +02:00
Andi Chandler
4cb8b58f62 Translated using Weblate (English (United Kingdom))
Currently translated at 100.0% (499 of 499 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/en_GB/
2023-07-17 18:41:51 -04:00
Achim Walz
1b13a88a0f Translated using Weblate (German)
Currently translated at 99.5% (497 of 499 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/de/
2023-07-16 15:41:51 -04:00
Yaron Shahrabani
b168b0a95c Translated using Weblate (Hebrew)
Currently translated at 100.0% (4 of 4 strings)

Translation: Jellyfin Android/Jellyfin Android TV Fastlane
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv-fastlane/he/
2023-07-16 11:41:51 -04:00
Yaron Shahrabani
fb91f57d6b Translated using Weblate (Hebrew)
Currently translated at 100.0% (499 of 499 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/he/
2023-07-16 11:41:51 -04:00
Achim Walz
8fe2f7b301 Translated using Weblate (German)
Currently translated at 99.5% (497 of 499 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/de/
2023-07-15 16:41:51 -04:00
gleb
a43ef8d09f Translated using Weblate (Russian)
Currently translated at 100.0% (499 of 499 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/ru/
2023-07-15 10:41:52 -04:00
Achim Walz
c26971a002 Translated using Weblate (German)
Currently translated at 99.5% (497 of 499 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/de/
2023-07-15 10:41:52 -04:00
Niels van Velzen
407463bc3c Fix player background not black 2023-07-15 15:15:46 +02:00
renovate[bot]
36d2166e36 Update github/codeql-action action to v2.20.4 2023-07-14 20:10:59 +02:00
Gloria Boucher
69a4be1606 Translated using Weblate (Latvian)
Currently translated at 25.0% (1 of 4 strings)

Translation: Jellyfin Android/Jellyfin Android TV Fastlane
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv-fastlane/lv/
2023-07-14 09:41:50 -04:00
Niels van Velzen
c498cdc5db Migrate AccountManager accounts to AuthenticationStore 2023-07-13 14:12:55 +02:00
Niels van Velzen
e14515b9ee Remove AccountManager support 2023-07-13 14:12:55 +02:00
Oskari Lavinto
e7c3446c8d Translated using Weblate (Finnish)
Currently translated at 99.3% (496 of 499 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/fi/
2023-07-13 00:41:50 -04:00
Bas
1c1720170e Translated using Weblate (Dutch)
Currently translated at 100.0% (499 of 499 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/nl/
2023-07-12 18:41:49 -04:00
Achim Walz
4153e07b28 Translated using Weblate (German)
Currently translated at 98.7% (493 of 499 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/de/
2023-07-12 18:41:49 -04:00
stanol
dcb4ca7ef1 Translated using Weblate (Ukrainian)
Currently translated at 100.0% (499 of 499 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/uk/
2023-07-12 12:41:49 -04:00
Lukáš Kucharczyk
81dc474823 Translated using Weblate (Czech)
Currently translated at 100.0% (499 of 499 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/cs/
2023-07-12 07:41:49 -04:00
Niels van Velzen
2bbe3db035 Add preference to disable device type detection notification 2023-07-12 08:37:48 +02:00
Achim Walz
3f83c0ada9 Translated using Weblate (German)
Currently translated at 99.5% (493 of 495 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/de/
2023-07-11 20:41:49 -04:00
xuhao61
b86edeabd5 Translated using Weblate (Japanese)
Currently translated at 25.0% (1 of 4 strings)

Translation: Jellyfin Android/Jellyfin Android TV Fastlane
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv-fastlane/ja/
2023-07-11 15:41:49 -04:00
Niels van Velzen
387baf93b6 Replace Glide with Coil 2023-07-11 21:21:38 +02:00
renovate[bot]
40f186f4a0 Update aboutlibraries to v10.8.2 2023-07-11 14:34:48 +02:00
renovate[bot]
dcb4cecd09 Update dependency gradle to v8.2.1 2023-07-11 14:25:27 +02:00
renovate[bot]
473a84a6c7 Update gradle/gradle-build-action action to v2.6.0 2023-07-11 14:25:20 +02:00
hoanghuy309
bd9056cdd6 Translated using Weblate (Vietnamese)
Currently translated at 96.5% (478 of 495 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/vi/
2023-07-10 04:41:48 -04:00
Achim Walz
7c9041bd7c Translated using Weblate (German)
Currently translated at 99.5% (493 of 495 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/de/
2023-07-10 04:41:48 -04:00
Achim Walz
ed1764bec0 Translated using Weblate (German)
Currently translated at 99.5% (493 of 495 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/de/
2023-07-09 20:41:48 -04:00
Niels van Velzen
21fd753197 Fix application crash on startup 2023-07-09 14:56:03 +02:00
renovate[bot]
f85a81c01b Update aboutlibraries to v10.8.1 2023-07-08 21:50:31 +02:00
Achim Walz
19d0b7cc5b Translated using Weblate (German)
Currently translated at 99.3% (492 of 495 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/de/
2023-07-08 15:41:48 -04:00
Niels van Velzen
902d3a1026 Add missing view action to StartupActivity 2023-07-08 19:17:36 +02:00
Niels van Velzen
ba4966a0ac Remove unused ProcessLifecycleInitializer 2023-07-08 19:17:23 +02:00
Jack
8e4975d31d Translated using Weblate (French)
Currently translated at 100.0% (495 of 495 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/fr/
2023-07-08 09:41:48 -04:00
Achim Walz
6c303360ef Translated using Weblate (German)
Currently translated at 100.0% (495 of 495 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/de/
2023-07-08 09:41:48 -04:00
nightfurydev
00df9ee646 Translated using Weblate (Belarusian)
Currently translated at 8.4% (42 of 498 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/be/
2023-07-07 18:41:48 -04:00
renovate[bot]
375b625740 Update github/codeql-action action to v2.20.3 2023-07-06 21:58:30 +02:00
newton181
6bf75e0cc5 Translated using Weblate (Spanish (Latin America))
Currently translated at 100.0% (498 of 498 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/es_419/
2023-07-05 21:41:47 -04:00
Niels van Velzen
5760ca565b Update volume functions in MediaSessionPlayer 2023-07-05 21:04:16 +02:00
renovate[bot]
4ab1e33fbf Update dependency androidx.media3:media3-session to v1.1.0 2023-07-05 21:04:16 +02:00
Radvin-89
e8529619a5 Translated using Weblate (Persian)
Currently translated at 50.0% (2 of 4 strings)

Translation: Jellyfin Android/Jellyfin Android TV Fastlane
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv-fastlane/fa/
2023-07-04 12:41:46 -04:00
Niels van Velzen
1d0a40c7b5 Fix navigation reset called during back stack pop call 2023-07-03 21:06:45 +02:00
Niels van Velzen
6b5ae73f39 Improve logging in NavigationRepository 2023-07-03 21:06:45 +02:00
renovate[bot]
6b025580c6 Update github/codeql-action action to v2.20.2 2023-07-03 19:42:14 +02:00
Oskari Lavinto
9c6eacb579 Translated using Weblate (Finnish)
Currently translated at 100.0% (4 of 4 strings)

Translation: Jellyfin Android/Jellyfin Android TV Fastlane
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv-fastlane/fi/
2023-07-03 03:41:46 -04:00
renovate[bot]
38f7f0f559 Update dependency gradle to v8.2 2023-07-02 22:00:39 +02:00
Niels van Velzen
af1a069610 Show UI mode notification on startup screen 2023-07-01 15:33:23 +02:00
yosubis
81eed24a53 Translated using Weblate (Hebrew)
Currently translated at 50.0% (2 of 4 strings)

Translation: Jellyfin Android/Jellyfin Android TV Fastlane
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv-fastlane/he/
2023-07-01 08:41:45 -04:00
yosubis
eb882b8194 Translated using Weblate (Hebrew)
Currently translated at 99.7% (494 of 495 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/he/
2023-07-01 08:41:45 -04:00
renovate[bot]
76b534e9fc Update dependency com.squareup.leakcanary:leakcanary-android to v2.12 2023-06-30 08:49:06 +02:00
renovate[bot]
f399868da1 Update Kotlin to v1.7.2 2023-06-29 21:59:42 +02:00
renovate[bot]
8bca5a5ab5 Update gradle/gradle-build-action action to v2.5.1 2023-06-29 21:59:25 +02:00
renovate[bot]
a447ef5932 Update Kotlin 2023-06-29 10:03:16 +02:00
renovate[bot]
5af514dae8 Update dependency androidx.compose.compiler:compiler to v1.4.8 2023-06-29 10:03:07 +02:00
renovate[bot]
4c918d8689 Update gradle/gradle-build-action action to v2.5.0 2023-06-29 07:35:17 +02:00
avicarpio
403136fbc5 Translated using Weblate (Catalan)
Currently translated at 100.0% (498 of 498 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/ca/
2023-06-25 09:41:42 -04:00
renovate[bot]
b390f2d8aa Update dependency com.mikepenz.aboutlibraries.plugin to v10.8.0 2023-06-24 09:26:24 +02:00
Niels van Velzen
f18557934c Add more logging to authentication code 2023-06-22 07:17:01 +02:00
renovate[bot]
e1fd20c791 Update github/codeql-action action to v2.20.1 2023-06-21 20:56:42 +02:00
hoanghuy309
5946f2db0a Translated using Weblate (Vietnamese)
Currently translated at 93.9% (465 of 495 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/vi/
2023-06-21 14:28:18 -04:00
lucaperl
4fa39b23c0 Translated using Weblate (Italian)
Currently translated at 100.0% (495 of 495 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/it/
2023-06-21 08:28:18 -04:00
lucaperl
64757edbc6 Translated using Weblate (German)
Currently translated at 100.0% (495 of 495 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/de/
2023-06-21 08:28:18 -04:00
Niels van Velzen
b66afe5116 Fix crash when player controls are auto-hiding while closing the player 2023-06-20 18:52:09 +02:00
renovate[bot]
46ba09f5d6 Update acra to v5.10.1 2023-06-20 13:15:50 +02:00
Szávai Patrik
0856041800 Translated using Weblate (Hungarian)
Currently translated at 80.4% (398 of 495 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/hu/
2023-06-18 20:28:19 -04:00
renovate[bot]
4d3253744f Update acra to v5.10.0 2023-06-18 19:45:01 +02:00
renovate[bot]
5116114266 Update github/codeql-action action to v2.20.0 2023-06-16 21:56:42 +02:00
oad
7a26911460 Translated using Weblate (Slovak)
Currently translated at 100.0% (495 of 495 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/sk/
2023-06-11 13:21:57 -04:00
renovate[bot]
a27b5e1db1 Update actions/checkout action to v3.5.3 2023-06-11 15:24:55 +02:00
Niels van Velzen
ef51c87b0d Use fast forward & rewind durations from preferences 2023-06-08 07:13:06 +02:00
renovate[bot]
0d4572e1de Update dependency androidx.window:window to v1.1.0 2023-06-07 21:58:41 +02:00
renovate[bot]
187dcb5e96 Update dependency androidx.fragment:fragment-ktx to v1.6.0 2023-06-07 21:56:52 +02:00
Andi Chandler
e593ae3078 Translated using Weblate (English (United Kingdom))
Currently translated at 100.0% (495 of 495 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/en_GB/
2023-06-06 18:21:57 -04:00
newton181
2cc0e6d8da Translated using Weblate (Spanish (Latin America))
Currently translated at 99.3% (495 of 498 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/es_419/
2023-06-05 23:21:57 -04:00
newton181
179cdea522 Translated using Weblate (Spanish)
Currently translated at 100.0% (495 of 495 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/es/
2023-06-05 23:21:57 -04:00
renovate[bot]
c1e6cdae8b Update koin to v3.4.2 2023-06-05 22:35:41 +02:00
皇甫朝云
3f5d0dcc93 Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (495 of 495 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/zh_Hans/
2023-06-05 15:21:57 -04:00
millallo
abe5edac9e Translated using Weblate (Italian)
Currently translated at 100.0% (495 of 495 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/it/
2023-06-05 09:21:57 -04:00
hoanghuy309
03e46d3396 Translated using Weblate (Vietnamese)
Currently translated at 92.1% (456 of 495 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/vi/
2023-06-04 22:21:57 -04:00
Bas
16ea04abb0 Translated using Weblate (Dutch)
Currently translated at 100.0% (495 of 495 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/nl/
2023-06-04 08:21:57 -04:00
Oskari Lavinto
8ba6703645 Translated using Weblate (Finnish)
Currently translated at 99.3% (492 of 495 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/fi/
2023-06-04 05:21:57 -04:00
Niels van Velzen
94457195a9 Add volume state to playback rewrite 2023-06-04 10:40:17 +02:00
stanol
4e8415871c Translated using Weblate (Ukrainian)
Currently translated at 100.0% (495 of 495 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/uk/
2023-06-03 14:21:58 -04:00
Sami
297d369ce2 Translated using Weblate (Indonesian)
Currently translated at 70.1% (347 of 495 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/id/
2023-06-03 14:21:58 -04:00
Niv Harel
84e9ced5ad focus back on versions button when selecting a version 2023-06-03 16:49:44 +02:00
Lukáš Kucharczyk
2660bc3640 Translated using Weblate (Czech)
Currently translated at 100.0% (498 of 498 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/cs/
2023-06-03 09:21:57 -04:00
StubbyB
9657743a19 Fix InfoLayoutHelper not using first video stream, update details when selecting version (#2812) 2023-06-03 13:47:59 +02:00
Niels van Velzen
046917020d Fix Live TV progress reporting 2023-06-03 13:40:37 +02:00
Weblate
ad959a21cc Update translation files
Updated by "Cleanup translation files" hook in Weblate.

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/
2023-06-03 04:19:16 -04:00
Niels van Velzen
cc388fda33 Enable playback rewrite by default for music 2023-06-03 10:19:12 +02:00
Niels van Velzen
c7827b1935 Update unsupported server messages 2023-06-03 10:19:02 +02:00
Niels van Velzen
85a81ce9d5 Add repeat mode to playback rewrite 2023-06-03 10:18:52 +02:00
renovate[bot]
c4c46737ea Update github/codeql-action action to v2.3.6 2023-06-01 21:16:59 +02:00
Bucky Patel
d2ca1a2e45 Translated using Weblate (Hindi)
Currently translated at 11.6% (58 of 496 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/hi/
2023-06-01 05:21:57 -04:00
newton181
002ec0f449 Translated using Weblate (Spanish (Latin America))
Currently translated at 100.0% (496 of 496 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/es_419/
2023-05-31 21:21:57 -04:00
newton181
d6a0e8e754 Translated using Weblate (Spanish (Mexico))
Currently translated at 86.2% (428 of 496 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/es_MX/
2023-05-31 21:21:57 -04:00
renovate[bot]
385ce512ce Update koin 2023-05-31 22:18:35 +02:00
Niels van Velzen
4193ccc458 Rewrite media session code with androidx.media3 2023-05-31 22:17:45 +02:00
Niels van Velzen
8c85115a88 Disable custom key handling when using playback rewrite
This fixes a case where both the system and the app would act on key presses at the same time
2023-05-30 22:26:35 +02:00
Niels van Velzen
570f9ec4ba Fix race condition in HomeFragment causing a crash when profile picture loads when the view is already destroyed 2023-05-30 22:26:26 +02:00
DareFox
5022942327 Translated using Weblate (Russian)
Currently translated at 99.1% (492 of 496 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/ru/
2023-05-30 12:21:57 -04:00
Niels van Velzen
eb68aaae68 Convert queue service to core functionality 2023-05-30 08:57:37 +02:00
renovate[bot]
04cb64be8b Update github/codeql-action action to v2.3.5 2023-05-29 09:49:23 +02:00
Nikita Panuhin
6f94463e25 Translated using Weblate (Russian)
Currently translated at 75.6% (375 of 496 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/ru/
2023-05-28 07:21:57 -04:00
Xanto Chrome
2acacc2199 Translated using Weblate (Indonesian)
Currently translated at 25.0% (1 of 4 strings)

Translation: Jellyfin Android/Jellyfin Android TV Fastlane
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv-fastlane/id/
2023-05-27 12:21:57 -04:00
renovate[bot]
337e0ffe8c Update dependency com.android.tools.build:gradle to v8.0.2 2023-05-25 10:27:47 +02:00
renovate[bot]
1eb18eb18c Update dependency androidx.activity:activity-ktx to v1.7.2 2023-05-25 10:20:23 +02:00
renovate[bot]
f6d2421f6b Update github/codeql-action action to v2.3.4 2023-05-25 10:20:07 +02:00
renovate[bot]
737f9404ce Update dependency io.gitlab.arturbosch.detekt to v1.23.0 2023-05-24 09:26:35 +02:00
Niels van Velzen
6af461e2fb Add playback order support to playback rewrite 2023-05-21 22:12:59 +02:00
Niels van Velzen
49a326c74c Fix playback of seasons with external player 2023-05-21 22:12:38 +02:00
renovate[bot]
1d6d235bab Update aboutlibraries to v10.7.0 2023-05-20 09:57:41 +02:00
phynxlyncis
040a39e9f4 Translated using Weblate (Norwegian Bokmål)
Currently translated at 54.8% (272 of 496 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/nb_NO/
2023-05-19 23:21:43 -04:00
Niels van Velzen
0c85619990 Replace XML menus with Kotlin 2023-05-19 07:41:29 +02:00
Jack Koskie
6a38c52871 Translated using Weblate (French)
Currently translated at 100.0% (496 of 496 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/fr/
2023-05-19 01:21:43 -04:00
Niels van Velzen
49b8526338 Fix rare race condition crash in HomeFragment 2023-05-18 22:21:22 +02:00
Kenneth SB
8bf831d97b Translated using Weblate (Danish)
Currently translated at 100.0% (496 of 496 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/da/
2023-05-18 10:21:42 -04:00
Niels van Velzen
c2583d944b Fix BackgroundService flickering 2023-05-18 10:26:41 +02:00
Niels van Velzen
b2562f07c3 Enable Renovate dependency dashboard 2023-05-18 10:26:13 +02:00
Niels van Velzen
59472ba7b0 Remove left over search hint code 2023-05-18 10:25:55 +02:00
Niels van Velzen
61324816d7 Fix PictureViewerFragment using activityViewModel 2023-05-18 10:25:29 +02:00
renovate[bot]
76695a0829 Update dependency com.squareup.leakcanary:leakcanary-android to v2.11 2023-05-18 10:25:04 +02:00
renovate[bot]
79c80198c1 Update dependency org.jellyfin.exoplayer:exoplayer-ffmpeg-extension to v2.18.7+1 2023-05-18 09:37:19 +02:00
adelao95
d28cc1739e Translated using Weblate (Spanish (Latin America))
Currently translated at 97.9% (486 of 496 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/es_419/
2023-05-17 20:21:43 -04:00
renovate[bot]
fd16bf51b8 Update dependency com.google.android.exoplayer:exoplayer to v2.18.7 2023-05-17 21:06:51 +02:00
Niels van Velzen
369c5bfd39 Update formatting, fix some minor issues 2023-05-17 12:43:06 +02:00
polson
4104612977 Use dependency injection and clean up code 2023-05-17 12:43:06 +02:00
polson
26fb739e39 Cancel search requests when search box input changes 2023-05-17 12:43:06 +02:00
newton181
ad132306da Translated using Weblate (Spanish (Latin America))
Currently translated at 92.9% (461 of 496 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/es_419/
2023-05-15 23:21:53 -04:00
luckkmaxx
ee16e5832e Translated using Weblate (Spanish)
Currently translated at 100.0% (496 of 496 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/es/
2023-05-15 23:21:51 -04:00
Bad Developer
af7fb35e51 Translated using Weblate (Uyghur)
Currently translated at 19.9% (99 of 496 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/ug/
2023-05-15 04:21:42 -04:00
Niels van Velzen
894ca60899 Use view binding in MainActivity & StartupActivity 2023-05-14 10:54:46 +02:00
Niels van Velzen
9b00839a2f Fix view binding leak in fragments 2023-05-14 10:54:35 +02:00
Niels van Velzen
11251d5185 Migrate some activity usages to context 2023-05-13 19:06:08 +02:00
Niels van Velzen
d7561b044a Use context in ItemLauncher 2023-05-13 18:32:11 +02:00
Niels van Velzen
82e09e147a Use navigation.goBack instead of activity finish in FullDetailsFragment 2023-05-13 10:09:02 +02:00
Niels van Velzen
e1066911cb Inline validateAuthentication extension into MainActivity 2023-05-13 10:08:54 +02:00
Niels van Velzen
2e4aa21db2 Fix app closing when media finishes playing 2023-05-12 22:09:57 +02:00
renovate[bot]
c1c08e8079 Update Kotlin to v1.7.1 2023-05-12 21:53:43 +02:00
Niels van Velzen
2d4adbf6a2 Migrate ExoPlayer to StyledPlayerView 2023-05-12 20:34:44 +02:00
Niels van Velzen
00b24be8cc Add fallback image to LeanbackChannelWorker 2023-05-11 22:55:28 +02:00
renovate[bot]
ffa6f1f9fb Update Kotlin 2023-05-11 17:56:41 +02:00
renovate[bot]
d29e8e8e0a Update kotest to v5.6.2 2023-05-11 08:37:42 +02:00
renovate[bot]
b1c76d2327 Update dependency androidx.core:core-ktx to v1.10.1 2023-05-10 21:32:37 +02:00
Niels van Velzen
c95ee3ab9b Fix screensaver support in video player 2023-05-10 21:30:10 +02:00
Niels van Velzen
12a24f3dd0 Migrate video player & next up screen to fragments 2023-05-10 09:07:26 +02:00
Niels van Velzen
10950e2689 Support replacement during navigation 2023-05-10 09:07:26 +02:00
Niels van Velzen
c17cc972e4 Remove NextUp screen from ExternalPlayerActivity 2023-05-10 09:07:26 +02:00
Niels van Velzen
a69e0748d8 Fix HomeFragmentLatestRow using wrong import for runBlocking function 2023-05-10 08:41:43 +02:00
Niels van Velzen
c579a1259e Remove old screen locks 2023-05-10 08:41:27 +02:00
liimee
844e914611 Translated using Weblate (Indonesian)
Currently translated at 58.0% (288 of 496 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/id/
2023-05-06 22:39:43 -04:00
Niels van Velzen
05651d37d2 Hide SSA tags from SRT subtitles 2023-05-06 19:28:47 +02:00
hoanghuy309
c7999e83a3 Translated using Weblate (Vietnamese)
Currently translated at 87.9% (436 of 496 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/vi/
2023-05-06 11:39:42 -04:00
liimee
e710ceb2dc Translated using Weblate (Indonesian)
Currently translated at 25.0% (1 of 4 strings)

Translation: Jellyfin Android/Jellyfin Android TV Fastlane
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv-fastlane/id/
2023-05-05 23:39:42 -04:00
liimee
faba2fd40f Translated using Weblate (Indonesian)
Currently translated at 42.5% (211 of 496 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/id/
2023-05-05 23:39:42 -04:00
renovate[bot]
39593eb7ed Update github/codeql-action action to v2.3.3 2023-05-04 21:31:12 +02:00
Marcinbar
531221b3fb Translated using Weblate (Polish)
Currently translated at 100.0% (4 of 4 strings)

Translation: Jellyfin Android/Jellyfin Android TV Fastlane
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv-fastlane/pl/
2023-05-04 06:39:41 -04:00
Marcinbar
de7b2dee82 Translated using Weblate (Polish)
Currently translated at 99.7% (495 of 496 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/pl/
2023-05-04 06:39:41 -04:00
renovate[bot]
a55bd51bbb Update aboutlibraries to v10.6.3 2023-05-04 08:52:04 +02:00
renovate[bot]
70a974bc0f Update dependency androidx.compose.ui:ui-tooling to v1.4.3 2023-05-03 22:21:17 +02:00
renovate[bot]
ba38cb654e Update dependency androidx.compose.material:material to v1.4.3 2023-05-03 22:13:55 +02:00
renovate[bot]
83f9b15803 Update dependency androidx.compose.foundation:foundation to v1.4.3 2023-05-03 22:07:37 +02:00
renovate[bot]
7e46ec2c84 Update Kotlin 2023-05-03 21:57:58 +02:00
renovate[bot]
a93d671573 Update dependency androidx.compose.compiler:compiler to v1.4.7 2023-05-03 21:57:50 +02:00
Niels van Velzen
c437842209 Fix resume row not hiding when empty 2023-05-02 17:11:44 +02:00
Chen-Tai,Peng
b3a2769692 Translated using Weblate (Chinese (Traditional))
Currently translated at 75.0% (3 of 4 strings)

Translation: Jellyfin Android/Jellyfin Android TV Fastlane
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv-fastlane/zh_Hant/
2023-05-01 14:39:40 -04:00
renovate[bot]
4eaf70bff3 Update dependency com.android.tools.build:gradle to v8.0.1 2023-05-01 19:17:31 +02:00
ZlabiDev
b101dd1e44 Translated using Weblate (German)
Currently translated at 100.0% (496 of 496 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/de/
2023-05-01 04:39:40 -04:00
Niels van Velzen
1d44c83501 Add support for retrieving resume items in BaseRowAdapter using the SDK 2023-04-30 16:30:12 +02:00
renovate[bot]
f808810889 Update github/codeql-action action to v2.3.2 2023-04-29 15:33:22 +02:00
Lukáš Kucharczyk
c39e2cad5b Translated using Weblate (Czech)
Currently translated at 99.3% (493 of 496 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/cs/
2023-04-28 05:39:39 -04:00
Jan
52e237349c Translated using Weblate (German)
Currently translated at 100.0% (4 of 4 strings)

Translation: Jellyfin Android/Jellyfin Android TV Fastlane
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv-fastlane/de/
2023-04-27 16:39:39 -04:00
Niels van Velzen
1d91af6956 Allow changing the (a)ac3 bitstreaming option on all devices 2023-04-27 19:35:46 +02:00
renovate[bot]
936356f835 Update dependency org.jellyfin.exoplayer:exoplayer-ffmpeg-extension to v2.18.6+1 2023-04-27 15:35:42 +02:00
Niels van Velzen
7d33516c31 Fix ExoPlayer extensions not used for music playback 2023-04-27 15:24:02 +02:00
Niels van Velzen
991447f797 Fix ExoPlayerProfile not always listing SubRip and SRT as external 2023-04-27 15:23:39 +02:00
Niels van Velzen
3d5142c108 Fix ExpandableTextView cutting content 2023-04-27 15:23:21 +02:00
renovate[bot]
70a17230de Update github/codeql-action action to v2.3.1 2023-04-27 10:40:58 +02:00
renovate[bot]
84efc6a693 Update dependency org.jellyfin.sdk:jellyfin-core to v1.4.2 2023-04-26 21:58:02 +02:00
Gselivanof
ccdeb8e44f Translated using Weblate (Greek)
Currently translated at 100.0% (496 of 496 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/el/
2023-04-24 16:39:38 -04:00
shakedex
1fd005e256 Translated using Weblate (Hebrew)
Currently translated at 98.7% (490 of 496 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/he/
2023-04-24 10:39:38 -04:00
hoanghuy309
917fc63e9e Translated using Weblate (Vietnamese)
Currently translated at 80.4% (399 of 496 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/vi/
2023-04-23 16:39:37 -04:00
Min3
e09dc5ded4 Translated using Weblate (Catalan)
Currently translated at 100.0% (496 of 496 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/ca/
2023-04-23 12:39:38 -04:00
Niels van Velzen
571028e967 Disable MPEG transport stream container in ExoPlayer
The track switching code (for audio/subtitle) implementation for ExoPlayer doesn't support the track ids in a mpeg-ts container
2023-04-23 14:43:12 +02:00
Niels van Velzen
93979e1d38 Add DVB subtitle support in ExoPlayer 2023-04-23 14:42:39 +02:00
皇甫朝云
dfe2e9e0c4 Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (4 of 4 strings)

Translation: Jellyfin Android/Jellyfin Android TV Fastlane
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv-fastlane/zh_Hans/
2023-04-23 07:39:37 -04:00
alison2033
596f873e06 Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (496 of 496 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/pt_BR/
2023-04-23 01:39:37 -04:00
Niels van Velzen
45bdb655bc Remove BaseProfile 2023-04-22 16:10:49 +02:00
Niels van Velzen
d63ac02525 Enable PGS subtitles in ExoPlayer 2023-04-22 16:10:36 +02:00
Niels van Velzen
e903d8c347 Fix switching ExoPlayer subtitle track 2023-04-22 16:10:23 +02:00
Niels van Velzen
f862a17223 Fix offsetStreamIndex when selecting last track 2023-04-22 16:09:39 +02:00
Andreas Maier
9b5050b3fa Translated using Weblate (German)
Currently translated at 99.5% (494 of 496 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/de/
2023-04-22 07:39:38 -04:00
renovate[bot]
058685d0c2 Update github/codeql-action action to v2.3.0 2023-04-22 09:36:19 +02:00
renovate[bot]
eac2cc3761 Update dependency gradle to v8.1.1 2023-04-21 20:27:35 +02:00
Andi Chandler
a419be52fa Translated using Weblate (English (United Kingdom))
Currently translated at 100.0% (496 of 496 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/en_GB/
2023-04-20 18:39:36 -04:00
renovate[bot]
207615c5c3 Update dependency androidx.compose.material:material to v1.4.2 2023-04-20 11:27:26 +02:00
renovate[bot]
4699454d5d Update dependency com.google.android.exoplayer:exoplayer to v2.18.6 2023-04-20 11:06:25 +02:00
renovate[bot]
2cf3016889 Update dependency androidx.compose.ui:ui-tooling to v1.4.2 2023-04-20 11:05:47 +02:00
renovate[bot]
d0d59a5d1b Update dependency androidx.compose.foundation:foundation to v1.4.2 2023-04-20 11:04:58 +02:00
renovate[bot]
2b5c229e10 Update dependency androidx.activity:activity-ktx to v1.7.1 2023-04-20 10:50:50 +02:00
stanol
61f61f4ea6 Translated using Weblate (Ukrainian)
Currently translated at 100.0% (4 of 4 strings)

Translation: Jellyfin Android/Jellyfin Android TV Fastlane
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv-fastlane/uk/
2023-04-19 18:39:36 -04:00
stanol
c23f3a064c Translated using Weblate (Ukrainian)
Currently translated at 100.0% (496 of 496 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/uk/
2023-04-19 18:39:36 -04:00
Niels van Velzen
9a77e63fa9 Rewrite GridButton with Compose 2023-04-19 19:29:39 +02:00
renovate[bot]
56a8c7d59b Update dependency androidx.fragment:fragment-ktx to v1.5.7 2023-04-19 19:20:46 +02:00
renovate[bot]
fcb2094541 Update dependency androidx.compose.compiler:compiler to v1.4.6 2023-04-19 19:12:58 +02:00
nextlooper42
8c7228d5c8 Translated using Weblate (Slovak)
Currently translated at 100.0% (496 of 496 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/sk/
2023-04-19 10:39:36 -04:00
Bas
066895bd4a Translated using Weblate (Dutch)
Currently translated at 100.0% (496 of 496 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/nl/
2023-04-19 10:39:36 -04:00
renovate[bot]
4dfaaab8af Update dependency com.google.devtools.ksp to v1.8.20-1.0.11 2023-04-19 08:59:50 +02:00
renovate[bot]
edd7afc36f Update kotest to v5.6.1 2023-04-19 08:59:34 +02:00
renovate[bot]
61b1fc73ee Update dependency io.insert-koin:koin-androidx-compose to v3.4.4 2023-04-19 08:40:34 +02:00
Alehaaaa
5c09724dd4 Translated using Weblate (Spanish)
Currently translated at 100.0% (496 of 496 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/es/
2023-04-17 19:39:35 -04:00
Troja
34511b5777 Translated using Weblate (Belarusian)
Currently translated at 6.0% (30 of 496 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/be/
2023-04-17 15:39:36 -04:00
stanol
40d9b2a70b Translated using Weblate (Ukrainian)
Currently translated at 99.7% (495 of 496 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/uk/
2023-04-17 15:39:36 -04:00
Simon Hung
655555c580 Translated using Weblate (Chinese (Traditional))
Currently translated at 100.0% (496 of 496 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/zh_Hant/
2023-04-17 15:39:36 -04:00
Ulices
10eb71b8c0 Translated using Weblate (Spanish (Latin America))
Currently translated at 72.1% (358 of 496 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/es_419/
2023-04-17 05:39:35 -04:00
小造xu_zh
c1b251aff2 Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (496 of 496 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/zh_Hans/
2023-04-17 05:39:35 -04:00
Alerego
e6898487dc Translated using Weblate (Italian)
Currently translated at 99.7% (495 of 496 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/it/
2023-04-16 19:39:35 -04:00
Oskari Lavinto
2f9c2149d9 Translated using Weblate (Finnish)
Currently translated at 99.3% (493 of 496 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/fi/
2023-04-16 07:39:34 -04:00
Niels van Velzen
a3e845db3a Add separate buttons for artists and album artists in music smart screen 2023-04-16 10:30:49 +02:00
Ac_K
e67ddbc030 Translated using Weblate (French)
Currently translated at 100.0% (4 of 4 strings)

Translation: Jellyfin Android/Jellyfin Android TV Fastlane
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv-fastlane/fr/
2023-04-16 01:39:35 -04:00
Ac_K
dd98ba4888 Translated using Weblate (French)
Currently translated at 100.0% (495 of 495 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/fr/
2023-04-16 01:39:34 -04:00
Kasper Kirkegaard
94417cab4e Translated using Weblate (Danish)
Currently translated at 100.0% (495 of 495 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/da/
2023-04-15 19:39:34 -04:00
Andi Chandler
ee3c72a16d Translated using Weblate (English (United Kingdom))
Currently translated at 100.0% (495 of 495 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/en_GB/
2023-04-15 11:39:34 -04:00
Niels van Velzen
f99451d22e Fix app crash when playback stop is requested without an active player 2023-04-15 16:01:35 +02:00
renovate[bot]
d5a3bae29f Update gradle/gradle-build-action action to v2.4.2 2023-04-15 09:08:38 +02:00
小造xu_zh
e93b788ab3 Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (495 of 495 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/zh_Hans/
2023-04-15 01:39:34 -04:00
newton181
46ebb30a6a Translated using Weblate (Spanish)
Currently translated at 98.3% (487 of 495 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/es/
2023-04-14 21:39:34 -04:00
Niels van Velzen
c1dba246c8 Enable nonTransitiveRClass 2023-04-14 19:27:43 +02:00
Robert
f77e281142 Translated using Weblate (Romanian)
Currently translated at 100.0% (4 of 4 strings)

Translation: Jellyfin Android/Jellyfin Android TV Fastlane
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv-fastlane/ro/
2023-04-14 08:39:34 -04:00
Robert
27a4ba75a4 Translated using Weblate (Romanian)
Currently translated at 100.0% (494 of 494 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/ro/
2023-04-14 08:39:34 -04:00
Niels van Velzen
d28fa5db69 Enable nonFinalResIds 2023-04-14 08:37:12 +02:00
Eryk Michalak
15ccc6b930 Translated using Weblate (Polish)
Currently translated at 100.0% (494 of 494 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/pl/
2023-04-13 17:39:34 -04:00
Niels van Velzen
c4678b8863 Temporarily disable nonFinalResIds and nonTransitiveRClass 2023-04-13 22:21:18 +02:00
Niels van Velzen
a237c4205e Enable buildConfig for app module 2023-04-13 22:21:18 +02:00
renovate[bot]
8290ef854a Update dependency com.android.tools.build:gradle to v8 2023-04-13 22:21:18 +02:00
David Fairbrother
4bb2158cc0 Use JDK toolchains to explicitly use JDK 17
Explicitly mark that we want version 17 of the JDK as the compiler. Previously
we implicitly relied on Gradle getting this right. This means devs and
CI will all use the same JDK explicitly rather than implicitly.

Pin the target JDK version in build.gradle.kts, this should apply to all
targets (including Kotlin) to version 1.8 to maintain compatibility with
older devices
2023-04-13 21:56:26 +02:00
renovate[bot]
45e7e6456c Update CI dependencies 2023-04-13 21:50:40 +02:00
Eryk Michalak
613463bef6 Translated using Weblate (Polish)
Currently translated at 78.9% (390 of 494 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/pl/
2023-04-13 15:16:45 -04:00
renovate[bot]
74c1494fef Update dependency io.mockk:mockk to v1.13.5 2023-04-13 10:51:19 +02:00
renovate[bot]
35bff80438 Update CI dependencies 2023-04-13 10:50:41 +02:00
renovate[bot]
d3d25cc1b7 Update dependency gradle to v8.1 2023-04-13 10:50:23 +02:00
renovate[bot]
dce6d94fee Update Kotlin 2023-04-13 10:27:12 +02:00
renovate[bot]
4c778eeaf8 Update dependency androidx.compose.compiler:compiler to v1.4.5 2023-04-13 10:27:03 +02:00
Niels van Velzen
775aa5ad69 Fix BaseItemInfoRow not showing for persons 2023-04-13 08:40:08 +02:00
Flibble
7a64fd39fd Translated using Weblate (Czech)
Currently translated at 99.7% (493 of 494 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/cs/
2023-04-12 09:39:33 -04:00
Kasper Kirkegaard
0b33b20174 Translated using Weblate (Danish)
Currently translated at 97.5% (482 of 494 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/da/
2023-04-11 20:39:33 -04:00
Alexandre Barde
76dd6e8910 Translated using Weblate (French)
Currently translated at 100.0% (4 of 4 strings)

Translation: Jellyfin Android/Jellyfin Android TV Fastlane
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv-fastlane/fr/
2023-04-10 18:39:32 -04:00
Alexandre Barde
f406a21477 Translated using Weblate (French)
Currently translated at 86.0% (425 of 494 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/fr/
2023-04-10 18:39:32 -04:00
DJSweder
1036bf59e6 Translated using Weblate (Czech)
Currently translated at 79.3% (392 of 494 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/cs/
2023-04-09 01:39:32 -04:00
Edgar Pireyn
ad359df5aa Translated using Weblate (French)
Currently translated at 84.0% (415 of 494 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/fr/
2023-04-08 15:39:31 -04:00
renovate[bot]
45e3689a26 Update aboutlibraries to v10.6.2 2023-04-08 14:29:29 +02:00
stanol
e578d7fdec Translated using Weblate (Ukrainian)
Currently translated at 99.7% (493 of 494 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/uk/
2023-04-07 19:39:31 -04:00
Bas
e5fdf4ef0c Translated using Weblate (Dutch)
Currently translated at 100.0% (494 of 494 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/nl/
2023-04-07 13:39:31 -04:00
Niels van Velzen
6f9d8b9fc7 Rewrite background service UI with compose 2023-04-07 19:13:57 +02:00
Niels van Velzen
3380d62960 Fix BaseRowItem index not set 2023-04-07 14:39:41 +02:00
Niels van Velzen
56c43b7059 Fix audio queue row showing previous items 2023-04-07 14:39:41 +02:00
Niels van Velzen
12e23c0bf1 Fix now playing row in home fragment not working with rewrite media manager 2023-04-07 14:39:41 +02:00
Andi Chandler
7b9b85491c Translated using Weblate (English (United Kingdom))
Currently translated at 100.0% (494 of 494 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/en_GB/
2023-04-07 08:39:31 -04:00
Oskari Lavinto
04c3c2e02e Translated using Weblate (Finnish)
Currently translated at 99.3% (491 of 494 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/fi/
2023-04-07 04:39:31 -04:00
wolong gl
bc55d7b56a Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (494 of 494 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/zh_Hans/
2023-04-07 04:39:31 -04:00
renovate[bot]
f7213dae2c Update github/codeql-action action to v2.2.11 2023-04-06 21:47:54 +02:00
Niels van Velzen
99373ef595 Add (experimental) reactive homepage 2023-04-06 18:23:15 +02:00
Niels van Velzen
a229c2e3dc Use ItemRowAdapterHelperKt.setItems for NextUpQuery in ItemRowAdapter 2023-04-06 18:22:59 +02:00
Oskari Lavinto
20f4fa4edd Translated using Weblate (Finnish)
Currently translated at 100.0% (4 of 4 strings)

Translation: Jellyfin Android/Jellyfin Android TV Fastlane
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv-fastlane/fi/
2023-04-06 00:39:30 -04:00
renovate[bot]
0947a261f8 Update dependency androidx.core:core-ktx to v1.10.0 2023-04-05 21:02:21 +02:00
renovate[bot]
3b120b3675 Update dependency androidx.compose.ui:ui-tooling to v1.4.1 2023-04-05 20:44:03 +02:00
renovate[bot]
05031a64c3 Update dependency androidx.compose.foundation:foundation to v1.4.1 2023-04-05 20:43:55 +02:00
AmmarAlhawsawi
8c118840ac Translated using Weblate (Arabic)
Currently translated at 100.0% (4 of 4 strings)

Translation: Jellyfin Android/Jellyfin Android TV Fastlane
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv-fastlane/ar/
2023-04-05 14:39:30 -04:00
renovate[bot]
a5f7b5cab9 Update dependency androidx.compose.material:material to v1.4.1 2023-04-05 20:33:33 +02:00
renovate[bot]
d593ae9f21 Update github/codeql-action action to v2.2.10 2023-04-05 19:58:58 +02:00
Troja
93e8708006 Translated using Weblate (Belarusian)
Currently translated at 5.0% (25 of 493 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/be/
2023-04-05 08:39:30 -04:00
Jorge San
98e50dacc5 Translated using Weblate (Spanish)
Currently translated at 98.5% (486 of 493 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/es/
2023-04-05 05:39:31 -04:00
Hawasys
e4ae04e3c0 Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (493 of 493 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/pt_BR/
2023-04-04 15:39:30 -04:00
iDoNtCaRe-69
13b9e4719e Translated using Weblate (Hindi)
Currently translated at 9.9% (49 of 493 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/hi/
2023-04-04 10:39:31 -04:00
renovate[bot]
4c9ed637ed Update appleboy/ssh-action action to v0.1.9 2023-04-04 14:41:01 +02:00
Niels van Velzen
98c5700b66 Set Java versions for Kotlin and Android plugin 2023-04-04 14:38:33 +02:00
renovate[bot]
df1c20138b Update dependency gradle to v8 2023-04-04 14:38:33 +02:00
iDoNtCaRe-69
7fb40055d4 Added translation using Weblate (Hindi) 2023-04-04 07:55:55 -04:00
Yan
ef5f800c66 Improve Android TV home screen recommendations (#2515)
* Add more channels

* Merge my_media channel logic into generic channel logic

* Implement selection of home screen channel and prefer backdrops option

* Add PreferredDefaultChannelData.NONE for no recommendations

* Remove leanbackPreferredDefaultChannel option

* Change default channel to latest_media

* fixup - set default channel to latest media

The first channel your app creates becomes its default channel. The default channel automatically appears in the home screen. All other channels you create must be selected and accepted by the user before they can appear in the home screen.

* Remove option to prefer backdrops

* Remove leanback related strings

* remove unused imports in CustomizationPreferencesScreen.kt

* Small formatting changes, bugfixes and code cleanup

---------

Co-authored-by: Niels van Velzen <git@ndat.nl>
2023-04-04 11:49:43 +00:00
AmmarAlhawsawi
8cf2115e7c Translated using Weblate (Arabic)
Currently translated at 100.0% (4 of 4 strings)

Translation: Jellyfin Android/Jellyfin Android TV Fastlane
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv-fastlane/ar/
2023-04-03 18:39:30 -04:00
AmmarAlhawsawi
1c4e8f0d11 Translated using Weblate (Arabic)
Currently translated at 100.0% (493 of 493 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/ar/
2023-04-03 18:39:30 -04:00
Adil
58b5a756ba Translated using Weblate (Turkish)
Currently translated at 25.0% (1 of 4 strings)

Translation: Jellyfin Android/Jellyfin Android TV Fastlane
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv-fastlane/tr/
2023-04-03 15:39:30 -04:00
Adil
ec796e5f5c Translated using Weblate (Turkish)
Currently translated at 56.9% (281 of 493 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/tr/
2023-04-03 15:39:30 -04:00
Niels van Velzen
1b44358f10 Add F-Droid badge to README 2023-04-03 20:55:48 +02:00
Niels van Velzen
3bfa126e0e Fix "null" on Live TV guide "load channels" button 2023-04-03 19:34:44 +02:00
Niels van Velzen
fb651b173c Set screenOrientation for PreferencesActivity 2023-04-02 10:05:09 +02:00
Niels van Velzen
1b3418908c Translated using Weblate (Belarusian)
Currently translated at 75.0% (3 of 4 strings)

Translation: Jellyfin Android/Jellyfin Android TV Fastlane
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv-fastlane/be/
2023-04-01 12:39:29 -04:00
Niels van Velzen
e9340f8e3e Translated using Weblate (Norwegian Bokmål)
Currently translated at 75.0% (3 of 4 strings)

Translation: Jellyfin Android/Jellyfin Android TV Fastlane
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv-fastlane/nb_NO/
2023-04-01 12:39:29 -04:00
renovate[bot]
e68cd1e755 Update dependency com.android.tools:desugar_jdk_libs to v2.0.3 2023-03-31 18:36:13 +02:00
Bas
2560d95fb6 Translated using Weblate (Dutch)
Currently translated at 100.0% (493 of 493 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/nl/
2023-03-31 10:39:29 -04:00
Juozapas
7c3244f61b Translated using Weblate (Lithuanian)
Currently translated at 85.8% (423 of 493 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/lt/
2023-03-30 17:39:29 -04:00
TheTach
1327da35fb Translated using Weblate (German)
Currently translated at 100.0% (493 of 493 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/de/
2023-03-30 17:39:28 -04:00
Oskari Lavinto
feebf3632e Translated using Weblate (Finnish)
Currently translated at 100.0% (4 of 4 strings)

Translation: Jellyfin Android/Jellyfin Android TV Fastlane
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv-fastlane/fi/
2023-03-30 14:39:28 -04:00
Bas
189f02c17b Translated using Weblate (Dutch)
Currently translated at 100.0% (493 of 493 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/nl/
2023-03-30 14:39:28 -04:00
Troja
6928cd051a Translated using Weblate (Belarusian)
Currently translated at 75.0% (3 of 4 strings)

Translation: Jellyfin Android/Jellyfin Android TV Fastlane
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv-fastlane/be/
2023-03-30 11:39:29 -04:00
Drembii
e8a9a84f2a Translated using Weblate (Romanian)
Currently translated at 82.9% (409 of 493 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/ro/
2023-03-30 11:39:28 -04:00
Drembii
0ea88e92f3 Translated using Weblate (Romanian)
Currently translated at 100.0% (4 of 4 strings)

Translation: Jellyfin Android/Jellyfin Android TV Fastlane
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv-fastlane/ro/
2023-03-30 08:39:29 -04:00
Drembii
b52ca81713 Translated using Weblate (Romanian)
Currently translated at 78.7% (388 of 493 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/ro/
2023-03-30 08:39:28 -04:00
Drembii
723b8f6e46 Translated using Weblate (Romanian)
Currently translated at 50.0% (2 of 4 strings)

Translation: Jellyfin Android/Jellyfin Android TV Fastlane
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv-fastlane/ro/
2023-03-30 05:39:29 -04:00
Drembii
93096677c6 Translated using Weblate (Romanian)
Currently translated at 70.5% (348 of 493 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/ro/
2023-03-30 05:39:28 -04:00
renovate[bot]
c29703c09c Update CI dependencies 2023-03-28 13:24:41 +02:00
n*!
c9397c484b Translated using Weblate (French)
Currently translated at 77.0% (380 of 493 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/fr/
2023-03-27 18:39:27 -04:00
Bas
8d065337c2 Translated using Weblate (Dutch)
Currently translated at 100.0% (493 of 493 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/nl/
2023-03-27 08:39:27 -04:00
Niels van Velzen
25e8738818 Fix app closing when music queue ends 2023-03-25 14:11:10 +01:00
Bas
57a5344265 Translated using Weblate (Dutch)
Currently translated at 100.0% (493 of 493 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/nl/
2023-03-24 19:39:26 -04:00
renovate[bot]
02f730094f Update dependency androidx.compose.material:material to v1.4.0 2023-03-24 21:09:34 +01:00
renovate[bot]
b6cfaed0d1 Update dependency androidx.compose.foundation:foundation to v1.4.0 2023-03-24 21:07:59 +01:00
renovate[bot]
3d3df83ac7 Update dependency androidx.compose.ui:ui-tooling to v1.4.0 2023-03-24 21:07:40 +01:00
renovate[bot]
8c6d66a2c8 Update koin 2023-03-24 19:39:37 +01:00
renovate[bot]
6f77374260 Update actions/checkout action to v3.5.0 2023-03-24 19:39:05 +01:00
renovate[bot]
526a84c667 Update dependency androidx.compose.compiler:compiler to v1.4.4 2023-03-23 20:50:17 +01:00
renovate[bot]
de27b394d2 Update dependency androidx.fragment:fragment-ktx to v1.5.6 2023-03-23 20:37:42 +01:00
renovate[bot]
3a510ae811 Update dependency androidx.activity:activity-ktx to v1.7.0 2023-03-23 20:37:33 +01:00
renovate[bot]
f51723ae6b Update dependency androidx.work:work-runtime-ktx to v2.8.1 2023-03-23 20:37:21 +01:00
renovate[bot]
55b6e4f23b Update androidx-lifecycle to v2.6.1 2023-03-23 20:37:10 +01:00
renovate[bot]
b292319a44 Update github/codeql-action action to v2.2.8 2023-03-23 20:36:27 +01:00
renovate[bot]
4d99dd1a17 Update actions/stale action to v8 2023-03-23 20:36:19 +01:00
renovate[bot]
341dc3c3b7 Update dependency com.google.android.exoplayer:exoplayer to v2.18.5 2023-03-23 20:36:11 +01:00
Andi Chandler
82b6044cc4 Translated using Weblate (English (United Kingdom))
Currently translated at 100.0% (493 of 493 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/en_GB/
2023-03-18 20:39:24 -04:00
David Fairbrother
34a9c18a75 Fix formatting across actions
These were picked up during the PR review, and represent a nice tidy-up
across the actions
2023-03-18 11:55:58 +01:00
David Fairbrother
fb1ba84130 Mark get*Fragment as NonNullable for Kotlin interop
Based on liner feedback, mark these as NonNullable, since the actions
videoPlayerAdapter shouldn't have any callers whilst tearing down the
various fragments
2023-03-18 11:55:58 +01:00
David Fairbrother
d0175cc3e6 Move logic for Android actions into own classes
This makes the default actions also use polymorphic dispatch, rather
than the existing static dispatch.

In future commits we can move the associated logic (e.g.
updatePlayState()) into the same classes too. This will effectively
separate our logic from our view and make it clear where the
responsibilities for each element are.
2023-03-18 11:55:58 +01:00
David Fairbrother
b1ac564f6e Grab LeanbackFragment through videoAdapter
Access the CustomPlaybackOverlayFragment and
LeanbackOverlayFragment through videoAdapter. This was fundamentally
what CustomPlaybackTransportGlue did on behalf of the callers.

Overall it's a code smell that we retrieve the view through the
VideoAdapter, but without serious re-architecting it's better to keep
our call sites consistent for if/when we refactor these fragments out of
the video adapter.
2023-03-18 11:55:58 +01:00
David Fairbrother
8767227b08 Switch static dispatch onClick to use Polymorphism
Previously the glue class performed static dispatch using a switch case,
despite the interface having the vars for almost every case.

Switching to relying on Polymorphism allows us to drop the static
dispatch and let each action encapsulate how they want to handle the
fragment and/or player.

At a later date this will make testing each action significantly easier
too, as we can simply inject mocks or fakes per action, rather than for
the CustomPlaybackTransportControlGlue class.
2023-03-18 11:55:58 +01:00
Joe Rogers
f81d27e085 Update and specify full action versions 2023-03-17 15:47:37 +01:00
Bas
dd4ec289a0 Translated using Weblate (Dutch)
Currently translated at 100.0% (493 of 493 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/nl/
2023-03-17 06:57:03 -04:00
Can Baytok
72ad6feae6 Translated using Weblate (German)
Currently translated at 81.3% (401 of 493 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/de/
2023-03-16 13:39:23 -04:00
stanol
d12d55e4a5 Translated using Weblate (Ukrainian)
Currently translated at 99.7% (492 of 493 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/uk/
2023-03-15 19:39:23 -04:00
Niels van Velzen
3111f0b37d Add androidx.media2 MediaSessionService implementation 2023-03-15 20:58:31 +01:00
Niels van Velzen
8060442ffb Add media manager implementation using new playback module 2023-03-15 15:31:55 +01:00
Bas
52d4f63e51 Translated using Weblate (Dutch)
Currently translated at 100.0% (493 of 493 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/nl/
2023-03-14 16:39:22 -04:00
Niels van Velzen
6806f69c62 Store accounts in Account Manager by address 2023-03-14 13:58:41 +01:00
renovate[bot]
4246579eb7 Update glide to v4.15.1 2023-03-13 21:57:04 +01:00
Niels van Velzen
7282a39544 Remove watched indicator from empty series 2023-03-13 21:28:56 +01:00
Andi Chandler
2642a16208 Translated using Weblate (English (United Kingdom))
Currently translated at 92.2% (455 of 493 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/en_GB/
2023-03-12 18:39:22 -04:00
Scorpionhunt
f2092ad378 Translated using Weblate (German)
Currently translated at 81.3% (401 of 493 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/de/
2023-03-12 18:39:22 -04:00
Andi Chandler
47ea017da9 Translated using Weblate (English (United Kingdom))
Currently translated at 90.0% (444 of 493 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/en_GB/
2023-03-12 11:39:22 -04:00
Niels van Velzen
5d29a293d1 Add blurHashPainter composable 2023-03-12 10:43:36 +01:00
Niels van Velzen
6712eeb781 Always check server id when retrieving user data from AccountManagerStore 2023-03-12 10:31:13 +01:00
Andi Chandler
c8edc06f49 Translated using Weblate (English (United Kingdom))
Currently translated at 80.1% (395 of 493 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/en_GB/
2023-03-11 15:39:22 -05:00
Oskari Lavinto
41ae242f16 Translated using Weblate (Finnish)
Currently translated at 99.3% (490 of 493 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/fi/
2023-03-10 22:39:21 -05:00
Niels van Velzen
757efe196b Fix screensaver crash when loading an invalid backdrop image 2023-03-10 22:25:38 +01:00
Niels van Velzen
8dd66bc601 Update androidx.lifecycle usages 2023-03-10 19:50:39 +01:00
renovate[bot]
498f5f97c9 Update androidx-lifecycle to v2.6.0 2023-03-10 19:50:39 +01:00
renovate[bot]
d85de5e1d7 Update github/codeql-action digest to 16964e9 2023-03-10 19:46:08 +01:00
Weblate
43f66ada9b Update translation files
Updated by "Cleanup translation files" hook in Weblate.

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/
2023-03-10 13:17:37 -05:00
renovate[bot]
83db59a54f Update dependency androidx.recyclerview:recyclerview to v1.3.0 2023-03-10 19:17:32 +01:00
Moritz
32c8d6e262 Translated using Weblate (German)
Currently translated at 76.8% (379 of 493 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/de/
2023-03-10 07:39:21 -05:00
Troja
ece9f85628 Translated using Weblate (Belarusian)
Currently translated at 0.6% (3 of 493 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/be/
2023-03-08 16:39:20 -05:00
Bas
b000d61061 Translated using Weblate (Dutch)
Currently translated at 100.0% (493 of 493 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/nl/
2023-03-08 13:39:20 -05:00
nextlooper42
2eaee494fe Translated using Weblate (Slovak)
Currently translated at 100.0% (493 of 493 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/sk/
2023-03-08 10:39:20 -05:00
Oskari Lavinto
7ab4e699cd Translated using Weblate (Finnish)
Currently translated at 97.1% (479 of 493 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/fi/
2023-03-08 01:39:20 -05:00
millallo
e7aec7a30e Translated using Weblate (Italian)
Currently translated at 99.1% (489 of 493 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/it/
2023-03-07 18:39:20 -05:00
小造xu_zh
4bf58b73e5 Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (493 of 493 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/zh_Hans/
2023-03-07 10:39:20 -05:00
丸子
3de51860f1 Translated using Weblate (Chinese (Simplified))
Currently translated at 82.3% (406 of 493 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/zh_Hans/
2023-03-06 10:39:20 -05:00
Weblate
113d718211 Update translation files
Updated by "Cleanup translation files" hook in Weblate.

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/
2023-03-05 15:18:00 -05:00
Weblate
d1355c5724 Update translation files
Updated by "Cleanup translation files" hook in Weblate.

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/
2023-03-05 15:17:59 -05:00
Niels van Velzen
b1348f07ce Remove some unused drawables/strings/layouts 2023-03-05 21:17:56 +01:00
Niels van Velzen
2a7e29e123 Use plurals for amount of tracks in album / episodes in season
Also renames "songs" to "tracks" for consistency with jellyfin-web
2023-03-05 21:17:44 +01:00
Niels van Velzen
0a30b50904 Fix SearchFragment not restoring properly 2023-03-05 10:09:20 +01:00
Niels van Velzen
eada9ea21c Add workaround for non reproducible baseline profiles in AGP 2023-03-05 10:08:08 +01:00
Niels van Velzen
317f8bff27 Fix missing background in DreamContentLogo
When the dream failed to display content and went from a library showcase to the logo it would flicker because there was no background for the logo content during the animation, and when the animation finished the library showcase would suddenly disappear. Adding a background color fixes this issue.
2023-03-05 10:07:50 +01:00
Niels van Velzen
1c0da723ae Fix all string capitalization 2023-03-05 10:07:26 +01:00
小造xu_zh
f66858282c Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (499 of 499 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/zh_Hans/
2023-03-04 13:39:18 -05:00
kaypiff
6816104800 Translated using Weblate (Polish)
Currently translated at 100.0% (499 of 499 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/pl/
2023-03-04 07:39:19 -05:00
Bas
a8a4a1a3e1 Translated using Weblate (Dutch)
Currently translated at 100.0% (499 of 499 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/nl/
2023-03-04 07:39:18 -05:00
omm
68f6b3a3ef Add IncludeType param when sending number query 2023-03-03 19:35:18 +01:00
renovate[bot]
7a5ee0b229 Update dependency com.google.android.exoplayer:exoplayer to v2.18.4 2023-03-03 18:59:54 +01:00
Oskari Lavinto
ca37eafb3d Translated using Weblate (Finnish)
Currently translated at 99.3% (496 of 499 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/fi/
2023-03-01 07:39:17 -05:00
Kela910512
15d65c0575 Translated using Weblate (Hungarian)
Currently translated at 100.0% (4 of 4 strings)

Translation: Jellyfin Android/Jellyfin Android TV Fastlane
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv-fastlane/hu/
2023-02-28 18:39:17 -05:00
Kela910512
d5b4b86f7e Translated using Weblate (Hungarian)
Currently translated at 100.0% (499 of 499 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/hu/
2023-02-28 18:39:17 -05:00
Weblate
55017866f8 Update translation files
Updated by "Cleanup translation files" hook in Weblate.

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/
2023-02-28 16:10:31 -05:00
Bill Thornton
d4206d9b4d Update "Latest" media labels to be "Recently Added" 2023-02-28 22:10:24 +01:00
Weblate
545b16dde2 Update translation files
Updated by "Cleanup translation files" hook in Weblate.

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/
2023-02-28 07:49:11 -05:00
Niels van Velzen
c6347c21ca Fix crash when scaleType is null in AsyncImage 2023-02-28 13:49:06 +01:00
Weblate
80b02db44c Update translation files
Updated by "Cleanup translation files" hook in Weblate.

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/
2023-02-28 07:24:12 -05:00
Niels van Velzen
9f80ec6ee4 Always use getQuantityString() for time units 2023-02-28 13:24:07 +01:00
renovate[bot]
9816154cee Update appleboy/ssh-action action to v0.1.8 2023-02-28 13:00:58 +01:00
Bas
237755f3ca Translated using Weblate (Dutch)
Currently translated at 100.0% (4 of 4 strings)

Translation: Jellyfin Android/Jellyfin Android TV Fastlane
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv-fastlane/nl/
2023-02-27 18:39:17 -05:00
Bas
6bddb305d1 Translated using Weblate (Dutch)
Currently translated at 100.0% (504 of 504 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/nl/
2023-02-27 18:39:17 -05:00
David Fairbrother
b3bc4b0d8c Accept KEYCODE_ENTER as alt to KEYCODE_CENTER
Adds KEYCODE_ENTER wherever we accept KEYCODE_DPAD_CENTER. This was
reported in #2526 where third-party remotes will use the former instead
of the latter value.

I've added this to all places we accept CENTER, since our most of our
Kotlin code already accepts this as an alternative and I can't forsee a
case where we would want these to accept different things.

This was tested in the emulator to ensure we've not broken the existing
mapping, but short of using the enter key (which works) I can't verify
this fixes the original issue.
2023-02-27 20:53:27 +01:00
renovate[bot]
e7b59f57f1 Update dependency com.android.tools.build:gradle to v7.4.2 2023-02-27 20:45:26 +01:00
Oskari Lavinto
89e242f3d9 Translated using Weblate (Finnish)
Currently translated at 99.4% (501 of 504 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/fi/
2023-02-27 04:39:17 -05:00
renovate[bot]
fcf8ad008b Update gradle/gradle-build-action digest to 6095a76 2023-02-26 21:10:52 +01:00
Niels van Velzen
231a54aaf5 Prevent screensaver from showing when picture viewer slideshow is active 2023-02-26 17:03:57 +01:00
Niels van Velzen
a199530e5d Add InAppScreensaver to show custom screensaver in app 2023-02-26 17:03:57 +01:00
Niels van Velzen
2aaf77770d Remove screensaver from AudioNowPlayingFragment 2023-02-26 17:03:57 +01:00
Niels van Velzen
328072a36c Fix theme not applied before creating views 2023-02-26 16:52:12 +01:00
Niels van Velzen
bbc7f3eea4 Only attach background service in activities 2023-02-26 16:52:01 +01:00
renovate[bot]
54d4be7446 Update github/codeql-action digest to 32dc499 2023-02-25 12:30:20 +01:00
renovate[bot]
10b488668c Update glide to v4.15.0 2023-02-25 12:30:05 +01:00
renovate[bot]
eb03a7f6dc Update dependency org.jetbrains.kotlinx:kotlinx-serialization-json to v1.5.0 2023-02-25 12:29:51 +01:00
renovate[bot]
7de86e8d68 Update dependency gradle to v7.6.1 2023-02-24 19:39:59 +01:00
dredstone1
f23a1da850 Translated using Weblate (Hebrew)
Currently translated at 98.9% (492 of 497 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/he/
2023-02-24 11:39:16 -05:00
nextlooper42
54b296b519 Translated using Weblate (Slovak)
Currently translated at 100.0% (497 of 497 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/sk/
2023-02-24 11:39:15 -05:00
Andi Chandler
00e7abf7b9 Translated using Weblate (English (United Kingdom))
Currently translated at 100.0% (497 of 497 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/en_GB/
2023-02-24 11:39:15 -05:00
Oskari Lavinto
e57dfebc8c Translated using Weblate (Finnish)
Currently translated at 99.1% (493 of 497 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/fi/
2023-02-24 02:39:15 -05:00
Bas
a480ec15ff Translated using Weblate (Dutch)
Currently translated at 100.0% (497 of 497 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/nl/
2023-02-23 17:39:15 -05:00
Niels van Velzen
74a7898fcd Mark LibVLC as experimental 2023-02-23 20:49:50 +01:00
renovate[bot]
cd8b55c1e2 Update aboutlibraries to v10.6.1 2023-02-23 16:49:10 +01:00
renovate[bot]
a83fdd3802 Update dependency androidx.compose.compiler:compiler to v1.4.3 2023-02-23 08:51:13 +01:00
lyaschuchenko
572a840235 Translated using Weblate (Ukrainian)
Currently translated at 100.0% (497 of 497 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/uk/
2023-02-22 05:39:15 -05:00
Weblate
486fe93d99 Update translation files
Updated by "Cleanup translation files" hook in Weblate.

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/
2023-02-21 08:11:54 -05:00
Niels van Velzen
4bc78a0b93 Remove saveAudioQueue from MediaManager 2023-02-21 14:11:48 +01:00
Niels van Velzen
a7e01ebcee Replace deprecated ExistingPeriodicWorkPolicy.REPLACE with ExistingPeriodicWorkPolicy.UPDATE 2023-02-21 14:11:40 +01:00
Niels van Velzen
1aa37fda98 Show currently playing music on screensaver 2023-02-20 22:26:32 +01:00
Niels van Velzen
1706074e73 Split LibraryDreamService into multiple files and fix various bugs 2023-02-20 22:26:32 +01:00
Niels van Velzen
1b256e3be9 Replace deprecated Bundle.get with Bundle.getParcelable 2023-02-20 22:23:50 +01:00
Niels van Velzen
67b8202e49 Revert "Update aboutlibraries to v10.6.0"
This reverts commit 63665cbf11.
2023-02-20 22:16:56 +01:00
Niels van Velzen
952b24d150 Replace sharedViewModel with activityViewModel 2023-02-20 22:05:26 +01:00
Niels van Velzen
84dc7fc48d Don't show "invalid credentials" message when authentication fails with an SDK exception 2023-02-20 22:04:47 +01:00
renovate[bot]
63665cbf11 Update aboutlibraries to v10.6.0 2023-02-20 21:47:44 +01:00
giotex64
a4d39ad648 Translated using Weblate (Italian)
Currently translated at 100.0% (500 of 500 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/it/
2023-02-20 05:39:14 -05:00
rushmash
433b80a841 Added translation using Weblate (Belarusian) 2023-02-19 14:57:21 -05:00
HK
ee781633b5 Translated using Weblate (Turkish)
Currently translated at 79.0% (395 of 500 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/tr/
2023-02-17 10:39:13 -05:00
阿卜杜克热木·吾加卜杜拉
132843d709 Translated using Weblate (Uyghur)
Currently translated at 19.4% (97 of 500 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/ug/
2023-02-17 06:39:14 -05:00
Niels van Velzen
2846d7fd0f Add rememberCurrentTime() composable and Modifier.overscan() 2023-02-16 21:59:02 +01:00
Niels van Velzen
13796cdd02 Remove KenBurns dependency 2023-02-16 21:59:02 +01:00
Niels van Velzen
fd47c5fb52 Rewrite LibraryDreamService with Compose 2023-02-16 21:59:02 +01:00
Niels van Velzen
fa70460188 Add DreamServiceCompat 2023-02-16 21:59:02 +01:00
Niels van Velzen
6298e5e44f Add AsyncImage composable 2023-02-16 21:59:02 +01:00
renovate[bot]
f10cbc05d7 Update dependency com.google.android.exoplayer:exoplayer to v2.18.3 2023-02-16 21:58:30 +01:00
Bruno Jurković
f13e2dcd9f Translated using Weblate (Croatian)
Currently translated at 100.0% (500 of 500 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/hr/
2023-02-16 11:39:14 -05:00
Ruben Kremer
37333bff40 Translated using Weblate (Dutch)
Currently translated at 100.0% (4 of 4 strings)

Translation: Jellyfin Android/Jellyfin Android TV Fastlane
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv-fastlane/nl/
2023-02-15 16:39:13 -05:00
Eduardo
2b0797c164 Translated using Weblate (Spanish)
Currently translated at 100.0% (500 of 500 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/es/
2023-02-15 16:39:12 -05:00
hoanghuy309
69b9384824 Translated using Weblate (Vietnamese)
Currently translated at 98.2% (491 of 500 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/vi/
2023-02-14 11:39:12 -05:00
小造xu_zh
ff6f2d2cff Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (500 of 500 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/zh_Hans/
2023-02-14 07:39:12 -05:00
Niels van Velzen
aeb76c3812 Add androidx.compose.compiler to libraries section in version catalog
This should trigger Renovate to update the version for us
2023-02-10 21:39:55 +01:00
renovate[bot]
a19f88c8d3 Update github/codeql-action digest to 17573ee 2023-02-10 21:39:45 +01:00
Timmeh
215b0e9f2c Fix old items are shown when new filter is applied. (#2472)
* clear rows after applying new filter.

* Always set current item to toolbar if current item is filtered out.
2023-02-10 21:39:15 +01:00
renovate[bot]
8233417799 Update dependency io.insert-koin:koin-androidx-compose to v3.4.2 2023-02-10 19:25:57 +01:00
Kamil Dzierbicki
d5be222191 Translated using Weblate (Polish)
Currently translated at 100.0% (4 of 4 strings)

Translation: Jellyfin Android/Jellyfin Android TV Fastlane
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv-fastlane/pl/
2023-02-10 10:51:33 -05:00
Andi Chandler
e9d9539ed1 Translated using Weblate (English (United Kingdom))
Currently translated at 100.0% (500 of 500 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/en_GB/
2023-02-09 18:51:32 -05:00
aenron
f1ee7109fe Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (500 of 500 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/zh_Hans/
2023-02-09 09:51:33 -05:00
Bas
331df19697 Translated using Weblate (Dutch)
Currently translated at 100.0% (500 of 500 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/nl/
2023-02-09 09:51:32 -05:00
renovate[bot]
6c3cda0d9f Update github/codeql-action digest to 8775e86 2023-02-08 21:46:54 +01:00
renovate[bot]
c747e026d4 Update dependency androidx.work:work-runtime-ktx to v2.8.0 2023-02-08 21:45:33 +01:00
renovate[bot]
0454d68f37 Update koin to v3.3.3 2023-02-08 21:37:34 +01:00
renovate[bot]
3003d32597 Update actions/setup-java digest to 3f07048 2023-02-08 19:35:28 +01:00
Niels van Velzen
4316dac93f Update compose compiler to 1.4.2 2023-02-08 19:34:43 +01:00
renovate[bot]
a2dcbe8cc8 Update Kotlin 2023-02-08 19:34:43 +01:00
renovate[bot]
ea182744b6 Update dependency androidx.appcompat:appcompat to v1.6.1 2023-02-08 19:34:26 +01:00
nextlooper42
b070115d54 Translated using Weblate (Slovak)
Currently translated at 100.0% (500 of 500 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/sk/
2023-02-08 12:51:32 -05:00
renovate[bot]
c46b7115ab Update github/codeql-action digest to 39d8d7e 2023-02-07 16:41:11 +01:00
Oskari Lavinto
ae7199489a Translated using Weblate (Finnish)
Currently translated at 99.2% (496 of 500 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/fi/
2023-02-07 05:51:32 -05:00
Niels van Velzen
18354cc929 Update item delete confirm message string 2023-02-06 19:07:09 +01:00
João Batista
c03debe57f Translated using Weblate (Portuguese (Portugal))
Currently translated at 100.0% (500 of 500 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/pt_PT/
2023-02-05 18:51:31 -05:00
renovate[bot]
262928c400 Update kotest to v5.5.5 2023-02-05 21:57:31 +01:00
Niels van Velzen
8ea6d3fde1 Update MediaManager interface again 2023-02-04 08:49:29 +01:00
Niels van Velzen
181d9c1fbb Add PlaybackManagerOptions for defaultRewindAmount and defaultFastForwardAmount 2023-02-04 08:49:29 +01:00
renovate[bot]
125cf74d5e Update dependency com.android.tools:desugar_jdk_libs to v2.0.2 2023-02-03 19:15:59 +01:00
Niels van Velzen
e3d5262b5b Fix rare picture viewer crash when album is empty 2023-02-02 21:31:41 +01:00
Niels van Velzen
8309056299 Revert "Update dependency com.android.tools:desugar_jdk_libs to v2.0.1"
This reverts commit 21850ccfcb.
2023-02-02 21:15:32 +01:00
renovate[bot]
21850ccfcb Update dependency com.android.tools:desugar_jdk_libs to v2.0.1 2023-02-02 19:56:42 +01:00
florentsarat
78d67f8ae3 Translated using Weblate (French)
Currently translated at 100.0% (500 of 500 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/fr/
2023-02-01 20:51:29 -05:00
Timmeh
ed0540a5a9 adds "shuffle with" button if "always ask" is selected as preferred player. (#2459)
* add "shuffle with" button

* fix linting warnings
2023-02-01 21:52:11 +01:00
renovate[bot]
6cbfe43ce7 Update dependency com.android.tools.build:gradle to v7.4.1 2023-02-01 20:28:52 +01:00
Niels van Velzen
adac020b0f Remove selected photo item from MediaManager and do some more cleaning 2023-02-01 19:19:49 +01:00
Weblate
82f131c8d7 Update translation files
Updated by "Remove blank strings" hook in Weblate.

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/
2023-01-31 18:51:30 -05:00
millallo
5c87e79e80 Translated using Weblate (Italian)
Currently translated at 99.2% (496 of 500 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/it/
2023-01-31 18:51:29 -05:00
Антон
df8a8bc265 Translated using Weblate (Russian)
Currently translated at 99.8% (499 of 500 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/ru/
2023-01-31 15:51:30 -05:00
Patrick Oliveira
b9b7a4dfab Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (500 of 500 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/pt_BR/
2023-01-31 15:51:29 -05:00
Moritz
097bbe19b5 Translated using Weblate (German)
Currently translated at 99.8% (499 of 500 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/de/
2023-01-31 15:51:29 -05:00
Magnus Clemmensen
d8cd3f065c Translated using Weblate (Danish)
Currently translated at 100.0% (500 of 500 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/da/
2023-01-31 15:51:29 -05:00
JonahKr
f603ad53d2 Translated using Weblate (German)
Currently translated at 100.0% (4 of 4 strings)

Translation: Jellyfin Android/Jellyfin Android TV Fastlane
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv-fastlane/de/
2023-01-31 07:51:29 -05:00
Niels van Velzen
2f5d4ac800 Split MediaManager into MediaManager and VideoQueueManager 2023-01-30 22:59:48 +01:00
Yaroslav Pavlov
c9864e7df4 Translated using Weblate (Ukrainian)
Currently translated at 100.0% (500 of 500 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/uk/
2023-01-30 16:51:29 -05:00
Niels van Velzen
9c4ef1a791 Enable desugaring for playback:core module 2023-01-30 20:18:22 +01:00
Niels van Velzen
e2cb114ff3 Add MediaSessionPlugin to playback rewrite 2023-01-30 20:18:22 +01:00
Paweł
c483315f03 Translated using Weblate (Polish)
Currently translated at 100.0% (4 of 4 strings)

Translation: Jellyfin Android/Jellyfin Android TV Fastlane
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv-fastlane/pl/
2023-01-30 12:51:29 -05:00
Paweł
1c2894dd04 Translated using Weblate (Polish)
Currently translated at 100.0% (500 of 500 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/pl/
2023-01-30 12:51:29 -05:00
nextlooper42
48b2e51ef2 Translated using Weblate (Slovak)
Currently translated at 100.0% (4 of 4 strings)

Translation: Jellyfin Android/Jellyfin Android TV Fastlane
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv-fastlane/sk/
2023-01-30 06:51:29 -05:00
nextlooper42
e5b4d71928 Translated using Weblate (Slovak)
Currently translated at 100.0% (500 of 500 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/sk/
2023-01-30 06:51:28 -05:00
AdamPinczesi
fba92045de Translated using Weblate (Hungarian)
Currently translated at 100.0% (500 of 500 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/hu/
2023-01-29 20:51:28 -05:00
Weblate
93f9e8fc58 Update translation files
Updated by "Cleanup translation files" hook in Weblate.

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/
2023-01-29 03:31:32 -05:00
Niels van Velzen
5c1990107a Add option to enable playback rewrite for audio 2023-01-29 09:31:19 +01:00
Bas
d58800c990 Translated using Weblate (Dutch)
Currently translated at 100.0% (501 of 501 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/nl/
2023-01-28 15:51:28 -05:00
HenryTujia
bbb5b1146e Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (501 of 501 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/zh_Hans/
2023-01-28 11:51:28 -05:00
Oskari Lavinto
e27ef9eff5 Translated using Weblate (Finnish)
Currently translated at 99.2% (497 of 501 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/fi/
2023-01-28 08:51:28 -05:00
Andi Chandler
7421ab1838 Translated using Weblate (English (United Kingdom))
Currently translated at 100.0% (501 of 501 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/en_GB/
2023-01-28 08:51:28 -05:00
Niels van Velzen
34ff9b941b Remove unused properties/functions from MediaManager 2023-01-28 12:31:52 +01:00
Niels van Velzen
4b7f6374ae Split MediaManager into interface and implementation 2023-01-28 12:31:52 +01:00
renovate[bot]
2c1bef0fea Update dependency io.mockk:mockk to v1.13.4 2023-01-28 12:08:39 +01:00
Niels van Velzen
d69a0630ed Add delete option to item details screen
The option only shows when the user has permissions to delete the displaying item
2023-01-27 20:30:24 +01:00
renovate[bot]
05f11c7a89 Update github/codeql-action digest to 3ebbd71 2023-01-27 20:30:17 +01:00
jtbrinkmann
95266d2960 Translated using Weblate (German)
Currently translated at 100.0% (495 of 495 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/de/
2023-01-27 07:51:27 -05:00
renovate[bot]
c8ff1a239f Update github/codeql-action digest to 436dbd9 2023-01-26 19:13:13 +01:00
renovate[bot]
88c5bc1594 Update dependency com.google.devtools.ksp to v1.8.0-1.0.9 2023-01-26 19:02:24 +01:00
Алексей
e4b134e92e Translated using Weblate (Russian)
Currently translated at 99.7% (494 of 495 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/ru/
2023-01-26 08:51:27 -05:00
moshiko kar
1f804a254f Translated using Weblate (Hebrew)
Currently translated at 100.0% (4 of 4 strings)

Translation: Jellyfin Android/Jellyfin Android TV Fastlane
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv-fastlane/he/
2023-01-26 04:51:28 -05:00
moshiko kar
8a230f7fe0 Translated using Weblate (Hebrew)
Currently translated at 100.0% (495 of 495 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/he/
2023-01-26 04:51:27 -05:00
moshiko kar
8d22fc9e1b Translated using Weblate (Hebrew (Israel))
Currently translated at 10.7% (53 of 495 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/he_IL/
2023-01-26 04:51:27 -05:00
Niels van Velzen
2b93f4f6e4 Remove gradlew-update.yaml workflow 2023-01-25 14:37:30 +01:00
Niels van Velzen
22b4ecf0ec Change opening menu to ACTION_UP instead of ACTION_DOWN 2023-01-24 21:22:10 +01:00
Kyle Repinski
848ff4772e Add AV1 direct play support 2023-01-24 16:25:30 +01:00
Bas
155ddc3eba Translated using Weblate (Dutch)
Currently translated at 100.0% (495 of 495 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/nl/
2023-01-23 11:51:26 -05:00
Ecor
4d8493920b Translated using Weblate (Spanish)
Currently translated at 100.0% (4 of 4 strings)

Translation: Jellyfin Android/Jellyfin Android TV Fastlane
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv-fastlane/es/
2023-01-23 04:51:26 -05:00
Niels van Velzen
6783ade9b5 Update playback module with SDK 1.4.1 changes 2023-01-23 08:57:27 +01:00
Niels van Velzen
a7b06d39e1 Use HEADER_ACCEPT from SDK as Glide Accept header value 2023-01-23 08:57:18 +01:00
Niels van Velzen
3839d1c5d1 Set Accept header in Glide requests 2023-01-23 08:57:18 +01:00
renovate[bot]
80a2dd5ac1 Update dependency org.jellyfin.sdk:jellyfin-core to v1.4.1 2023-01-22 22:31:15 +01:00
renovate[bot]
76561be44b Update github/codeql-action digest to a34ca99 2023-01-20 20:20:14 +01:00
Niels van Velzen
5988dca2a6 Optimize ExoPlayerBackend.prepareStream() 2023-01-20 20:19:56 +01:00
Niels van Velzen
15c870f815 Add ExoPlayer playback backend 2023-01-20 20:19:56 +01:00
Bas
7e9745f57d Translated using Weblate (Dutch)
Currently translated at 100.0% (495 of 495 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/nl/
2023-01-20 05:51:25 -05:00
Aleksander Helgaker
37a29d628c Translated using Weblate (Norwegian Bokmål)
Currently translated at 76.5% (379 of 495 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/nb_NO/
2023-01-19 08:51:25 -05:00
Niels van Velzen
82297e0f93 Update compose compiler to 1.4.0 2023-01-18 09:47:26 +01:00
renovate[bot]
578a986f8c Update Kotlin 2023-01-18 09:47:26 +01:00
Niels van Velzen
602928924a Add PlaySessionService to playback rewrite 2023-01-18 09:07:40 +01:00
hoanghuy309
a8e3779f10 Translated using Weblate (Vietnamese)
Currently translated at 98.1% (486 of 495 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/vi/
2023-01-17 23:51:24 -05:00
Bas
4fb47e1c92 Translated using Weblate (Dutch)
Currently translated at 100.0% (495 of 495 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/nl/
2023-01-17 11:51:24 -05:00
renovate[bot]
4fc82a5cfc Update dependency com.android.tools:desugar_jdk_libs to v2 2023-01-16 22:10:44 +01:00
renovate[bot]
2f2cd91b2f Update dependency com.android.tools.build:gradle to v7.4.0 2023-01-16 22:03:34 +01:00
Teo Baranga
d673614865 Ignore missing notification permission from Glide 2023-01-16 21:56:32 +01:00
gefs72
e18034e499 Translated using Weblate (Russian)
Currently translated at 99.7% (494 of 495 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/ru/
2023-01-16 06:51:23 -05:00
Niels van Velzen
5b83a6de9a Increase amount of logs added to crash reports
Some stacktraces with Kotlin coroutines can get big. Right now we have 100 lines and this often is not enough to see what happened before the exception. Increasing the count to 250 should hopefully fix that.
2023-01-15 09:46:25 +01:00
Niels van Velzen
48f25033a3 Enable desugaring for playback/jellyfin module 2023-01-14 12:42:14 +01:00
Niels van Velzen
0f4d2e1bef Add builder for PlaybackManager 2023-01-14 12:42:14 +01:00
Niels van Velzen
880248f863 Add initial PlaybackManager implementation 2023-01-14 12:42:14 +01:00
Niels van Velzen
cddb57afaa Implement PlayerState and rewrite QueueManager to QueueService 2023-01-14 12:42:14 +01:00
Niels van Velzen
b4d4f8ee8f Add initial plugin support 2023-01-14 12:42:14 +01:00
Niels van Velzen
ca08def3d7 Add MediaStreamResolver 2023-01-14 12:42:14 +01:00
Niels van Velzen
941c251f7c Add initial playback backend implementation 2023-01-14 12:42:14 +01:00
0TTA
f001a54f38 Translated using Weblate (Arabic)
Currently translated at 100.0% (495 of 495 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/ar/
2023-01-14 04:51:22 -05:00
Bas
d900c37fe1 Translated using Weblate (Dutch)
Currently translated at 98.7% (489 of 495 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/nl/
2023-01-13 06:51:22 -05:00
renovate[bot]
e52a56d4a6 Update github/codeql-action digest to 515828d 2023-01-12 21:47:15 +01:00
renovate[bot]
0c832b4eb0 Update dependency androidx.appcompat:appcompat to v1.6.0 2023-01-12 21:47:07 +01:00
renovate[bot]
56222a5f01 Update dependency androidx.compose.ui:ui-tooling to v1.3.3 2023-01-12 21:46:51 +01:00
Weevild
2baacc134f Translated using Weblate (Swedish)
Currently translated at 100.0% (495 of 495 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/sv/
2023-01-12 12:51:22 -05:00
0TTA
51a4bc6a87 Translated using Weblate (Arabic)
Currently translated at 100.0% (495 of 495 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/ar/
2023-01-12 03:51:22 -05:00
Andrey Neto
a5e6d7375b Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (495 of 495 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/pt_BR/
2023-01-10 15:51:21 -05:00
0TTA
3f3906a21b Translated using Weblate (Arabic)
Currently translated at 100.0% (4 of 4 strings)

Translation: Jellyfin Android/Jellyfin Android TV Fastlane
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv-fastlane/ar/
2023-01-10 08:51:21 -05:00
0TTA
98d170549f Translated using Weblate (Arabic)
Currently translated at 98.7% (489 of 495 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/ar/
2023-01-10 08:51:21 -05:00
Niels van Velzen
3c70b8abf5 Remove experimental audio codecs from transcoding profile 2023-01-09 23:58:56 +01:00
renovate[bot]
6076448984 Update appleboy/ssh-action action to v0.1.7 2023-01-09 18:48:37 +01:00
Niels van Velzen
b5b87fb625 Fix BrowseGridFragment focus not restoring when going back 2023-01-09 18:48:07 +01:00
Lundy Rasuli
2ffea30c53 Translated using Weblate (Indonesian)
Currently translated at 52.5% (260 of 495 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/id/
2023-01-09 07:51:20 -05:00
Retrial
caf85e56f1 Translated using Weblate (Greek)
Currently translated at 100.0% (4 of 4 strings)

Translation: Jellyfin Android/Jellyfin Android TV Fastlane
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv-fastlane/el/
2023-01-08 10:51:20 -05:00
Bas
1edf86ea4f Translated using Weblate (Dutch)
Currently translated at 98.3% (487 of 495 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/nl/
2023-01-08 10:51:20 -05:00
Ecor
8f54c6ced9 Translated using Weblate (Spanish (Mexico))
Currently translated at 100.0% (4 of 4 strings)

Translation: Jellyfin Android/Jellyfin Android TV Fastlane
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv-fastlane/es_MX/
2023-01-07 19:51:20 -05:00
Ecor
420d3b5ca9 Translated using Weblate (Spanish)
Currently translated at 100.0% (495 of 495 strings)

Translation: Jellyfin Android/Jellyfin Android TV
Translate-URL: https://translate.jellyfin.org/projects/jellyfin-android/jellyfin-androidtv/es/
2023-01-07 19:51:20 -05:00
Marco Ammon
c0d0ae65ce Show resumable episodes in smart screen for shows (#2275) 2023-01-07 12:12:25 +01:00
renovate[bot]
fc4ffbc36e Update actions/upload-artifact digest to 0b7f8ab 2023-01-06 20:09:39 +01:00
renovate[bot]
c709d1b53c Update koin 2023-01-06 20:03:16 +01:00
renovate[bot]
f8c766234e Update dependency io.mockk:mockk to v1.13.3 2023-01-06 20:01:57 +01:00
renovate[bot]
92c0022eaf Update aboutlibraries to v10.5.2 2023-01-06 20:01:45 +01:00
renovate[bot]
ffc3028035 Update dependency io.gitlab.arturbosch.detekt to v1.22.0 2023-01-06 20:01:17 +01:00
gradle-update-robot
6d3a7b631d Update Gradle Wrapper from 7.5.1 to 7.6.
Signed-off-by: gradle-update-robot <gradle-update-robot@regolo.cc>
2023-01-06 20:00:44 +01:00
renovate[bot]
9f3002404a Update github/codeql-action digest to 959cbb7 2023-01-06 20:00:21 +01:00
renovate[bot]
88898a4037 Update actions/setup-java digest to 1df8dbe 2023-01-06 20:00:14 +01:00
renovate[bot]
3e1d67c45d Update appleboy/ssh-action action to v0.1.6 2023-01-06 20:00:08 +01:00
renovate[bot]
04646c426d Update actions/checkout digest to ac59398 2023-01-06 20:00:00 +01:00
renovate[bot]
9a3efd9542 Update actions/stale action to v7 2023-01-06 19:59:52 +01:00
480 changed files with 14224 additions and 7281 deletions

View File

@@ -15,20 +15,20 @@ jobs:
runs-on: ubuntu-22.04
steps:
- name: Checkout repository
uses: actions/checkout@2541b1294d2704b0964813337f33b291d3f8596b # tag=v3
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
- name: Setup Java
uses: actions/setup-java@de1bb2b0c5634f0fc4438d7aa9944e68f9bf86cc # tag=v3
uses: actions/setup-java@387ac29b308b003ca37ba93a6cab5eb57c8f5f93 # v4.0.0
with:
distribution: temurin
java-version: 17
- name: Setup Gradle
uses: gradle/gradle-build-action@3fbe033aaae657f011f88f29be9e65ed26bd29ef # tag=v2
uses: gradle/gradle-build-action@8cbcb9948b5892387aed077daf6f90e1f0ba5b27 # v2.11.0
- name: Assemble debug APKs
run: ./gradlew assembleDebug
- name: Create publish bundle
run: mkdir -p build/gh-app-publish/; find app/build/ -iname "*.apk" -exec mv "{}" build/gh-app-publish/ \;
- name: Upload artifacts
uses: actions/upload-artifact@83fd05a356d7e2593de66fc9913b3002723633cb # tag=v3
uses: actions/upload-artifact@c7d193f32edcb7bfad88892161225aeda64e9392 # v4.0.0
with:
name: build-artifacts
retention-days: 14

View File

@@ -17,18 +17,18 @@ jobs:
runs-on: ubuntu-22.04
steps:
- name: Checkout repository
uses: actions/checkout@2541b1294d2704b0964813337f33b291d3f8596b # tag=v3
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
- name: Setup Java
uses: actions/setup-java@de1bb2b0c5634f0fc4438d7aa9944e68f9bf86cc # tag=v3
uses: actions/setup-java@387ac29b308b003ca37ba93a6cab5eb57c8f5f93 # v4.0.0
with:
distribution: temurin
java-version: 17
- name: Setup Gradle
uses: gradle/gradle-build-action@3fbe033aaae657f011f88f29be9e65ed26bd29ef # tag=v2
uses: gradle/gradle-build-action@8cbcb9948b5892387aed077daf6f90e1f0ba5b27 # v2.11.0
- name: Run detekt and lint tasks
run: ./gradlew detekt lint
- name: Upload SARIF files
uses: github/codeql-action/upload-sarif@678fc3afe258fb2e0cdc165ccf77b85719de7b3c # v2
uses: github/codeql-action/upload-sarif@b374143c1149a9115d881581d29b8390bbcbb59c # v3.22.11
if: ${{ always() }}
with:
sarif_file: .

View File

@@ -12,14 +12,14 @@ jobs:
if: ${{ contains(github.repository_owner, 'jellyfin') }}
steps:
- name: Checkout repository
uses: actions/checkout@2541b1294d2704b0964813337f33b291d3f8596b # tag=v3
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
- name: Setup Java
uses: actions/setup-java@de1bb2b0c5634f0fc4438d7aa9944e68f9bf86cc # tag=v3
uses: actions/setup-java@387ac29b308b003ca37ba93a6cab5eb57c8f5f93 # v4.0.0
with:
distribution: temurin
java-version: 17
- name: Setup Gradle
uses: gradle/gradle-build-action@3fbe033aaae657f011f88f29be9e65ed26bd29ef # tag=v2
uses: gradle/gradle-build-action@8cbcb9948b5892387aed077daf6f90e1f0ba5b27 # v2.11.0
- name: Set JELLYFIN_VERSION
run: echo "JELLYFIN_VERSION=$(echo ${GITHUB_REF#refs/tags/v} | tr / -)" >> $GITHUB_ENV
- name: Assemble release files
@@ -27,6 +27,8 @@ jobs:
- name: Sign APK
id: signApk
uses: r0adkll/sign-android-release@349ebdef58775b1e0d8099458af0816dc79b6407 # tag=v1
env:
BUILD_TOOLS_VERSION: "34.0.0"
with:
releaseDirectory: app/build/outputs/apk/release
signingKeyBase64: ${{ secrets.KEYSTORE }}
@@ -36,6 +38,8 @@ jobs:
- name: Sign app bundle
id: signAab
uses: r0adkll/sign-android-release@349ebdef58775b1e0d8099458af0816dc79b6407 # tag=v1
env:
BUILD_TOOLS_VERSION: "34.0.0"
with:
releaseDirectory: app/build/outputs/bundle/release
signingKeyBase64: ${{ secrets.KEYSTORE }}
@@ -66,7 +70,7 @@ jobs:
remote_user: ${{ secrets.DEPLOY_USER }}
remote_key: ${{ secrets.DEPLOY_KEY }}
- name: Update repo.jellyfin.org symlinks
uses: appleboy/ssh-action@f9010ff7f1bbd7db1a0b4bab661437550cea20c0 # tag=v0.1.5
uses: appleboy/ssh-action@55dabf81b49d4120609345970c91507e2d734799 # v1.0.0
with:
host: ${{ secrets.DEPLOY_HOST }}
username: ${{ secrets.DEPLOY_USER }}

View File

@@ -16,13 +16,13 @@ jobs:
runs-on: ubuntu-22.04
steps:
- name: Checkout repository
uses: actions/checkout@2541b1294d2704b0964813337f33b291d3f8596b # tag=v3
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
- name: Setup Java
uses: actions/setup-java@de1bb2b0c5634f0fc4438d7aa9944e68f9bf86cc # tag=v3
uses: actions/setup-java@387ac29b308b003ca37ba93a6cab5eb57c8f5f93 # v4.0.0
with:
distribution: temurin
java-version: 17
- name: Setup Gradle
uses: gradle/gradle-build-action@3fbe033aaae657f011f88f29be9e65ed26bd29ef # tag=v2
uses: gradle/gradle-build-action@8cbcb9948b5892387aed077daf6f90e1f0ba5b27 # v2.11.0
- name: Run test task
run: ./gradlew test

View File

@@ -1,18 +0,0 @@
name: Gradle / Update wrapper
on:
schedule:
- cron: '0 4 * * *'
jobs:
update:
name: Update
runs-on: ubuntu-22.04
if: ${{ contains(github.repository_owner, 'jellyfin') }}
steps:
- name: Checkout repository
uses: actions/checkout@2541b1294d2704b0964813337f33b291d3f8596b # tag=v3
- name: Update Gradle Wrapper
uses: gradle-update/update-gradle-wrapper-action@981c551b17dbcf1940b1b4435afdb79babb7c13a # tag=v1
with:
repo-token: ${{ secrets.JF_BOT_TOKEN }}

View File

@@ -17,6 +17,6 @@ jobs:
runs-on: ubuntu-22.04
steps:
- name: Checkout repository
uses: actions/checkout@2541b1294d2704b0964813337f33b291d3f8596b # tag=v3
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
- name: Validate Gradle Wrapper
uses: gradle/wrapper-validation-action@55e685c48d84285a5b0418cd094606e199cca3b6 # tag=v1
uses: gradle/wrapper-validation-action@56b90f209b02bf6d1deae490e9ef18b21a389cd4 # v1.1.0

View File

@@ -15,7 +15,7 @@ jobs:
runs-on: ubuntu-22.04
if: ${{ contains(github.repository_owner, 'jellyfin') }}
steps:
- uses: actions/stale@5ebf00ea0e4c1561e9b43a292ed34424fb1d4578 # tag=v6
- uses: actions/stale@28ca1036281a5e5922ead5184a1bbf96e5fc984e # v9.0.0
with:
repo-token: ${{ secrets.JF_BOT_TOKEN }}
days-before-stale: 120

View File

@@ -11,6 +11,8 @@
- [Niels van Velzen](https://github.com/nielsvanvelzen)
- [GodTamIt](https://github.com/GodTamIt)
- [sparky3387](https://github.com/sparky3387)
- [mohd-akram](https://github.com/mohd-akram)
- [3l0w](https://github.com/3l0w)
# Emby Contributors

View File

@@ -35,6 +35,9 @@
<a href="https://www.amazon.com/gp/aw/d/B07TX7Z725">
<img width="153" alt="Jellyfin on Amazon Appstore" src="https://jellyfin.org/images/store-icons/amazon.png"/>
</a>
<a href="https://f-droid.org/en/packages/org.jellyfin.androidtv/">
<img width="153" alt="Jellyfin on F-Droid" src="https://jellyfin.org/images/store-icons/fdroid.png"/>
</a>
<br/>
<a href="https://repo.jellyfin.org/releases/client/androidtv/">Download archive</a>
</p>

View File

@@ -13,6 +13,9 @@
<issue id="MangledCRLF" severity="warning" />
<issue id="NegativeMargin" severity="warning" />
<issue id="NoHardKeywords" severity="warning" />
<issue id="NotificationPermission">
<ignore regexp="com.bumptech.glide.request.target.NotificationTarget" />
</issue>
<issue id="Registered" severity="warning" />
<issue id="RequiredSize" severity="error" />
<issue id="Typos" severity="ignore" />

View File

@@ -0,0 +1,39 @@
buildscript {
repositories {
mavenCentral()
google()
}
dependencies {
classpath libs.android.gradle
}
}
import com.android.tools.profgen.ArtProfileKt
import com.android.tools.profgen.ArtProfileSerializer
import com.android.tools.profgen.DexFile
/**
* This is a temporary workaround for https://issuetracker.google.com/issues/231837768
* which will be fixed in AGP 8.1.0, at which point the workaround may be deleted.
*/
project.afterEvaluate {
tasks.compileReleaseArtProfile.doLast {
outputs.files.each { file ->
if (file.toString().endsWith(".profm")) {
println("Sorting ${file}")
def version = ArtProfileSerializer.valueOf("METADATA_0_0_2")
def profile = ArtProfileKt.ArtProfile(file)
def keys = new ArrayList(profile.profileData.keySet())
def sortedData = new LinkedHashMap()
Collections.sort keys, new DexFile.Companion()
keys.each { key -> sortedData[key] = profile.profileData[key] }
new FileOutputStream(file).with {
write(version.magicBytes$profgen)
write(version.versionBytes$profgen)
version.write$profgen(it, sortedData, "")
}
}
}
}
}

View File

@@ -1,14 +1,16 @@
plugins {
id("com.android.application")
kotlin("android")
alias(libs.plugins.kotlin.ksp)
alias(libs.plugins.kotlin.serialization)
alias(libs.plugins.aboutlibraries)
}
// Apply workaround
apply("baselineWorkaround.gradle")
android {
namespace = "org.jellyfin.androidtv"
compileSdk = 33
compileSdk = 34
defaultConfig {
minSdk = 21
@@ -22,6 +24,7 @@ android {
}
buildFeatures {
buildConfig = true
viewBinding = true
compose = true
}
@@ -136,8 +139,10 @@ dependencies {
implementation(libs.bundles.koin)
// Media players
implementation(libs.exoplayer)
implementation(libs.jellyfin.exoplayer.ffmpegextension)
implementation(libs.androidx.media3.exoplayer)
implementation(libs.androidx.media3.exoplayer.hls)
implementation(libs.androidx.media3.ui)
implementation(libs.jellyfin.androidx.media3.ffmpeg.decoder)
implementation(libs.libvlc)
// Markdown
@@ -145,9 +150,7 @@ dependencies {
// Image utility
implementation(libs.blurhash)
implementation(libs.glide.core)
ksp(libs.glide.ksp)
implementation(libs.kenburnsview)
implementation(libs.bundles.coil)
// Crash Reporting
implementation(libs.bundles.acra)

View File

@@ -6,23 +6,13 @@
<!-- Android TV Integration -->
<uses-permission android:name="com.android.providers.tv.permission.WRITE_EPG_DATA" />
<!-- AccountManager permissions -->
<uses-permission
android:name="android.permission.AUTHENTICATE_ACCOUNTS"
android:maxSdkVersion="22" />
<uses-permission
android:name="android.permission.GET_ACCOUNTS"
android:maxSdkVersion="22" />
<uses-permission
android:name="android.permission.MANAGE_ACCOUNTS"
android:maxSdkVersion="22" />
<!-- Generic permissions -->
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission
android:name="android.permission.ACCESS_WIFI_STATE"
tools:ignore="LeanbackUsesWifi" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<!-- Search permissions (voice to text) -->
<uses-permission android:name="android.permission.RECORD_AUDIO" />
@@ -38,6 +28,15 @@
android:name="android.hardware.microphone"
android:required="false" />
<queries>
<intent>
<action android:name="android.intent.action.VIEW" />
<data
android:host="youtube.com"
android:scheme="https" />
</intent>
</queries>
<application
android:name=".JellyfinApplication"
android:allowBackup="true"
@@ -52,6 +51,8 @@
android:supportsRtl="true"
android:theme="@style/Theme.Jellyfin"
android:usesCleartextTraffic="true">
<!-- Legacy service required for AccountManagerMigration -->
<service
android:name=".auth.service.AuthenticatorService"
android:exported="false">
@@ -90,9 +91,6 @@
<meta-data
android:name="org.jellyfin.androidtv.SessionInitializer"
android:value="androidx.startup" />
<meta-data
android:name="org.jellyfin.androidtv.ProcessLifecycleInitializer"
android:value="androidx.startup" />
</provider>
<provider
@@ -117,6 +115,7 @@
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<action android:name="android.intent.action.SEARCH" />
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.LAUNCHER" />
<category android:name="android.intent.category.LEANBACK_LAUNCHER" />
@@ -138,20 +137,11 @@
<activity
android:name=".ui.preference.PreferencesActivity"
android:screenOrientation="behind"
android:theme="@style/Theme.Jellyfin.Preferences" />
<!-- Playback related activities -->
<activity
android:name=".ui.playback.PlaybackOverlayActivity"
android:screenOrientation="landscape" />
<activity android:name=".ui.playback.ExternalPlayerActivity" />
<activity
android:name=".ui.playback.nextup.NextUpActivity"
android:noHistory="true"
android:screenOrientation="landscape" />
<!-- Playback Rewrite - Temporary -->
<activity android:name=".ui.playback.rewrite.PlaybackForwardingActivity" />
</application>

View File

@@ -9,7 +9,6 @@ import androidx.work.ExistingPeriodicWorkPolicy
import androidx.work.PeriodicWorkRequestBuilder
import androidx.work.WorkManager
import androidx.work.await
import com.bumptech.glide.Glide
import com.vanniktech.blurhash.BlurHash
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
@@ -51,7 +50,7 @@ class JellyfinApplication : Application() {
// Recreate periodic workers
workManager.enqueueUniquePeriodicWork(
LeanbackChannelWorker.PERIODIC_UPDATE_REQUEST_NAME,
ExistingPeriodicWorkPolicy.REPLACE,
ExistingPeriodicWorkPolicy.UPDATE,
PeriodicWorkRequestBuilder<LeanbackChannelWorker>(1, TimeUnit.HOURS)
.setBackoffCriteria(BackoffPolicy.LINEAR, 10, TimeUnit.MINUTES)
.build()
@@ -70,13 +69,6 @@ class JellyfinApplication : Application() {
super.onLowMemory()
BlurHash.clearCache()
Glide.with(this).onLowMemory()
}
override fun onTrimMemory(level: Int) {
super.onTrimMemory(level)
Glide.with(this).onTrimMemory(level)
}
override fun attachBaseContext(base: Context?) {

View File

@@ -1,22 +0,0 @@
package org.jellyfin.androidtv
import android.content.Context
import android.util.Log
import com.bumptech.glide.GlideBuilder
import com.bumptech.glide.annotation.GlideModule
import com.bumptech.glide.load.engine.DiskCacheStrategy
import com.bumptech.glide.module.AppGlideModule
import com.bumptech.glide.request.RequestOptions
@GlideModule
class JellyfinGlideModule : AppGlideModule() {
override fun applyOptions(context: Context, builder: GlideBuilder): Unit = with(builder) {
setDefaultRequestOptions(
// Set default disk cache strategy
RequestOptions().diskCacheStrategy(DiskCacheStrategy.RESOURCE)
)
// Silence image load errors
setLogLevel(Log.ERROR)
}
}

View File

@@ -1,50 +0,0 @@
package org.jellyfin.androidtv
import android.app.Application
import android.content.Context
import androidx.lifecycle.DefaultLifecycleObserver
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.ProcessLifecycleOwner
import androidx.lifecycle.lifecycleScope
import androidx.startup.AppInitializer
import androidx.startup.Initializer
import kotlinx.coroutines.launch
import org.jellyfin.androidtv.auth.repository.SessionRepository
import org.jellyfin.androidtv.di.KoinInitializer
import timber.log.Timber
@Suppress("unused")
class ProcessLifecycleInitializer : Initializer<Unit> {
override fun create(context: Context) {
val koin = AppInitializer.getInstance(context)
.initializeComponent(KoinInitializer::class.java)
.koin
// Register application lifecycle events
ProcessLifecycleOwner.get().lifecycle.addObserver(object : DefaultLifecycleObserver {
/**
* Called by the Process Lifecycle when the app is created. It is called after [onCreate].
*/
override fun onCreate(owner: LifecycleOwner) {
// Register activity lifecycle callbacks
val callbacks = koin.getAll<Application.ActivityLifecycleCallbacks>()
Timber.i("Registering ${callbacks.size} ActivityLifecycleCallbacks")
val app = context.applicationContext as Application
callbacks.forEach { callback -> app.registerActivityLifecycleCallbacks(callback) }
}
/**
* Called by the Process Lifecycle when the app is activated in the foreground (activity opened).
*/
override fun onStart(owner: LifecycleOwner) {
Timber.i("Process lifecycle started")
owner.lifecycleScope.launch {
koin.get<SessionRepository>().restoreSession()
}
}
})
}
override fun dependencies() = listOf(KoinInitializer::class.java)
}

View File

@@ -18,7 +18,7 @@ class SessionInitializer : Initializer<Unit> {
// Restore system session
ProcessLifecycleOwner.get().lifecycleScope.launch {
koin.get<SessionRepository>().restoreSession()
koin.get<SessionRepository>().restoreSession(destroyOnly = false)
}
}

View File

@@ -0,0 +1,48 @@
package org.jellyfin.androidtv.auth
import android.accounts.AccountManager
import android.content.Context
import androidx.core.content.getSystemService
import org.jellyfin.androidtv.BuildConfig
import org.jellyfin.androidtv.auth.model.AuthenticationStoreServer
import org.jellyfin.sdk.model.serializer.toUUIDOrNull
import timber.log.Timber
import java.util.UUID
class AccountManagerMigration(
context: Context,
) {
private val accountManager = requireNotNull(context.getSystemService<AccountManager>())
fun migrate(
servers: Map<UUID, AuthenticationStoreServer>,
) = servers.mapValues { (serverId, server) ->
Timber.i("Migrating server $serverId (${server.name})")
server.copy(
users = server.users.mapValues { (userId, user) ->
val accessToken = getAccessToken(serverId, userId)
Timber.i("Migrating user $userId (${user.name}): ${if (accessToken != null) "success" else "no token"}")
user.copy(accessToken = accessToken)
}
)
}
@Suppress("MissingPermission")
private fun getAccessToken(serverId: UUID, userId: UUID): String? = runCatching {
accountManager.getAccountsByType(ACCOUNT_TYPE)
.firstOrNull {
val validServerId = accountManager.getUserData(it, ACCOUNT_DATA_SERVER)?.toUUIDOrNull() == serverId
val validUserId = accountManager.getUserData(it, ACCOUNT_DATA_ID)?.toUUIDOrNull() == userId
validServerId && validUserId
}
?.let { account -> accountManager.peekAuthToken(account, ACCOUNT_ACCESS_TOKEN_TYPE) }
}.getOrNull()
companion object {
const val ACCOUNT_TYPE = BuildConfig.APPLICATION_ID
const val ACCOUNT_DATA_ID = "$ACCOUNT_TYPE.id"
const val ACCOUNT_DATA_SERVER = "$ACCOUNT_TYPE.server"
const val ACCOUNT_ACCESS_TOKEN_TYPE = "$ACCOUNT_TYPE.access_token"
}
}

View File

@@ -1,10 +0,0 @@
package org.jellyfin.androidtv.auth.model
import java.util.UUID
data class AccountManagerAccount(
val id: UUID,
val server: UUID,
val name: String,
val accessToken: String? = null,
)

View File

@@ -1,11 +1,11 @@
package org.jellyfin.androidtv.auth.model
import org.jellyfin.androidtv.R
import org.jellyfin.androidtv.ui.preference.dsl.EnumDisplayOptions
import org.jellyfin.preference.PreferenceEnum
enum class AuthenticationSortBy {
@EnumDisplayOptions(name = R.string.last_use)
LAST_USE,
@EnumDisplayOptions(name = R.string.alphabetical)
ALPHABETICAL
enum class AuthenticationSortBy(
override val nameRes: Int
) : PreferenceEnum {
LAST_USE(R.string.last_use),
ALPHABETICAL(R.string.alphabetical);
}

View File

@@ -12,5 +12,6 @@ data class AuthenticationStoreUser(
val name: String,
@SerialName("last_used") val lastUsed: Long = Date().time,
@SerialName("require_password") val requirePassword: Boolean = true,
@SerialName("image_tag") val imageTag: String? = null
@SerialName("image_tag") val imageTag: String? = null,
@SerialName("access_token") val accessToken: String? = null,
)

View File

@@ -1,8 +1,11 @@
package org.jellyfin.androidtv.auth.model
import org.jellyfin.sdk.api.client.exception.ApiClientException
sealed class LoginState
object AuthenticatingState : LoginState()
object RequireSignInState : LoginState()
object ServerUnavailableState : LoginState()
data class ServerVersionNotSupported(val server: Server) : LoginState()
data class ApiClientErrorLoginState(val error: ApiClientException) : LoginState()
object AuthenticatedState : LoginState()

View File

@@ -4,7 +4,7 @@ import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.emitAll
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.flowOf
import org.jellyfin.androidtv.auth.model.AccountManagerAccount
import org.jellyfin.androidtv.auth.model.ApiClientErrorLoginState
import org.jellyfin.androidtv.auth.model.AuthenticateMethod
import org.jellyfin.androidtv.auth.model.AuthenticatedState
import org.jellyfin.androidtv.auth.model.AuthenticatingState
@@ -19,7 +19,6 @@ import org.jellyfin.androidtv.auth.model.Server
import org.jellyfin.androidtv.auth.model.ServerUnavailableState
import org.jellyfin.androidtv.auth.model.ServerVersionNotSupported
import org.jellyfin.androidtv.auth.model.User
import org.jellyfin.androidtv.auth.store.AccountManagerStore
import org.jellyfin.androidtv.auth.store.AuthenticationPreferences
import org.jellyfin.androidtv.auth.store.AuthenticationStore
import org.jellyfin.androidtv.util.ImageUtils
@@ -51,7 +50,6 @@ interface AuthenticationRepository {
class AuthenticationRepositoryImpl(
private val jellyfin: Jellyfin,
private val sessionRepository: SessionRepository,
private val accountManagerStore: AccountManagerStore,
private val authenticationStore: AuthenticationStore,
private val userApiClient: ApiClient,
private val authenticationPreferences: AuthenticationPreferences,
@@ -69,14 +67,14 @@ class AuthenticationRepositoryImpl(
}
private fun authenticateAutomatic(server: Server, user: User): Flow<LoginState> {
Timber.d("Authenticating user %s", user)
Timber.i("Authenticating user %s", user)
// Automatic logic is disabled when the always authenticate preference is enabled
if (authenticationPreferences[AuthenticationPreferences.alwaysAuthenticate]) return flowOf(RequireSignInState)
val account = accountManagerStore.getAccount(user.id)
val authStoreUser = authenticationStore.getUser(server.id, user.id)
// Try login with access token
return if (account?.accessToken != null) authenticateToken(server, user.withToken(account.accessToken))
return if (authStoreUser?.accessToken != null) authenticateToken(server, user.withToken(authStoreUser.accessToken))
// Try login without password
else if (!user.requirePassword) authenticateCredential(server, user.name, "")
// Require login
@@ -94,7 +92,7 @@ class AuthenticationRepositoryImpl(
return@flow
} catch (err: ApiClientException) {
Timber.e(err, "Unable to sign in as $username")
emit(RequireSignInState)
emit(ApiClientErrorLoginState(err))
return@flow
}
@@ -106,9 +104,13 @@ class AuthenticationRepositoryImpl(
val result = try {
val response = api.userApi.authenticateWithQuickConnect(secret)
response.content
} catch (err: TimeoutException) {
Timber.e(err, "Failed to connect to server")
emit(ServerUnavailableState)
return@flow
} catch (err: ApiClientException) {
Timber.e(err, "Unable to sign in with Quick Connect secret")
emit(RequireSignInState)
emit(ApiClientErrorLoginState(err))
return@flow
}
@@ -116,7 +118,7 @@ class AuthenticationRepositoryImpl(
}
private fun authenticateAuthenticationResult(server: Server, result: AuthenticationResult) = flow {
val accessToken = result.accessToken ?:return@flow emit(RequireSignInState)
val accessToken = result.accessToken ?: return@flow emit(RequireSignInState)
val userInfo = result.user ?: return@flow emit(RequireSignInState)
val user = PrivateUser(
id = userInfo.id,
@@ -130,8 +132,12 @@ class AuthenticationRepositoryImpl(
authenticateFinish(server, userInfo, accessToken)
val success = setActiveSession(user, server)
if (success) emit(AuthenticatedState)
else emit(RequireSignInState)
if (success) {
emit(AuthenticatedState)
} else {
Timber.w("Failed to set active session after authenticating")
emit(RequireSignInState)
}
}
private fun authenticateToken(server: Server, user: User) = flow {
@@ -145,9 +151,13 @@ class AuthenticationRepositoryImpl(
val userInfo by userApiClient.userApi.getCurrentUser()
authenticateFinish(server, userInfo, user.accessToken.orEmpty())
emit(AuthenticatedState)
} catch (err: TimeoutException) {
Timber.e(err, "Failed to connect to server")
emit(ServerUnavailableState)
return@flow
} catch (err: ApiClientException) {
Timber.e(err, "Unable to get current user data")
emit(RequireSignInState)
emit(ApiClientErrorLoginState(err))
}
}
@@ -158,20 +168,19 @@ class AuthenticationRepositoryImpl(
name = userInfo.name!!,
lastUsed = Date().time,
requirePassword = userInfo.hasPassword,
imageTag = userInfo.primaryImageTag
imageTag = userInfo.primaryImageTag,
accessToken = accessToken,
) ?: AuthenticationStoreUser(
name = userInfo.name!!,
requirePassword = userInfo.hasPassword,
imageTag = userInfo.primaryImageTag
imageTag = userInfo.primaryImageTag,
accessToken = accessToken,
)
authenticationStore.putUser(server.id, userInfo.id, updatedUser)
val accountManagerAccount = AccountManagerAccount(userInfo.id, server.id, updatedUser.name, accessToken)
accountManagerStore.putAccount(accountManagerAccount)
}
private suspend fun setActiveSession(user: User, server: Server): Boolean {
val authenticated = sessionRepository.switchCurrentSession(user.id)
val authenticated = sessionRepository.switchCurrentSession(server.id, user.id)
if (authenticated) {
// Update last use in store
@@ -188,8 +197,12 @@ class AuthenticationRepositoryImpl(
}
override fun logout(user: User): Boolean {
val authInfo = accountManagerStore.getAccount(user.id) ?: return false
return accountManagerStore.removeAccount(authInfo)
val authStoreUser = authenticationStore
.getUser(user.serverId, user.id)
?.copy(accessToken = null)
return if (authStoreUser != null) authenticationStore.putUser(user.serverId, user.id, authStoreUser)
else false
}
override fun getUserImageUrl(server: Server, user: User): String? = user.imageTag?.let { tag ->

View File

@@ -4,7 +4,6 @@ import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.firstOrNull
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.map
import org.jellyfin.androidtv.auth.model.AuthenticationStoreServer
@@ -47,6 +46,7 @@ interface ServerRepository {
companion object {
val minimumServerVersion = Jellyfin.minimumVersion.copy(build = null)
val recommendedServerVersion = Jellyfin.apiVersion.copy(build = null)
}
}
@@ -83,12 +83,12 @@ class ServerRepositoryImpl(
// Mutating data
override fun addServer(address: String): Flow<ServerAdditionState> = flow {
Timber.d("Adding server %s", address)
Timber.i("Adding server %s", address)
emit(ConnectingState(address))
val addressCandidates = jellyfin.discovery.getAddressCandidates(address)
Timber.d("Found ${addressCandidates.size} candidates")
Timber.i("Found ${addressCandidates.size} candidates")
val goodRecommendations = mutableListOf<RecommendedServerInfo>()
val badRecommendations = mutableListOf<RecommendedServerInfo>()

View File

@@ -3,7 +3,6 @@ package org.jellyfin.androidtv.auth.repository
import org.jellyfin.androidtv.auth.model.PrivateUser
import org.jellyfin.androidtv.auth.model.PublicUser
import org.jellyfin.androidtv.auth.model.Server
import org.jellyfin.androidtv.auth.store.AccountManagerStore
import org.jellyfin.androidtv.auth.store.AuthenticationStore
import org.jellyfin.androidtv.util.sdk.toPublicUser
import org.jellyfin.sdk.Jellyfin
@@ -27,14 +26,13 @@ interface ServerUserRepository {
class ServerUserRepositoryImpl(
private val jellyfin: Jellyfin,
private val authenticationStore: AuthenticationStore,
private val accountManagerStore: AccountManagerStore,
) : ServerUserRepository {
override fun getStoredServerUsers(server: Server) = authenticationStore.getUsers(server.id)
?.mapNotNull { (userId, userInfo) ->
val authInfo = accountManagerStore.getAccount(userId)
val authInfo = authenticationStore.getUser(server.id, userId)
PrivateUser(
id = authInfo?.id ?: userId,
serverId = authInfo?.server ?: server.id,
id = userId,
serverId = server.id,
name = userInfo.name,
accessToken = authInfo?.accessToken,
requirePassword = userInfo.requirePassword,
@@ -62,10 +60,5 @@ class ServerUserRepositoryImpl(
override fun deleteStoredUser(user: PrivateUser) {
// Remove user info from store
authenticationStore.removeUser(user.serverId, user.id)
// Remove authentication info from system account manager
accountManagerStore.getAccount(user.id)?.let { accountManagerAccount ->
accountManagerStore.removeAccount(accountManagerAccount)
}
}
}

View File

@@ -1,12 +1,13 @@
package org.jellyfin.androidtv.auth.repository
import kotlinx.coroutines.NonCancellable
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import kotlinx.coroutines.withContext
import org.jellyfin.androidtv.auth.apiclient.ApiBinder
import org.jellyfin.androidtv.auth.store.AccountManagerStore
import org.jellyfin.androidtv.auth.store.AuthenticationPreferences
import org.jellyfin.androidtv.auth.store.AuthenticationStore
import org.jellyfin.androidtv.preference.PreferencesRepository
@@ -40,14 +41,13 @@ interface SessionRepository {
val currentSession: StateFlow<Session?>
val state: StateFlow<SessionRepositoryState>
suspend fun restoreSession()
suspend fun switchCurrentSession(userId: UUID): Boolean
suspend fun restoreSession(destroyOnly: Boolean)
suspend fun switchCurrentSession(serverId: UUID, userId: UUID): Boolean
fun destroyCurrentSession()
}
class SessionRepositoryImpl(
private val authenticationPreferences: AuthenticationPreferences,
private val accountManagerStore: AccountManagerStore,
private val apiBinder: ApiBinder,
private val authenticationStore: AuthenticationStore,
private val userApiClient: ApiClient,
@@ -63,34 +63,44 @@ class SessionRepositoryImpl(
private val _state = MutableStateFlow(SessionRepositoryState.READY)
override val state = _state.asStateFlow()
override suspend fun restoreSession(): Unit = currentSessionMutex.withLock {
Timber.d("Restoring session")
_state.value = SessionRepositoryState.RESTORING_SESSION
override suspend fun restoreSession(destroyOnly: Boolean): Unit = withContext(NonCancellable) {
currentSessionMutex.withLock {
Timber.i("Restoring session")
if (authenticationPreferences[AuthenticationPreferences.alwaysAuthenticate]) return destroyCurrentSession()
_state.value = SessionRepositoryState.RESTORING_SESSION
val behavior = authenticationPreferences[AuthenticationPreferences.autoLoginUserBehavior]
val userId = authenticationPreferences[AuthenticationPreferences.autoLoginUserId].toUUIDOrNull()
val alwaysAuthenticate = authenticationPreferences[AuthenticationPreferences.alwaysAuthenticate]
val autoLoginBehavior = authenticationPreferences[AuthenticationPreferences.autoLoginUserBehavior]
when (behavior) {
DISABLED -> destroyCurrentSession()
LAST_USER -> setCurrentSession(createLastUserSession())
SPECIFIC_USER -> setCurrentSession(createUserSession(userId))
when {
alwaysAuthenticate -> destroyCurrentSession()
autoLoginBehavior == DISABLED -> destroyCurrentSession()
autoLoginBehavior == LAST_USER && !destroyOnly -> setCurrentSession(createLastUserSession())
autoLoginBehavior == SPECIFIC_USER && !destroyOnly -> {
val serverId = authenticationPreferences[AuthenticationPreferences.autoLoginServerId].toUUIDOrNull()
val userId = authenticationPreferences[AuthenticationPreferences.autoLoginUserId].toUUIDOrNull()
if (serverId != null && userId != null) setCurrentSession(createUserSession(serverId, userId))
}
}
_state.value = SessionRepositoryState.READY
}
_state.value = SessionRepositoryState.READY
}
override suspend fun switchCurrentSession(userId: UUID): Boolean {
override suspend fun switchCurrentSession(serverId: UUID, userId: UUID): Boolean {
// No change in user - don't switch
if (currentSession.value?.userId == userId) return false
if (currentSession.value?.userId == userId) {
Timber.d("Current session user is the same as the requested user")
return false
}
_state.value = SessionRepositoryState.SWITCHING_SESSION
Timber.d("Switching current session to user $userId")
Timber.i("Switching current session to user $userId")
val session = createUserSession(userId)
val session = createUserSession(serverId, userId)
if (session == null) {
Timber.d("Could not switch to non-existing session for user $userId")
Timber.w("Could not switch to non-existing session for user $userId")
_state.value = SessionRepositoryState.READY
return false
}
@@ -100,11 +110,10 @@ class SessionRepositoryImpl(
}
override fun destroyCurrentSession() {
Timber.d("Destroying current session")
Timber.i("Destroying current session")
userRepository.updateCurrentUser(null)
_currentSession.value = null
userApiClient.applySession(null)
apiBinder.updateSession(null, userApiClient.deviceInfo)
_state.value = SessionRepositoryState.READY
}
@@ -115,6 +124,7 @@ class SessionRepositoryImpl(
if (currentSession.value?.userId == session.userId) return true
// Update last active user
authenticationPreferences[AuthenticationPreferences.lastServerId] = session.serverId.toString()
authenticationPreferences[AuthenticationPreferences.lastUserId] = session.userId.toString()
// Check if server version is supported
@@ -125,12 +135,12 @@ class SessionRepositoryImpl(
// Update session after binding the apiclient settings
val deviceInfo = session?.let { defaultDeviceInfo.forUser(it.userId) } ?: defaultDeviceInfo
val success = apiBinder.updateSession(session, deviceInfo)
Timber.d("Updating current session. userId=${session?.userId} apiBindingSuccess=${success}")
Timber.i("Updating current session. userId=${session?.userId} apiBindingSuccess=${success}")
if (success) {
userApiClient.applySession(session, deviceInfo)
val applied = userApiClient.applySession(session, deviceInfo)
if (session != null) {
if (applied && session != null) {
// Update crash reporting URL
val crashReportUrl = userApiClient.clientLogApi.logFileUrl(includeCredentials = false)
telemetryPreferences[TelemetryPreferences.crashReportUrl] = crashReportUrl
@@ -156,23 +166,24 @@ class SessionRepositoryImpl(
private fun createLastUserSession(): Session? {
val lastUserId = authenticationPreferences[AuthenticationPreferences.lastUserId].toUUIDOrNull()
return createUserSession(lastUserId)
val lastServerId = authenticationPreferences[AuthenticationPreferences.lastServerId].toUUIDOrNull()
return if (lastUserId != null && lastServerId != null) createUserSession(lastServerId, lastUserId)
else null
}
private fun createUserSession(userId: UUID?): Session? {
if (userId == null) return null
val account = accountManagerStore.getAccount(userId)
private fun createUserSession(serverId: UUID, userId: UUID): Session? {
val account = authenticationStore.getUser(serverId, userId)
if (account?.accessToken == null) return null
return Session(
userId = account.id,
serverId = account.server,
userId = userId,
serverId = serverId,
accessToken = account.accessToken
)
}
private fun ApiClient.applySession(session: Session?, newDeviceInfo: DeviceInfo = defaultDeviceInfo) {
private fun ApiClient.applySession(session: Session?, newDeviceInfo: DeviceInfo = defaultDeviceInfo): Boolean {
deviceInfo = newDeviceInfo
if (session == null) {
@@ -181,10 +192,12 @@ class SessionRepositoryImpl(
userId = null
} else {
val server = authenticationStore.getServer(session.serverId)
?: return applySession(null)
?: return false
baseUrl = server.address
accessToken = session.accessToken
userId = session.userId
}
return true
}
}

View File

@@ -1,82 +0,0 @@
package org.jellyfin.androidtv.auth.store
import android.accounts.Account
import android.accounts.AccountManager
import android.os.Bundle
import org.jellyfin.androidtv.BuildConfig
import org.jellyfin.androidtv.auth.model.AccountManagerAccount
import org.jellyfin.sdk.model.serializer.toUUID
import org.jellyfin.sdk.model.serializer.toUUIDOrNull
import java.util.UUID
import kotlin.coroutines.resume
import kotlin.coroutines.suspendCoroutine
class AccountManagerStore(
private val accountManager: AccountManager
) {
companion object {
const val ACCOUNT_TYPE = BuildConfig.APPLICATION_ID
const val ACCOUNT_DATA_ID = "$ACCOUNT_TYPE.id"
const val ACCOUNT_DATA_SERVER = "$ACCOUNT_TYPE.server"
const val ACCOUNT_DATA_NAME = "$ACCOUNT_TYPE.name"
const val ACCOUNT_ACCESS_TOKEN_TYPE = "$ACCOUNT_TYPE.access_token"
}
private fun getAccountData(account: Account): AccountManagerAccount = AccountManagerAccount(
id = accountManager.getUserData(account, ACCOUNT_DATA_ID).toUUID(),
server = accountManager.getUserData(account, ACCOUNT_DATA_SERVER).toUUID(),
name = accountManager.getUserData(account, ACCOUNT_DATA_NAME),
accessToken = accountManager.peekAuthToken(account, ACCOUNT_ACCESS_TOKEN_TYPE)
)
suspend fun putAccount(accountManagerAccount: AccountManagerAccount) {
var androidAccount = accountManager.getAccountsByType(ACCOUNT_TYPE)
.firstOrNull { accountManager.getUserData(it, ACCOUNT_DATA_ID)?.toUUIDOrNull() == accountManagerAccount.id }
// Update credentials
if (androidAccount == null) {
androidAccount = Account(accountManagerAccount.name, ACCOUNT_TYPE)
accountManager.addAccountExplicitly(
androidAccount,
"", // Leave password empty
Bundle()
)
accountManager.setUserData(androidAccount, ACCOUNT_DATA_ID, accountManagerAccount.id.toString())
}
// Update name
if (androidAccount.name != accountManagerAccount.name) {
androidAccount = suspendCoroutine { continuation ->
accountManager.renameAccount(androidAccount, accountManagerAccount.name, { continuation.resume(it.result) }, null)
}
}
accountManager.setUserData(androidAccount, ACCOUNT_DATA_NAME, accountManagerAccount.name)
accountManager.setUserData(androidAccount, ACCOUNT_DATA_SERVER, accountManagerAccount.server.toString())
accountManager.setAuthToken(androidAccount, ACCOUNT_ACCESS_TOKEN_TYPE, accountManagerAccount.accessToken)
}
fun removeAccount(accountManagerAccount: AccountManagerAccount): Boolean {
val androidAccount = accountManager.getAccountsByType(ACCOUNT_TYPE)
.firstOrNull { accountManager.getUserData(it, ACCOUNT_DATA_ID)?.toUUIDOrNull() == accountManagerAccount.id }
?: return false
// Remove current account info
@Suppress("DEPRECATION")
return if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.LOLLIPOP_MR1) {
accountManager.removeAccount(androidAccount, null, null)
true
} else accountManager.removeAccountExplicitly(androidAccount)
}
fun getAccounts() = accountManager.getAccountsByType(ACCOUNT_TYPE).map(::getAccountData)
fun getAccountsByServer(server: UUID) = accountManager.getAccountsByType(ACCOUNT_TYPE).filter { account ->
accountManager.getUserData(account, ACCOUNT_DATA_SERVER)?.toUUIDOrNull() == server
}.map(::getAccountData)
fun getAccount(id: UUID) = accountManager.getAccountsByType(ACCOUNT_TYPE).firstOrNull { account ->
accountManager.getUserData(account, ACCOUNT_DATA_ID)?.toUUIDOrNull() == id
}?.let(::getAccountData)
}

View File

@@ -14,12 +14,30 @@ class AuthenticationPreferences(context: Context) : SharedPreferenceStore(
companion object {
// Preferences
val autoLoginUserBehavior = enumPreference("auto_login_user_behavior", UserSelectBehavior.LAST_USER)
val autoLoginServerId = stringPreference("auto_login_server_id", "")
val autoLoginUserId = stringPreference("auto_login_user_id", "")
val sortBy = enumPreference("sort_by", AuthenticationSortBy.LAST_USE)
val alwaysAuthenticate = booleanPreference("always_authenticate", false)
// Persistent state
val lastServerId = stringPreference("last_server_id", "")
val lastUserId = stringPreference("last_user_id", "")
}
init {
runMigrations {
// v0.15.4 to v0.15.5
migration(toVersion = 2) {
// Unfortunately we cannot migrate the "specific user" login option
// so we'll reset the preference to disabled if it was used
if (it.getString("auto_login_user_behavior", null) === UserSelectBehavior.SPECIFIC_USER.name) {
putString("auto_login_user_id", "")
putString("auto_login_user_behavior", UserSelectBehavior.DISABLED.name)
}
putString("last_user_id", "")
}
}
}
}

View File

@@ -13,6 +13,7 @@ import kotlinx.serialization.json.jsonObject
import kotlinx.serialization.json.jsonPrimitive
import kotlinx.serialization.modules.SerializersModule
import kotlinx.serialization.modules.contextual
import org.jellyfin.androidtv.auth.AccountManagerMigration
import org.jellyfin.androidtv.auth.model.AuthenticationStoreServer
import org.jellyfin.androidtv.auth.model.AuthenticationStoreUser
import org.jellyfin.sdk.model.serializer.UUIDSerializer
@@ -20,14 +21,14 @@ import timber.log.Timber
import java.util.UUID
/**
* Storage for authentication related entities. Stores servers with users inside. Should be used in
* combination with the [AccountManagerStore] to store access tokens and integrate with the
* operating system.
* Storage for authentication related entities. Stores servers with users inside, including
* access tokens.
*
* The data is stored in a JSON file located in the applications data directory.
*/
class AuthenticationStore(
private val context: Context
private val context: Context,
private val accountManagerMigration: AccountManagerMigration,
) {
private val storePath
get() = context.filesDir.resolve("authentication_store.json")
@@ -59,10 +60,17 @@ class AuthenticationStore(
// Check for version
return when (root["version"]?.jsonPrimitive?.intOrNull) {
1 -> json.decodeFromJsonElement<Map<UUID, AuthenticationStoreServer>>(root["servers"]!!)
// Add access tokens from account manager to stored users and save the migrated data
.let { servers -> accountManagerMigration.migrate(servers) }
.also { servers -> write(servers) }
2 -> json.decodeFromJsonElement<Map<UUID, AuthenticationStoreServer>>(root["servers"]!!)
null -> {
Timber.e("Authentication Store is corrupt!")
emptyMap()
}
else -> {
Timber.e("Authentication Store is using an unknown version!")
emptyMap()
@@ -70,10 +78,10 @@ class AuthenticationStore(
}
}
private fun save(): Boolean {
private fun write(servers: Map<UUID, AuthenticationStoreServer>): Boolean {
val root = JsonObject(mapOf(
"version" to JsonPrimitive(1),
"servers" to json.encodeToJsonElement(store)
"version" to JsonPrimitive(2),
"servers" to json.encodeToJsonElement(servers)
))
storePath.writeText(json.encodeToString(root))
@@ -81,6 +89,10 @@ class AuthenticationStore(
return true
}
private fun save(): Boolean {
return write(store)
}
fun getServers(): Map<UUID, AuthenticationStoreServer> = store
fun getUsers(server: UUID): Map<UUID, AuthenticationStoreUser>? = getServer(server)?.users

View File

@@ -1,31 +0,0 @@
package org.jellyfin.androidtv.auth.ui
import android.content.Intent
import androidx.fragment.app.FragmentActivity
import org.jellyfin.androidtv.auth.repository.SessionRepository
import org.jellyfin.androidtv.auth.repository.UserRepository
import org.jellyfin.androidtv.ui.startup.StartupActivity
import org.koin.android.ext.android.inject
import timber.log.Timber
/**
* Extension function to check authentication. Should be called in [FragmentActivity.onCreate] and
* [FragmentActivity.onResume]. It validates the current session and opens the authentication screen
* when no session is found.
*
* @return whether to proceed creating the activity or not. When `false` is returned the
* [FragmentActivity.finish] function is automatically called.
*/
fun FragmentActivity.validateAuthentication(): Boolean {
val sessionRepository by inject<SessionRepository>()
val userRepository by inject<UserRepository>()
if (sessionRepository.currentSession.value == null || userRepository.currentUser.value == null) {
Timber.w("Activity ${this::class.qualifiedName} started without a session, bouncing to StartupActivity")
startActivity(Intent(this, StartupActivity::class.java))
finish()
return false
}
return true
}

View File

@@ -62,10 +62,12 @@ object Codec {
const val MPEG2VIDEO = "mpeg2video"
const val VP8 = "vp8"
const val VP9 = "vp9"
const val AV1 = "av1"
}
object Subtitle {
const val ASS = "ass"
const val DVBSUB = "dvbsub"
const val DVDSUB = "dvdsub"
const val IDX = "idx"
const val PGS = "pgs"

View File

@@ -1,6 +1,6 @@
package org.jellyfin.androidtv.constant
sealed interface CustomMessage {
object RefreshCurrentItem : CustomMessage
object ActionComplete : CustomMessage
data object RefreshCurrentItem : CustomMessage
data object ActionComplete : CustomMessage
}

View File

@@ -1,18 +1,18 @@
package org.jellyfin.androidtv.constant
import org.jellyfin.androidtv.R
import org.jellyfin.androidtv.ui.preference.dsl.EnumDisplayOptions
import org.jellyfin.preference.PreferenceEnum
enum class GridDirection {
enum class GridDirection(
override val nameRes: Int,
) : PreferenceEnum {
/**
* Horizontal.
*/
@EnumDisplayOptions(R.string.grid_direction_horizontal)
HORIZONTAL,
HORIZONTAL(R.string.grid_direction_horizontal),
/**
* Vertical.
*/
@EnumDisplayOptions(R.string.grid_direction_vertical)
VERTICAL,
VERTICAL(R.string.grid_direction_vertical),
}

View File

@@ -1,7 +1,6 @@
package org.jellyfin.androidtv.constant
import org.jellyfin.androidtv.R
import org.jellyfin.androidtv.ui.preference.dsl.EnumDisplayOptions
import org.jellyfin.preference.PreferenceEnum
/**
@@ -10,35 +9,17 @@ import org.jellyfin.preference.PreferenceEnum
* https://github.com/jellyfin/jellyfin-web/blob/master/src/components/homesections/homesections.js
*/
enum class HomeSectionType(
override val serializedName: String
override val serializedName: String,
override val nameRes: Int,
) : PreferenceEnum {
@EnumDisplayOptions(R.string.home_section_latest_media)
LATEST_MEDIA("latestmedia"),
@EnumDisplayOptions(R.string.home_section_library)
LIBRARY_TILES_SMALL("smalllibrarytiles"),
@EnumDisplayOptions(R.string.home_section_library_small)
LIBRARY_BUTTONS("librarybuttons"),
@EnumDisplayOptions(R.string.home_section_resume)
RESUME("resume"),
@EnumDisplayOptions(R.string.home_section_resume_audio)
RESUME_AUDIO("resumeaudio"),
@EnumDisplayOptions(R.string.home_section_resume_book)
RESUME_BOOK("resumebook"),
@EnumDisplayOptions(R.string.home_section_active_recordings)
ACTIVE_RECORDINGS("activerecordings"),
@EnumDisplayOptions(R.string.home_section_next_up)
NEXT_UP("nextup"),
@EnumDisplayOptions(R.string.home_section_livetv)
LIVE_TV("livetv"),
@EnumDisplayOptions(R.string.home_section_none)
NONE("none"),
LATEST_MEDIA("latestmedia", R.string.home_section_latest_media),
LIBRARY_TILES_SMALL("smalllibrarytiles", R.string.home_section_library),
LIBRARY_BUTTONS("librarybuttons", R.string.home_section_library_small),
RESUME("resume", R.string.home_section_resume),
RESUME_AUDIO("resumeaudio", R.string.home_section_resume_audio),
RESUME_BOOK("resumebook", R.string.home_section_resume_book),
ACTIVE_RECORDINGS("activerecordings", R.string.home_section_active_recordings),
NEXT_UP("nextup", R.string.home_section_next_up),
LIVE_TV("livetv", R.string.home_section_livetv),
NONE("none", R.string.home_section_none),
}

View File

@@ -1,24 +1,23 @@
package org.jellyfin.androidtv.constant
import org.jellyfin.androidtv.R
import org.jellyfin.androidtv.ui.preference.dsl.EnumDisplayOptions
import org.jellyfin.preference.PreferenceEnum
enum class ImageType {
enum class ImageType(
override val nameRes: Int,
) : PreferenceEnum {
/**
* Poster.
*/
@EnumDisplayOptions(R.string.image_type_poster)
POSTER,
POSTER(R.string.image_type_poster),
/**
* Thumbnail.
*/
@EnumDisplayOptions(R.string.image_type_thumbnail)
THUMB,
THUMB(R.string.image_type_thumbnail),
/**
* Banner.
*/
@EnumDisplayOptions(R.string.image_type_banner)
BANNER
BANNER(R.string.image_type_banner),
}

View File

@@ -1,35 +1,32 @@
package org.jellyfin.androidtv.constant
import org.jellyfin.androidtv.R
import org.jellyfin.androidtv.ui.preference.dsl.EnumDisplayOptions
import org.jellyfin.preference.PreferenceEnum
enum class PosterSize {
enum class PosterSize(
override val nameRes: Int,
) : PreferenceEnum {
/**
* Smallest.
*/
@EnumDisplayOptions(R.string.image_size_smallest)
SMALLEST,
SMALLEST(R.string.image_size_smallest),
/**
* Small.
*/
@EnumDisplayOptions(R.string.image_size_small)
SMALL,
SMALL(R.string.image_size_small),
/**
* Medium.
*/
@EnumDisplayOptions(R.string.image_size_medium)
MED,
MED(R.string.image_size_medium),
/**
* Large.
*/
@EnumDisplayOptions(R.string.image_size_large)
LARGE,
LARGE(R.string.image_size_large),
/**
* Extra Large.
*/
@EnumDisplayOptions(R.string.image_size_xlarge)
X_LARGE
X_LARGE(R.string.image_size_xlarge),
}

View File

@@ -20,9 +20,11 @@ enum class QueryType {
StaticItems,
Persons,
StaticAudioQueueItems,
Artists,
AlbumArtists,
AudioPlaylists,
LatestItems,
SeriesTimer,
Premieres,
Resume,
}

View File

@@ -1,6 +1,8 @@
package org.jellyfin.androidtv.data.eventhandling
import android.content.Context
import android.media.AudioManager
import android.os.Build
import android.widget.Toast
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
@@ -12,6 +14,7 @@ import org.jellyfin.androidtv.ui.navigation.Destinations
import org.jellyfin.androidtv.ui.navigation.NavigationRepository
import org.jellyfin.androidtv.ui.playback.MediaManager
import org.jellyfin.androidtv.ui.playback.PlaybackControllerContainer
import org.jellyfin.androidtv.ui.playback.rewrite.RewriteMediaManager
import org.jellyfin.androidtv.util.apiclient.PlaybackHelper
import org.jellyfin.sdk.api.client.ApiClient
import org.jellyfin.sdk.api.client.exception.ApiClientException
@@ -42,6 +45,7 @@ class SocketHandler(
private val mediaManager: MediaManager,
private val playbackControllerContainer: PlaybackControllerContainer,
private val navigationRepository: NavigationRepository,
private val audioManager: AudioManager,
) {
private val coroutineScope = CoroutineScope(Dispatchers.IO)
val state = socketInstance.state
@@ -51,11 +55,23 @@ class SocketHandler(
api.sessionApi.postCapabilities(
playableMediaTypes = listOf(MediaType.Video, MediaType.Audio),
supportsMediaControl = true,
supportedCommands = listOf(
GeneralCommandType.DISPLAY_MESSAGE,
GeneralCommandType.SEND_STRING,
GeneralCommandType.DISPLAY_CONTENT,
),
supportedCommands = buildList {
add(GeneralCommandType.DISPLAY_CONTENT)
add(GeneralCommandType.DISPLAY_MESSAGE)
add(GeneralCommandType.SEND_STRING)
// Note: These are used in the PlaySessionSocketService
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && !audioManager.isVolumeFixed) {
add(GeneralCommandType.VOLUME_UP)
add(GeneralCommandType.VOLUME_DOWN)
add(GeneralCommandType.SET_VOLUME)
add(GeneralCommandType.MUTE)
add(GeneralCommandType.UNMUTE)
add(GeneralCommandType.TOGGLE_MUTE)
}
},
)
} catch (err: ApiClientException) {
Timber.e(err, "Unable to update capabilities")
@@ -111,48 +127,65 @@ class SocketHandler(
private fun onPlayMessage(message: PlayMessage) {
val itemId = message.request.itemIds?.firstOrNull() ?: return
PlaybackHelper.retrieveAndPlay(
itemId.toString(),
false,
message.request.startPositionTicks,
context
)
runCatching {
PlaybackHelper.retrieveAndPlay(
itemId.toString(),
false,
message.request.startPositionTicks,
context
)
}.onFailure { Timber.w(it, "Failed to start playback") }
}
@Suppress("ComplexMethod")
private fun onPlayStateMessage(message: PlayStateMessage) = coroutineScope.launch(Dispatchers.Main) {
Timber.i("Received PlayStateMessage with command ${message.request.command}")
val playbackController = playbackControllerContainer.playbackController
// Audio playback uses the mediaManager, video playback and live tv use the playbackController
if (mediaManager.isAudioPlayerInitialized) when (message.request.command) {
PlaystateCommand.STOP -> mediaManager.stopAudio(true)
PlaystateCommand.PAUSE, PlaystateCommand.UNPAUSE, PlaystateCommand.PLAY_PAUSE -> mediaManager.playPauseAudio()
PlaystateCommand.NEXT_TRACK -> mediaManager.nextAudioItem()
PlaystateCommand.PREVIOUS_TRACK -> mediaManager.prevAudioItem()
// Not implemented
PlaystateCommand.SEEK,
PlaystateCommand.REWIND,
PlaystateCommand.FAST_FORWARD -> Unit
} else when (message.request.command) {
PlaystateCommand.STOP -> playbackController?.endPlayback(true)
PlaystateCommand.PAUSE, PlaystateCommand.UNPAUSE, PlaystateCommand.PLAY_PAUSE -> playbackController?.playPause()
PlaystateCommand.NEXT_TRACK -> playbackController?.next()
PlaystateCommand.PREVIOUS_TRACK -> playbackController?.prev()
PlaystateCommand.SEEK -> playbackController?.seek(
(message.request.seekPositionTicks ?: 0) / TICKS_TO_MS
)
// FIXME get rewind/forward amount from displayprefs
PlaystateCommand.REWIND -> playbackController?.skip(REWIND_MS)
PlaystateCommand.FAST_FORWARD -> playbackController?.skip(FORWARD_MS)
// Audio playback uses (Rewrite)MediaManager, (legacy) video playback uses playbackController
when {
// Ignore RewriteMediaManager
mediaManager is RewriteMediaManager && mediaManager.hasAudioQueueItems() -> {
Timber.i("Ignoring PlayStateMessage: should be handled by PlaySessionSocketService")
return@launch
}
// LegacyMediaManager
mediaManager.hasAudioQueueItems() -> when (message.request.command) {
PlaystateCommand.STOP -> mediaManager.stopAudio(true)
PlaystateCommand.PAUSE, PlaystateCommand.UNPAUSE, PlaystateCommand.PLAY_PAUSE -> mediaManager.playPauseAudio()
PlaystateCommand.NEXT_TRACK -> mediaManager.nextAudioItem()
PlaystateCommand.PREVIOUS_TRACK -> mediaManager.prevAudioItem()
// Not implemented
PlaystateCommand.SEEK,
PlaystateCommand.REWIND,
PlaystateCommand.FAST_FORWARD -> Unit
}
// PlaybackController
else -> {
val playbackController = playbackControllerContainer.playbackController
when (message.request.command) {
PlaystateCommand.STOP -> playbackController?.endPlayback(true)
PlaystateCommand.PAUSE, PlaystateCommand.UNPAUSE, PlaystateCommand.PLAY_PAUSE -> playbackController?.playPause()
PlaystateCommand.NEXT_TRACK -> playbackController?.next()
PlaystateCommand.PREVIOUS_TRACK -> playbackController?.prev()
PlaystateCommand.SEEK -> playbackController?.seek(
(message.request.seekPositionTicks ?: 0) / TICKS_TO_MS
)
PlaystateCommand.REWIND -> playbackController?.rewind()
PlaystateCommand.FAST_FORWARD -> playbackController?.fastForward()
}
}
}
}
private fun onDisplayContent(itemId: UUID, itemKind: BaseItemKind) {
private fun onDisplayContent(itemId: UUID, itemKind: BaseItemKind) = coroutineScope.launch(Dispatchers.Main) {
val playbackController = playbackControllerContainer.playbackController
if (playbackController?.isPlaying == true || playbackController?.isPaused == true) {
Timber.i("Not launching $itemId: playback in progress")
return
return@launch
}
Timber.i("Launching $itemId")
@@ -163,6 +196,7 @@ class SocketHandler(
val item by api.userLibraryApi.getItem(itemId = itemId)
ItemLauncher.launchUserView(item)
}
else -> navigationRepository.navigate(Destinations.itemDetails(itemId))
}
}
@@ -180,7 +214,5 @@ class SocketHandler(
companion object {
const val TICKS_TO_MS = 10000L
const val REWIND_MS = -11000
const val FORWARD_MS = 30000
}
}

View File

@@ -3,4 +3,5 @@ package org.jellyfin.androidtv.data.model
data class AppNotification(
val message: String,
val dismiss: () -> Unit,
val public: Boolean,
)

View File

@@ -1,9 +1,10 @@
package org.jellyfin.androidtv.data.model
import org.jellyfin.sdk.model.api.BaseItemDto
import java.util.UUID
class DataRefreshService {
var lastDeletedItemId: String? = null
var lastDeletedItemId: UUID? = null
var lastPlayback: Long = 0
var lastMoviePlayback: Long = 0
var lastTvPlayback: Long = 0

View File

@@ -0,0 +1,5 @@
package org.jellyfin.androidtv.data.querying
import org.jellyfin.apiclient.model.querying.ArtistsQuery
class AlbumArtistsQuery : ArtistsQuery()

View File

@@ -2,13 +2,13 @@ package org.jellyfin.androidtv.data.repository
import android.app.UiModeManager
import android.content.Context
import android.content.res.Configuration
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import org.jellyfin.androidtv.BuildConfig
import org.jellyfin.androidtv.R
import org.jellyfin.androidtv.data.model.AppNotification
import org.jellyfin.androidtv.preference.SystemPreferences
import org.jellyfin.androidtv.util.isTvDevice
interface NotificationsRepository {
val notifications: StateFlow<List<AppNotification>>
@@ -34,18 +34,15 @@ class NotificationsRepositoryImpl(
addBetaNotification()
}
private fun addNotification(message: String, dismiss: () -> Unit = {}) {
notifications.value = notifications.value + AppNotification(message, dismiss)
private fun addNotification(message: String, public: Boolean = false, dismiss: () -> Unit = {}) {
notifications.value = notifications.value + AppNotification(message, dismiss, public)
}
private fun addUiModeNotification() {
val supportedUiModes = setOf(Configuration.UI_MODE_TYPE_TELEVISION, Configuration.UI_MODE_TYPE_UNDEFINED)
val invalidUiMode = !supportedUiModes.contains(uiModeManager.currentModeType)
val isTouch = context.packageManager.hasSystemFeature("android.hardware.touchscreen")
val hasHdmiCec = context.packageManager.hasSystemFeature("android.hardware.hdmi.cec")
val disableUiModeWarning = systemPreferences[SystemPreferences.disableUiModeWarning]
if (invalidUiMode && isTouch && !hasHdmiCec) {
addNotification(context.getString(R.string.app_notification_uimode_invalid))
if (!context.isTvDevice() && !disableUiModeWarning) {
addNotification(context.getString(R.string.app_notification_uimode_invalid), public = true)
}
}

View File

@@ -1,33 +1,18 @@
package org.jellyfin.androidtv.data.service
import android.animation.ValueAnimator
import android.content.Context
import android.graphics.Color
import android.graphics.drawable.ColorDrawable
import android.graphics.drawable.Drawable
import android.graphics.drawable.LayerDrawable
import android.util.Size
import android.view.animation.AccelerateDecelerateInterpolator
import androidx.annotation.MainThread
import androidx.core.animation.doOnEnd
import androidx.core.content.ContextCompat
import androidx.core.graphics.BlendModeColorFilterCompat
import androidx.core.graphics.BlendModeCompat
import androidx.compose.ui.graphics.ImageBitmap
import androidx.compose.ui.graphics.asImageBitmap
import androidx.core.graphics.drawable.toBitmap
import androidx.core.graphics.drawable.toDrawable
import androidx.fragment.app.FragmentActivity
import androidx.fragment.app.add
import androidx.window.layout.WindowMetricsCalculator
import com.bumptech.glide.Glide
import coil.ImageLoader
import coil.request.ImageRequest
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.MainScope
import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.jellyfin.androidtv.R
import org.jellyfin.androidtv.auth.model.Server
import org.jellyfin.androidtv.preference.UserPreferences
import org.jellyfin.sdk.Jellyfin
@@ -35,98 +20,34 @@ import org.jellyfin.sdk.api.client.ApiClient
import org.jellyfin.sdk.api.client.extensions.imageApi
import org.jellyfin.sdk.model.api.BaseItemDto
import org.jellyfin.sdk.model.api.ImageType
import org.jellyfin.sdk.model.api.SearchHint
import org.jellyfin.sdk.model.serializer.toUUID
import timber.log.Timber
import java.util.UUID
import java.util.concurrent.ExecutionException
import kotlin.time.Duration
import kotlin.time.Duration.Companion.milliseconds
import kotlin.time.Duration.Companion.seconds
class BackgroundService(
private val context: Context,
private val jellyfin: Jellyfin,
private val api: ApiClient,
private val userPreferences: UserPreferences,
private val imageLoader: ImageLoader,
) {
companion object {
const val TRANSITION_DURATION = 400L // 0.4 seconds
const val SLIDESHOW_DURATION = 30000L // 30 seconds
const val UPDATE_INTERVAL = 500L // 0.5 seconds
val FRAGMENT_TAG = BackgroundServiceFragment::class.qualifiedName!!
val SLIDESHOW_DURATION = 30.seconds
val TRANSITION_DURATION = 800.milliseconds
}
// Async
private val scope = MainScope()
private var loadBackgroundsJob: Job? = null
private var updateBackgroundTimerJob: Job? = null
private var lastBackgroundUpdate = 0L
private var lastBackgroundTimerUpdate = 0L
// All background drawables currently showing
internal val backgrounds = mutableListOf<Drawable>()
// Current background index
private var currentIndex = 0
// Preferred display size, set when calling [attach].
private var windowSize = Size(0, 0)
private var windowBackground: Drawable = ColorDrawable(Color.BLACK)
// Background layers
internal val backgroundDrawable = ContextCompat.getDrawable(context, R.drawable.layer_background) as LayerDrawable
// Filter to darken backgrounds
private val colorFilter = BlendModeColorFilterCompat.createBlendModeColorFilterCompat(
ContextCompat.getColor(context, R.color.background_filter),
BlendModeCompat.SRC_ATOP
)
// Animation
@Suppress("MagicNumber")
private val backgroundAnimator = ValueAnimator.ofInt(0, 255).apply {
interpolator = AccelerateDecelerateInterpolator()
duration = TRANSITION_DURATION
addUpdateListener { animation ->
// Set alpha
val value = animation.animatedValue as Int
backgroundDrawable.findDrawableByLayerId(R.id.background_next).alpha = value
backgroundDrawable.invalidateSelf()
}
doOnEnd {
// Set next as current and clear next
val drawable = backgroundDrawable.findDrawableByLayerId(R.id.background_next)
backgroundDrawable.setDrawableByLayerId(R.id.background_current, drawable)
backgroundDrawable.setDrawableByLayerId(R.id.background_next, ColorDrawable(Color.TRANSPARENT))
backgroundDrawable.invalidateSelf()
}
}
/**
* Attach the bakground to [activity].
*/
fun attach(activity: FragmentActivity) {
// Set default background to current if it's not layered
val current = activity.window.decorView.background
windowBackground = if (current !is LayerDrawable) current.copy() else ColorDrawable(Color.BLACK)
backgroundDrawable.setDrawableByLayerId(R.id.background_static, windowBackground)
// Store size of window manager for this activity
windowSize = WindowMetricsCalculator.getOrCreate().computeCurrentWindowMetrics(activity).let {
Size(it.bounds.width(), it.bounds.height())
}
// Add a fragment to the activity to automatically set the background on resume
val fragment = activity.supportFragmentManager.findFragmentByTag(FRAGMENT_TAG)
if (fragment == null) {
// Add fragment
Timber.i("Adding BackgroundServiceFragment to activity")
activity.supportFragmentManager
.beginTransaction()
.add<BackgroundServiceFragment>(FRAGMENT_TAG)
.commit()
}
}
// Current background data
private var _backgrounds = emptyList<ImageBitmap>()
private var _currentIndex = 0
private var _currentBackground = MutableStateFlow<ImageBitmap?>(null)
val currentBackground get() = _currentBackground.asStateFlow()
// Helper function for [setBackground]
private fun List<String>?.getUrls(itemId: UUID?): List<String> {
@@ -139,6 +60,8 @@ class BackgroundService(
imageType = ImageType.BACKDROP,
tag = tag,
imageIndex = index,
fillWidth = context.resources.displayMetrics.widthPixels,
fillHeight = context.resources.displayMetrics.heightPixels,
)
}
}
@@ -159,26 +82,6 @@ class BackgroundService(
loadBackgrounds(backdropUrls)
}
/**
* Use backdrop from [searchHint] as background.
*/
fun setBackground(searchHint: SearchHint) {
// Check if item is set and backgrounds are enabled
if (!userPreferences[UserPreferences.backdropEnabled])
return clearBackgrounds()
// Manually grab the backdrop URL
val backdropUrls = setOfNotNull(searchHint.backdropImageItemId?.let { itemId ->
api.imageApi.getItemImageUrl(
itemId = itemId.toUUID(),
imageType = ImageType.BACKDROP,
tag = searchHint.backdropImageTag,
)
})
loadBackgrounds(backdropUrls)
}
/**
* Use splashscreen from [server] as background.
*/
@@ -194,6 +97,7 @@ class BackgroundService(
// Manually grab the backdrop URL
val api = jellyfin.createApi(baseUrl = server.address)
val splashscreenUrl = api.imageApi.getSplashscreenUrl()
loadBackgrounds(setOf(splashscreenUrl))
}
@@ -203,92 +107,53 @@ class BackgroundService(
// Cancel current loading job
loadBackgroundsJob?.cancel()
loadBackgroundsJob = scope.launch(Dispatchers.IO) {
val backdropDrawables = backdropUrls
.map { url ->
Glide.with(context)
.load(url)
.override(windowSize.width, windowSize.height)
.centerCrop()
.submit()
}
.map { future ->
async {
try {
future.get()
} catch (ex: ExecutionException) {
Timber.e(ex, "There was an error fetching the background image.")
null
}
}
}
.awaitAll()
.filterNotNull()
.onEach { it.colorFilter = colorFilter }
backgrounds.clear()
backgrounds.addAll(backdropDrawables)
withContext(Dispatchers.Main) {
// Go to first background
currentIndex = 0
update()
_backgrounds = backdropUrls.mapNotNull { url ->
imageLoader.execute(
request = ImageRequest.Builder(context).data(url).build()
).drawable?.toBitmap()?.asImageBitmap()
}
// Go to first background
_currentIndex = 0
update()
}
}
fun clearBackgrounds() {
loadBackgroundsJob?.cancel()
if (backgrounds.isEmpty()) return
if (_backgrounds.isEmpty()) return
backgrounds.clear()
_backgrounds = emptyList()
update()
}
@MainThread
internal fun update() {
val now = System.currentTimeMillis()
if (lastBackgroundUpdate > now - UPDATE_INTERVAL)
return setTimer(lastBackgroundUpdate - now + UPDATE_INTERVAL, false)
if (lastBackgroundTimerUpdate > now - TRANSITION_DURATION.inWholeMilliseconds)
return setTimer((lastBackgroundTimerUpdate - now).milliseconds + TRANSITION_DURATION, false)
lastBackgroundUpdate = now
// Snapshot the current state if an animation is running and draw the new
// background on top.
if (backgroundAnimator.isRunning) {
val current = backgroundDrawable
.toBitmap(windowSize.width, windowSize.height)
.toDrawable(context.resources)
backgroundAnimator.end()
backgroundDrawable.setDrawableByLayerId(R.id.background_current, current)
}
lastBackgroundTimerUpdate = now
// Get next background to show
if (currentIndex >= backgrounds.size) currentIndex = 0
if (_currentIndex >= _backgrounds.size) _currentIndex = 0
backgroundDrawable.setDrawableByLayerId(
R.id.background_next,
backgrounds.getOrElse(currentIndex) { windowBackground.copy() }
)
// Animate
backgroundAnimator.start()
// Set background
_currentBackground.value = _backgrounds.getOrNull(_currentIndex)
// Set timer for next background
if (backgrounds.size > 1) setTimer()
if (_backgrounds.size > 1) setTimer()
else updateBackgroundTimerJob?.cancel()
}
private fun setTimer(updateDelay: Long = SLIDESHOW_DURATION, increaseIndex: Boolean = true) {
private fun setTimer(updateDelay: Duration = SLIDESHOW_DURATION, increaseIndex: Boolean = true) {
updateBackgroundTimerJob?.cancel()
updateBackgroundTimerJob = scope.launch {
delay(updateDelay)
if (increaseIndex) currentIndex++
if (increaseIndex) _currentIndex++
update()
}
}
private fun Drawable.copy() = constantState!!.newDrawable().mutate()
}

View File

@@ -1,43 +0,0 @@
package org.jellyfin.androidtv.data.service
import android.graphics.drawable.Drawable
import androidx.fragment.app.Fragment
import org.koin.android.ext.android.inject
import timber.log.Timber
/**
* Fragment used to hook into the activity lifecycle and set the background when neccesary.
*/
internal class BackgroundServiceFragment : Fragment() {
private val backgroundService by inject<BackgroundService>()
private var backgrounds: Array<Drawable>? = null
override fun onResume() {
super.onResume()
if (backgrounds != null) {
Timber.d("Restoring active backgrounds")
backgroundService.backgrounds.clear()
backgroundService.backgrounds.addAll(backgrounds!!)
backgroundService.update()
}
activity?.window?.decorView?.apply {
Timber.d("Restoring background drawable")
// We need to force the system to add a new callback
// this won't happen if we set the background to the same one
// so we set it to null first
if (background == backgroundService.backgroundDrawable) background = null
background = backgroundService.backgroundDrawable
}
}
override fun onPause() {
super.onPause()
backgrounds = backgroundService.backgrounds.toTypedArray()
}
}

View File

@@ -1,7 +1,7 @@
package org.jellyfin.androidtv.di
import android.accounts.AccountManager
import android.app.UiModeManager
import android.media.AudioManager
import androidx.core.content.getSystemService
import androidx.work.WorkManager
import org.koin.android.ext.koin.androidApplication
@@ -11,7 +11,7 @@ import org.koin.dsl.module
* Provides DI for Android system components
*/
val androidModule = module {
factory { androidApplication().getSystemService<AccountManager>()!! }
factory { androidApplication().getSystemService<UiModeManager>()!! }
factory { androidApplication().getSystemService<AudioManager>()!! }
factory { WorkManager.getInstance(get()) }
}

View File

@@ -1,5 +1,11 @@
package org.jellyfin.androidtv.di
import android.content.Context
import android.os.Build
import coil.ImageLoader
import coil.decode.GifDecoder
import coil.decode.ImageDecoderDecoder
import coil.decode.SvgDecoder
import org.jellyfin.androidtv.BuildConfig
import org.jellyfin.androidtv.auth.repository.ServerRepository
import org.jellyfin.androidtv.auth.repository.UserRepository
@@ -15,13 +21,17 @@ import org.jellyfin.androidtv.data.repository.NotificationsRepositoryImpl
import org.jellyfin.androidtv.data.repository.UserViewsRepository
import org.jellyfin.androidtv.data.repository.UserViewsRepositoryImpl
import org.jellyfin.androidtv.data.service.BackgroundService
import org.jellyfin.androidtv.ui.ScreensaverViewModel
import org.jellyfin.androidtv.ui.navigation.Destinations
import org.jellyfin.androidtv.ui.navigation.NavigationRepository
import org.jellyfin.androidtv.ui.navigation.NavigationRepositoryImpl
import org.jellyfin.androidtv.ui.picture.PictureViewerViewModel
import org.jellyfin.androidtv.ui.playback.MediaManager
import org.jellyfin.androidtv.ui.playback.PlaybackControllerContainer
import org.jellyfin.androidtv.ui.playback.nextup.NextUpViewModel
import org.jellyfin.androidtv.ui.search.SearchFragmentDelegate
import org.jellyfin.androidtv.ui.search.SearchRepository
import org.jellyfin.androidtv.ui.search.SearchRepositoryImpl
import org.jellyfin.androidtv.ui.search.SearchViewModel
import org.jellyfin.androidtv.ui.startup.ServerAddViewModel
import org.jellyfin.androidtv.ui.startup.StartupViewModel
import org.jellyfin.androidtv.ui.startup.UserLoginViewModel
@@ -68,7 +78,7 @@ val appModule = module {
single { get<ApiClient>().ws() }
single { SocketHandler(get(), get(), get(), get(), get(), get(), get()) }
single { SocketHandler(get(), get(), get(), get(), get(), get(), get(), get()) }
// Old apiclient
single {
@@ -85,9 +95,18 @@ val appModule = module {
)
}
// Non API related
single { MediaManager(get()) }
// Coil (images)
single {
ImageLoader.Builder(androidContext()).apply {
components {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) add(ImageDecoderDecoder.Factory())
else add(GifDecoder.Factory())
add(SvgDecoder.Factory())
}
}.build()
}
// Non API related
single { DataRefreshService() }
single { PlaybackControllerContainer() }
@@ -97,14 +116,19 @@ val appModule = module {
single<ItemMutationRepository> { ItemMutationRepositoryImpl(get(), get()) }
single<CustomMessageRepository> { CustomMessageRepositoryImpl() }
single<NavigationRepository> { NavigationRepositoryImpl(Destinations.home) }
single<SearchRepository> { SearchRepositoryImpl(get()) }
viewModel { StartupViewModel(get(), get(), get(), get()) }
viewModel { UserLoginViewModel(get(), get(), get(), get(defaultDeviceInfo)) }
viewModel { ServerAddViewModel(get()) }
viewModel { NextUpViewModel(get(), get(), get(), get()) }
viewModel { PictureViewerViewModel(get()) }
viewModel { ScreensaverViewModel(get()) }
viewModel { SearchViewModel(get()) }
single { BackgroundService(get(), get(), get(), get()) }
single { BackgroundService(get(), get(), get(), get(), get()) }
single { MarkdownRenderer(get()) }
factory { (context: Context) -> SearchFragmentDelegate(context, get()) }
}

View File

@@ -1,5 +1,6 @@
package org.jellyfin.androidtv.di
import org.jellyfin.androidtv.auth.AccountManagerMigration
import org.jellyfin.androidtv.auth.apiclient.ApiBinder
import org.jellyfin.androidtv.auth.repository.AuthenticationRepository
import org.jellyfin.androidtv.auth.repository.AuthenticationRepositoryImpl
@@ -9,23 +10,22 @@ import org.jellyfin.androidtv.auth.repository.ServerUserRepository
import org.jellyfin.androidtv.auth.repository.ServerUserRepositoryImpl
import org.jellyfin.androidtv.auth.repository.SessionRepository
import org.jellyfin.androidtv.auth.repository.SessionRepositoryImpl
import org.jellyfin.androidtv.auth.store.AccountManagerStore
import org.jellyfin.androidtv.auth.store.AuthenticationPreferences
import org.jellyfin.androidtv.auth.store.AuthenticationStore
import org.koin.dsl.module
val authModule = module {
single { AuthenticationStore(get()) }
single { AccountManagerStore(get()) }
single { AccountManagerMigration(get()) }
single { AuthenticationStore(get(), get()) }
single { AuthenticationPreferences(get()) }
single<AuthenticationRepository> {
AuthenticationRepositoryImpl(get(), get(), get(), get(), get(), get(), get(defaultDeviceInfo))
AuthenticationRepositoryImpl(get(), get(), get(), get(), get(), get(defaultDeviceInfo))
}
single<ServerRepository> { ServerRepositoryImpl(get(), get()) }
single<ServerUserRepository> { ServerUserRepositoryImpl(get(), get(), get()) }
single<ServerUserRepository> { ServerUserRepositoryImpl(get(), get()) }
single<SessionRepository> {
SessionRepositoryImpl(get(), get(), get(), get(), get(), get(), get(defaultDeviceInfo), get(), get(), get())
SessionRepositoryImpl(get(), get(), get(), get(), get(), get(defaultDeviceInfo), get(), get(), get())
}
single { ApiBinder(get(), get()) }

View File

@@ -1,22 +1,83 @@
package org.jellyfin.androidtv.di
import android.app.NotificationChannel
import android.app.NotificationManager
import android.app.PendingIntent
import android.content.Intent
import android.os.Build
import androidx.core.app.NotificationManagerCompat
import org.jellyfin.androidtv.BuildConfig
import org.jellyfin.androidtv.R
import org.jellyfin.androidtv.preference.UserPreferences
import org.jellyfin.androidtv.preference.UserSettingPreferences
import org.jellyfin.androidtv.ui.browsing.MainActivity
import org.jellyfin.androidtv.ui.playback.GarbagePlaybackLauncher
import org.jellyfin.androidtv.ui.playback.PlaybackManager
import org.jellyfin.androidtv.ui.playback.LegacyMediaManager
import org.jellyfin.androidtv.ui.playback.RewritePlaybackLauncher
import org.jellyfin.androidtv.ui.playback.VideoQueueManager
import org.jellyfin.androidtv.ui.playback.rewrite.RewriteMediaManager
import org.jellyfin.playback.core.mediasession.MediaSessionOptions
import org.jellyfin.playback.core.mediasession.mediaSessionPlugin
import org.jellyfin.playback.core.playbackManager
import org.jellyfin.playback.exoplayer.exoPlayerPlugin
import org.jellyfin.playback.jellyfin.jellyfinPlugin
import org.koin.android.ext.koin.androidContext
import org.koin.core.scope.Scope
import org.koin.dsl.module
import kotlin.time.Duration.Companion.milliseconds
import org.jellyfin.androidtv.ui.playback.PlaybackManager as LegacyPlaybackManager
val playbackModule = module {
single {
PlaybackManager(get())
single { LegacyPlaybackManager(get()) }
single { VideoQueueManager() }
single { LegacyMediaManager(get()) }
single { RewriteMediaManager(get(), get(), get(), get()) }
factory {
val preferences = get<UserPreferences>()
val useRewrite = preferences[UserPreferences.playbackRewriteAudioEnabled]
if (useRewrite) get<RewriteMediaManager>()
else get<LegacyMediaManager>()
}
factory {
val preferences = get<UserPreferences>()
val useRewrite = preferences[UserPreferences.playbackRewriteEnabled] && BuildConfig.DEVELOPMENT
val useRewrite = preferences[UserPreferences.playbackRewriteVideoEnabled] && BuildConfig.DEVELOPMENT
if (useRewrite) RewritePlaybackLauncher()
else GarbagePlaybackLauncher(get())
}
single { createPlaybackManager() }
}
fun Scope.createPlaybackManager() = playbackManager(androidContext()) {
install(exoPlayerPlugin(get()))
install(jellyfinPlugin(get(), get()))
val activityIntent = Intent(get(), MainActivity::class.java)
val pendingIntent = PendingIntent.getActivity(get(), 0, activityIntent, PendingIntent.FLAG_IMMUTABLE)
val notificationChannelId = "mediasession"
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val channel = NotificationChannel(
notificationChannelId,
notificationChannelId,
NotificationManager.IMPORTANCE_LOW
)
NotificationManagerCompat.from(get()).createNotificationChannel(channel)
}
install(mediaSessionPlugin(get(), MediaSessionOptions(
channelId = notificationChannelId,
notificationId = 1,
iconSmall = R.drawable.app_icon_foreground,
openIntent = pendingIntent,
)))
// Options
val userSettingPreferences = get<UserSettingPreferences>()
defaultRewindAmount = { userSettingPreferences[UserSettingPreferences.skipBackLength].milliseconds }
defaultFastForwardAmount = { userSettingPreferences[UserSettingPreferences.skipForwardLength].milliseconds }
}

View File

@@ -1,13 +1,14 @@
package org.jellyfin.androidtv.integration
import android.annotation.SuppressLint
import android.content.ContentUris
import android.content.ContentValues
import android.content.Context
import android.content.Intent
import android.net.Uri
import android.os.Build
import androidx.core.content.res.ResourcesCompat
import androidx.core.graphics.drawable.toBitmap
import androidx.core.net.toUri
import androidx.tvprovider.media.tv.Channel
import androidx.tvprovider.media.tv.ChannelLogoUtils
import androidx.tvprovider.media.tv.PreviewProgram
@@ -33,6 +34,7 @@ import org.jellyfin.sdk.api.client.exception.TimeoutException
import org.jellyfin.sdk.api.client.extensions.imageApi
import org.jellyfin.sdk.api.client.extensions.itemsApi
import org.jellyfin.sdk.api.client.extensions.tvShowsApi
import org.jellyfin.sdk.api.client.extensions.userLibraryApi
import org.jellyfin.sdk.api.client.extensions.userViewsApi
import org.jellyfin.sdk.model.api.BaseItemDto
import org.jellyfin.sdk.model.api.BaseItemKind
@@ -40,10 +42,14 @@ import org.jellyfin.sdk.model.api.ImageFormat
import org.jellyfin.sdk.model.api.ImageType
import org.jellyfin.sdk.model.api.ItemFields
import org.jellyfin.sdk.model.constant.MediaType
import org.jellyfin.sdk.model.extensions.ticks
import org.koin.core.component.KoinComponent
import org.koin.core.component.inject
import timber.log.Timber
import java.time.ZoneOffset
import java.time.format.DateTimeFormatter
import kotlin.time.Duration
/**
* Manages channels on the android tv home screen.
@@ -55,11 +61,6 @@ class LeanbackChannelWorker(
workerParams: WorkerParameters,
) : CoroutineWorker(context, workerParams), KoinComponent {
companion object {
/**
* Amount of ticks found in a millisecond, used for calculation.
*/
private const val TICKS_IN_MILLISECOND = 10000
const val PERIODIC_UPDATE_REQUEST_NAME = "LeanbackChannelPeriodicUpdateRequest"
}
@@ -87,13 +88,73 @@ class LeanbackChannelWorker(
else -> try {
// Get next up episodes
val (resumeItems, nextUpItems) = getNextUpItems()
// Delete current items from the My Media and Next Up channels
// Get latest media
val (latestEpisodes, latestMovies, latestMedia) = getLatestMedia()
val myMedia = getMyMedia()
// Delete current items from the channels
context.contentResolver.delete(TvContractCompat.PreviewPrograms.CONTENT_URI, null, null)
// Update various channels
updateMyMedia()
updateNextUp(nextUpItems)
// Get channel URIs
val latestMediaChannel = getChannelUri(
"latest_media", Channel.Builder()
.setType(TvContractCompat.Channels.TYPE_PREVIEW)
.setDisplayName(context.getString(R.string.home_section_latest_media))
.setAppLinkIntent(Intent(context, StartupActivity::class.java))
.build(),
default = true
)
val myMediaChannel = getChannelUri(
"my_media", Channel.Builder()
.setType(TvContractCompat.Channels.TYPE_PREVIEW)
.setDisplayName(context.getString(R.string.lbl_my_media))
.setAppLinkIntent(Intent(context, StartupActivity::class.java))
.build()
)
val nextUpChannel = getChannelUri(
"next_up", Channel.Builder()
.setType(TvContractCompat.Channels.TYPE_PREVIEW)
.setDisplayName(context.getString(R.string.lbl_next_up))
.setAppLinkIntent(Intent(context, StartupActivity::class.java))
.build()
)
val latestMoviesChannel = getChannelUri(
"latest_movies", Channel.Builder()
.setType(TvContractCompat.Channels.TYPE_PREVIEW)
.setDisplayName(context.getString(R.string.lbl_movies))
.setAppLinkIntent(Intent(context, StartupActivity::class.java))
.build()
)
val latestEpisodesChannel = getChannelUri(
"latest_episodes", Channel.Builder()
.setType(TvContractCompat.Channels.TYPE_PREVIEW)
.setDisplayName(context.getString(R.string.lbl_new_episodes))
.setAppLinkIntent(Intent(context, StartupActivity::class.java))
.build()
)
val preferParentThumb = userPreferences[UserPreferences.seriesThumbnailsEnabled]
// Add new items
arrayOf(
nextUpItems to nextUpChannel,
latestMedia to latestMediaChannel,
latestMovies to latestMoviesChannel,
latestEpisodes to latestEpisodesChannel,
myMedia to myMediaChannel,
).forEach { (items, channel) ->
Timber.d("Updating channel %s", channel)
items.map { item ->
createPreviewProgram(
channel,
item,
preferParentThumb
)
}.let {
context.contentResolver.bulkInsert(
TvContractCompat.PreviewPrograms.CONTENT_URI,
it.toTypedArray()
)
}
}
updateWatchNext(resumeItems + nextUpItems)
// Success!
@@ -124,7 +185,10 @@ class LeanbackChannelWorker(
}
} else {
// Create new channel and save uri
context.contentResolver.insert(TvContractCompat.Channels.CONTENT_URI, settings.toContentValues())!!.also { uri ->
context.contentResolver.insert(
TvContractCompat.Channels.CONTENT_URI,
settings.toContentValues()
)!!.also { uri ->
store.edit().putString(name, uri.toString()).apply()
if (default) {
// Set as default row to display (we can request one row to automatically be added to the home screen)
@@ -135,7 +199,11 @@ class LeanbackChannelWorker(
// Update logo
ResourcesCompat.getDrawable(context.resources, R.drawable.app_icon, context.theme)?.let {
ChannelLogoUtils.storeChannelLogo(context, ContentUris.parseId(uri), it.toBitmap(80.dp(context), 80.dp(context)))
ChannelLogoUtils.storeChannelLogo(
context,
ContentUris.parseId(uri),
it.toBitmap(80.dp(context), 80.dp(context))
)
}
return uri
@@ -145,53 +213,29 @@ class LeanbackChannelWorker(
* Updates the "my media" row with current media libraries.
*/
@Suppress("RestrictedApi")
private suspend fun updateMyMedia() {
// Get channel
val channelUri = getChannelUri("my_media", Channel.Builder()
.setType(TvContractCompat.Channels.TYPE_PREVIEW)
.setDisplayName(context.getString(R.string.lbl_my_media))
.setAppLinkIntent(Intent(context, StartupActivity::class.java))
.build(), default = true)
private suspend fun getMyMedia(): List<BaseItemDto> {
val response by api.userViewsApi.getUserViews(includeHidden = false)
// Add new items
val items = response.items
return response.items
.orEmpty()
.filter { userViewsRepository.isSupported(it.collectionType) }
.map { item ->
val imageUri = if (item.imageTags?.contains(ImageType.PRIMARY) == true)
ImageProvider.getImageUri(api.imageApi.getItemImageUrl(item.id, ImageType.PRIMARY))
else
ImageUtils.getResourceUrl(context, R.drawable.tile_land_tv).toUri()
PreviewProgram.Builder()
.setChannelId(ContentUris.parseId(channelUri))
.setType(TvContractCompat.PreviewPrograms.TYPE_CHANNEL)
.setTitle(item.name)
.setPosterArtUri(imageUri)
.setPosterArtAspectRatio(TvContractCompat.PreviewPrograms.ASPECT_RATIO_16_9)
.setIntent(Intent(context, StartupActivity::class.java).apply {
putExtra(StartupActivity.EXTRA_ITEM_ID, item.id.toString())
putExtra(StartupActivity.EXTRA_ITEM_IS_USER_VIEW, true)
})
.build()
.toContentValues()
}.toTypedArray()
context.contentResolver.bulkInsert(TvContractCompat.PreviewPrograms.CONTENT_URI, items)
}
/**
* Gets the poster art for an item. Uses the [preferParentThumb] parameter to fetch the series
* image when preferred.
*/
private fun BaseItemDto.getPosterArtImageUrl(preferParentThumb: Boolean): Uri = when {
type == BaseItemKind.MOVIE -> api.imageApi.getItemImageUrl(
private fun BaseItemDto.getPosterArtImageUrl(
preferParentThumb: Boolean
): Uri = when {
type == BaseItemKind.MOVIE || type == BaseItemKind.SERIES -> api.imageApi.getItemImageUrl(
itemId = id,
imageType = ImageType.PRIMARY,
format = ImageFormat.WEBP,
width = 106.dp(context),
height = 153.dp(context),
tag = imageTags?.get(ImageType.PRIMARY),
)
(preferParentThumb || imageTags?.contains(ImageType.PRIMARY) != true) && parentThumbItemId != null -> api.imageApi.getItemImageUrl(
@@ -200,15 +244,19 @@ class LeanbackChannelWorker(
format = ImageFormat.WEBP,
width = 272.dp(context),
height = 153.dp(context),
tag = imageTags?.get(ImageType.THUMB),
)
else -> api.imageApi.getItemImageUrl(
imageTags?.containsKey(ImageType.PRIMARY) == true -> api.imageApi.getItemImageUrl(
itemId = id,
imageType = ImageType.PRIMARY,
format = ImageFormat.WEBP,
width = 272.dp(context),
height = 153.dp(context),
tag = imageTags?.get(ImageType.PRIMARY),
)
else -> ImageUtils.getResourceUrl(context, R.drawable.tile_land_tv)
}.let(ImageProvider::getImageUri)
/**
@@ -216,73 +264,133 @@ class LeanbackChannelWorker(
* 1. resume items
* 2. next up items
*/
private suspend fun getNextUpItems(): Pair<List<BaseItemDto>, List<BaseItemDto>> = withContext(Dispatchers.IO) {
val resume = async {
api.itemsApi.getResumeItems(
fields = listOf(ItemFields.DATE_CREATED),
imageTypeLimit = 1,
limit = 10,
mediaTypes = listOf(MediaType.Video),
includeItemTypes = listOf(BaseItemKind.EPISODE, BaseItemKind.MOVIE),
excludeActiveSessions = true,
).content.items.orEmpty()
}
val nextup = async {
api.tvShowsApi.getNextUp(
userId = api.userId,
imageTypeLimit = 1,
limit = 10,
fields = listOf(ItemFields.DATE_CREATED),
).content.items.orEmpty()
}
// Concat
Pair(resume.await(), nextup.await())
}
/**
* Updates the "next up" row with current episodes. Uses the [nextUpItems] parameter to store
* items returned by a NextUpQuery().
*/
@Suppress("RestrictedApi")
private fun updateNextUp(nextUpItems: List<BaseItemDto>) {
val preferParentThumb = userPreferences[UserPreferences.seriesThumbnailsEnabled]
// Get channel
val channelUri = getChannelUri("next_up", Channel.Builder()
.setType(TvContractCompat.Channels.TYPE_PREVIEW)
.setDisplayName(context.getString(R.string.lbl_next_up))
.setAppLinkIntent(Intent(context, StartupActivity::class.java))
.build())
// Add new items
nextUpItems.map { item ->
val imageUri = item.getPosterArtImageUrl(preferParentThumb)
val seasonString = item.parentIndexNumber?.toString().orEmpty()
val episodeString = when {
item.indexNumberEnd != null && item.indexNumber != null ->
"${item.indexNumber}-${item.indexNumberEnd}"
else -> item.indexNumber?.toString().orEmpty()
private suspend fun getNextUpItems(): Pair<List<BaseItemDto>, List<BaseItemDto>> =
withContext(Dispatchers.IO) {
val resume = async {
api.itemsApi.getResumeItems(
fields = listOf(ItemFields.DATE_CREATED),
imageTypeLimit = 1,
limit = 10,
mediaTypes = listOf(MediaType.Video),
includeItemTypes = listOf(BaseItemKind.EPISODE, BaseItemKind.MOVIE),
excludeActiveSessions = true,
).content.items.orEmpty()
}
PreviewProgram.Builder()
.setChannelId(ContentUris.parseId(channelUri))
.setType(WatchNextPrograms.TYPE_TV_EPISODE)
.setTitle(item.seriesName)
.setEpisodeTitle(item.name)
.setSeasonNumber(seasonString, item.parentIndexNumber ?: 0)
.setEpisodeNumber(episodeString, item.indexNumber ?: 0)
.setPosterArtUri(imageUri)
.setPosterArtAspectRatio(TvContractCompat.PreviewPrograms.ASPECT_RATIO_16_9)
.setIntent(Intent(context, StartupActivity::class.java).apply {
putExtra(StartupActivity.EXTRA_ITEM_ID, item.id.toString())
})
.build()
.toContentValues()
}.let { context.contentResolver.bulkInsert(TvContractCompat.PreviewPrograms.CONTENT_URI, it.toTypedArray()) }
val nextUp = async {
api.tvShowsApi.getNextUp(
userId = api.userId,
imageTypeLimit = 1,
limit = 10,
fields = listOf(ItemFields.DATE_CREATED),
).content.items.orEmpty()
}
// Concat
Pair(resume.await(), nextUp.await())
}
private suspend fun getLatestMedia(): Triple<List<BaseItemDto>, List<BaseItemDto>, List<BaseItemDto>> =
withContext(Dispatchers.IO) {
val latestEpisodes = async {
api.userLibraryApi.getLatestMedia(
fields = listOf(
ItemFields.OVERVIEW,
),
limit = 50,
includeItemTypes = listOf(BaseItemKind.EPISODE),
isPlayed = false
).content
}
val latestMovies = async {
api.userLibraryApi.getLatestMedia(
fields = listOf(
ItemFields.OVERVIEW,
),
limit = 50,
includeItemTypes = listOf(BaseItemKind.MOVIE),
isPlayed = false
).content
}
val latestMedia = async {
api.userLibraryApi.getLatestMedia(
fields = listOf(
ItemFields.OVERVIEW,
),
limit = 50,
includeItemTypes = listOf(BaseItemKind.MOVIE, BaseItemKind.SERIES),
isPlayed = false
).content
}
// Concat
Triple(latestEpisodes.await(), latestMovies.await(), latestMedia.await())
}
@SuppressLint("RestrictedApi")
private fun createPreviewProgram(
channelUri: Uri,
item: BaseItemDto,
preferParentThumb: Boolean
): ContentValues {
val imageUri = item.getPosterArtImageUrl(preferParentThumb)
val seasonString = item.parentIndexNumber?.toString().orEmpty()
val episodeString = when {
item.indexNumberEnd != null && item.indexNumber != null ->
"${item.indexNumber}-${item.indexNumberEnd}"
else -> item.indexNumber?.toString().orEmpty()
}
return PreviewProgram.Builder()
.setChannelId(ContentUris.parseId(channelUri))
.setType(
when (item.type) {
BaseItemKind.SERIES -> WatchNextPrograms.TYPE_TV_SERIES
BaseItemKind.MOVIE -> WatchNextPrograms.TYPE_MOVIE
BaseItemKind.EPISODE -> WatchNextPrograms.TYPE_TV_EPISODE
BaseItemKind.AUDIO -> WatchNextPrograms.TYPE_TRACK
BaseItemKind.PLAYLIST -> WatchNextPrograms.TYPE_PLAYLIST
else -> WatchNextPrograms.TYPE_CHANNEL
}
)
.setTitle(item.seriesName ?: item.name)
.setEpisodeTitle(if (item.type == BaseItemKind.EPISODE) item.name else null)
.setSeasonNumber(seasonString, item.parentIndexNumber ?: 0)
.setEpisodeNumber(episodeString, item.indexNumber ?: 0)
.setDescription(item.overview)
.setReleaseDate(
if (item.premiereDate != null) DateTimeFormatter.ISO_DATE.format(item.premiereDate)
else null
)
.setDurationMillis(
if (item.runTimeTicks?.ticks != null) {
// If we are resuming, we need to show remaining time, cause GoogleTV
// ignores setLastPlaybackPositionMillis
val duration = item.runTimeTicks?.ticks ?: Duration.ZERO
val playbackPosition = item.userData?.playbackPositionTicks?.ticks
?: Duration.ZERO
(duration - playbackPosition).inWholeMilliseconds.toInt()
} else 0
)
.setPosterArtUri(imageUri)
.setPosterArtAspectRatio(
when (item.type) {
BaseItemKind.COLLECTION_FOLDER,
BaseItemKind.EPISODE -> TvContractCompat.PreviewPrograms.ASPECT_RATIO_16_9
else -> TvContractCompat.PreviewPrograms.ASPECT_RATIO_MOVIE_POSTER
}
)
.setIntent(Intent(context, StartupActivity::class.java).apply {
putExtra(StartupActivity.EXTRA_ITEM_ID, item.id.toString())
putExtra(StartupActivity.EXTRA_ITEM_IS_USER_VIEW, item.type == BaseItemKind.COLLECTION_FOLDER)
})
.build()
.toContentValues()
}
/**
@@ -297,7 +405,8 @@ class LeanbackChannelWorker(
// Add new items
context.contentResolver.bulkInsert(
WatchNextPrograms.CONTENT_URI,
nextUpItems.map { item -> getBaseItemAsWatchNextProgram(item).toContentValues() }.toTypedArray()
nextUpItems.map { item -> getBaseItemAsWatchNextProgram(item).toContentValues() }
.toTypedArray()
)
}
@@ -305,55 +414,58 @@ class LeanbackChannelWorker(
* Convert [BaseItemDto] to [WatchNextProgram]. Assumes the item type is "episode".
*/
@Suppress("RestrictedApi")
private fun getBaseItemAsWatchNextProgram(item: BaseItemDto) = WatchNextProgram.Builder().apply {
val preferParentThumb = userPreferences[UserPreferences.seriesThumbnailsEnabled]
private fun getBaseItemAsWatchNextProgram(item: BaseItemDto) =
WatchNextProgram.Builder().apply {
val preferParentThumb = userPreferences[UserPreferences.seriesThumbnailsEnabled]
setInternalProviderId(item.id.toString())
setInternalProviderId(item.id.toString())
// Poster size & type
if (item.type == BaseItemKind.EPISODE) {
setType(WatchNextPrograms.TYPE_TV_EPISODE)
setPosterArtAspectRatio(WatchNextPrograms.ASPECT_RATIO_16_9)
} else if (item.type == BaseItemKind.MOVIE) {
setType(WatchNextPrograms.TYPE_MOVIE)
setPosterArtAspectRatio(WatchNextPrograms.ASPECT_RATIO_MOVIE_POSTER)
}
// Name
if (item.seriesName != null) setTitle("${item.seriesName} - ${item.name}")
else setTitle(item.name)
// Poster
setPosterArtUri(item.getPosterArtImageUrl(preferParentThumb))
// Use date created or fallback to current time if unavailable
var engagement = item.dateCreated
when {
// User has started playing the episode
item.userData?.playbackPositionTicks ?: 0 > 0 -> {
setWatchNextType(WatchNextPrograms.WATCH_NEXT_TYPE_CONTINUE)
setLastPlaybackPositionMillis((item.userData!!.playbackPositionTicks / TICKS_IN_MILLISECOND).toInt())
// Use last played date to prioritze
engagement = item.userData?.lastPlayedDate
// Poster size & type
if (item.type == BaseItemKind.EPISODE) {
setType(WatchNextPrograms.TYPE_TV_EPISODE)
setPosterArtAspectRatio(WatchNextPrograms.ASPECT_RATIO_16_9)
} else if (item.type == BaseItemKind.MOVIE) {
setType(WatchNextPrograms.TYPE_MOVIE)
setPosterArtAspectRatio(WatchNextPrograms.ASPECT_RATIO_MOVIE_POSTER)
}
// First episode of the season
item.indexNumber == 1 -> setWatchNextType(WatchNextPrograms.WATCH_NEXT_TYPE_NEW)
// Default
else -> setWatchNextType(WatchNextPrograms.WATCH_NEXT_TYPE_NEXT)
}
setLastEngagementTimeUtcMillis(engagement?.toInstant(ZoneOffset.UTC)?.toEpochMilli()
?: System.currentTimeMillis())
// Name
if (item.seriesName != null) setTitle("${item.seriesName} - ${item.name}")
else setTitle(item.name)
// Episode runtime has been determined
item.runTimeTicks?.let { runTimeTicks ->
setDurationMillis((runTimeTicks / TICKS_IN_MILLISECOND).toInt())
}
// Poster
setPosterArtUri(item.getPosterArtImageUrl(preferParentThumb))
// Set intent to open the episode
setIntent(Intent(context, StartupActivity::class.java).apply {
putExtra(StartupActivity.EXTRA_ITEM_ID, item.id.toString())
})
}.build()
// Use date created or fallback to current time if unavailable
var engagement = item.dateCreated
when {
// User has started playing the episode
(item.userData?.playbackPositionTicks ?: 0) > 0 -> {
setWatchNextType(WatchNextPrograms.WATCH_NEXT_TYPE_CONTINUE)
setLastPlaybackPositionMillis(item.userData!!.playbackPositionTicks.ticks.inWholeMilliseconds.toInt())
// Use last played date to prioritize
engagement = item.userData?.lastPlayedDate
}
// First episode of the season
item.indexNumber == 1 -> setWatchNextType(WatchNextPrograms.WATCH_NEXT_TYPE_NEW)
// Default
else -> setWatchNextType(WatchNextPrograms.WATCH_NEXT_TYPE_NEXT)
}
setLastEngagementTimeUtcMillis(
engagement?.toInstant(ZoneOffset.UTC)?.toEpochMilli()
?: System.currentTimeMillis()
)
// Episode runtime has been determined
item.runTimeTicks?.let { runTimeTicks ->
setDurationMillis(runTimeTicks.ticks.inWholeMilliseconds.toInt())
}
// Set intent to open the episode
setIntent(Intent(context, StartupActivity::class.java).apply {
putExtra(StartupActivity.EXTRA_ITEM_ID, item.id.toString())
})
}.build()
}

View File

@@ -0,0 +1,60 @@
package org.jellyfin.androidtv.integration.dream
import android.service.dreams.DreamService
import androidx.annotation.CallSuper
import androidx.compose.runtime.Composable
import androidx.compose.ui.platform.ComposeView
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleRegistry
import androidx.lifecycle.setViewTreeLifecycleOwner
import androidx.savedstate.SavedStateRegistry
import androidx.savedstate.SavedStateRegistryController
import androidx.savedstate.SavedStateRegistryOwner
import androidx.savedstate.setViewTreeSavedStateRegistryOwner
abstract class DreamServiceCompat : DreamService(), SavedStateRegistryOwner {
@Suppress("LeakingThis")
private val lifecycleRegistry = LifecycleRegistry(this)
@Suppress("LeakingThis")
private val savedStateRegistryController = SavedStateRegistryController.create(this).apply {
performAttach()
}
override val lifecycle: Lifecycle get() = lifecycleRegistry
override val savedStateRegistry: SavedStateRegistry get() = savedStateRegistryController.savedStateRegistry
@CallSuper
override fun onCreate() {
super.onCreate()
savedStateRegistryController.performRestore(null)
lifecycleRegistry.currentState = Lifecycle.State.CREATED
}
override fun onDreamingStarted() {
super.onDreamingStarted()
lifecycleRegistry.currentState = Lifecycle.State.STARTED
}
override fun onDreamingStopped() {
super.onDreamingStopped()
lifecycleRegistry.currentState = Lifecycle.State.CREATED
}
fun setContent(content: @Composable () -> Unit) {
val view = ComposeView(this)
// Inject dependencies normally added by appcompat activities
view.setViewTreeLifecycleOwner(this)
view.setViewTreeSavedStateRegistryOwner(this)
// Set content composable
view.setContent(content)
// Set content view
setContentView(view)
}
}

View File

@@ -1,9 +0,0 @@
package org.jellyfin.androidtv.integration.dream
import android.graphics.drawable.Drawable
import org.jellyfin.sdk.model.api.BaseItemDto
data class LibraryDreamItem(
val baseItem: BaseItemDto,
val background: Drawable,
)

View File

@@ -1,34 +0,0 @@
package org.jellyfin.androidtv.integration.dream
import android.content.Context
import android.util.AttributeSet
import android.view.LayoutInflater
import android.view.animation.AccelerateDecelerateInterpolator
import android.widget.FrameLayout
import org.jellyfin.androidtv.databinding.ViewDreamLibraryItemBinding
import org.jellyfin.androidtv.util.KenBurnsTransitionGenerator
class LibraryDreamItemView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0,
defStyleRes: Int = 0,
) : FrameLayout(context, attrs, defStyleAttr, defStyleRes) {
val binding = ViewDreamLibraryItemBinding.inflate(LayoutInflater.from(context), this, true)
private val kenburnsTransitionGenerator = KenBurnsTransitionGenerator(
LibraryDreamService.UPDATE_DELAY,
AccelerateDecelerateInterpolator()
)
init {
binding.background.setTransitionGenerator(kenburnsTransitionGenerator)
}
fun setItem(item: LibraryDreamItem) {
kenburnsTransitionGenerator.reset()
binding.background.setImageDrawable(item.background)
binding.title.text = item.baseItem.name
}
}

View File

@@ -1,143 +1,22 @@
package org.jellyfin.androidtv.integration.dream
import android.graphics.drawable.Drawable
import android.service.dreams.DreamService
import android.view.LayoutInflater
import androidx.core.view.isVisible
import com.bumptech.glide.Glide
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.cancel
import kotlinx.coroutines.delay
import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.jellyfin.androidtv.databinding.DreamLibraryBinding
import org.jellyfin.androidtv.preference.UserPreferences
import org.jellyfin.androidtv.preference.constant.ClockBehavior
import org.jellyfin.sdk.api.client.ApiClient
import org.jellyfin.sdk.api.client.exception.ApiClientException
import org.jellyfin.sdk.api.client.extensions.imageApi
import org.jellyfin.sdk.api.client.extensions.itemsApi
import org.jellyfin.sdk.model.api.BaseItemDto
import org.jellyfin.sdk.model.api.BaseItemKind
import org.jellyfin.sdk.model.api.ImageFormat
import org.jellyfin.sdk.model.api.ImageType
import org.jellyfin.sdk.model.constant.ItemSortBy
import org.koin.android.ext.android.inject
import timber.log.Timber
import java.util.concurrent.ExecutionException
import org.jellyfin.androidtv.integration.dream.composable.DreamHost
/**
* An Android [DreamService] (screensaver) that shows TV series and movies from all libraries.
* Use `adb shell am start -n "com.android.systemui/.Somnambulator"` to start after changing the
* default screensaver in the device settings.
*/
class LibraryDreamService : DreamService() {
companion object {
const val INITIAL_DELAY = 2_000L // 2s
const val UPDATE_DELAY = 30_000L // 30s
const val TRANSITION_DURATION = 1_000L // 1s
}
private val scope = CoroutineScope(Dispatchers.IO + SupervisorJob())
private val api: ApiClient by inject()
private val userPreferences: UserPreferences by inject()
private lateinit var binding: DreamLibraryBinding
class LibraryDreamService : DreamServiceCompat() {
override fun onAttachedToWindow() {
super.onAttachedToWindow()
isInteractive = false
isFullscreen = true
binding = DreamLibraryBinding.inflate(LayoutInflater.from(window.context), null, false)
binding.clock.isVisible = when (userPreferences[UserPreferences.clockBehavior]) {
ClockBehavior.ALWAYS, ClockBehavior.IN_MENUS -> true
else -> false
setContent {
DreamHost()
}
setContentView(binding.root)
}
override fun onDreamingStarted() {
super.onDreamingStarted()
scope.launch {
delay(INITIAL_DELAY)
while (isActive) {
update()
delay(UPDATE_DELAY)
}
}
}
private suspend fun getRandomItem(): BaseItemDto? = try {
val response by api.itemsApi.getItemsByUserId(
includeItemTypes = listOf(BaseItemKind.MOVIE, BaseItemKind.SERIES),
recursive = true,
sortBy = listOf(ItemSortBy.Random),
limit = 1,
imageTypes = listOf(ImageType.BACKDROP)
)
response.items?.firstOrNull()
} catch (err: ApiClientException) {
Timber.e(err)
null
}
private suspend fun loadBackdrop(item: BaseItemDto): Drawable? {
val tag = item.backdropImageTags?.randomOrNull()
?: item.imageTags?.get(ImageType.BACKDROP)
val url = api.imageApi.getItemImageUrl(
itemId = item.id,
imageType = ImageType.BACKDROP,
tag = tag,
format = ImageFormat.WEBP,
)
return withContext(Dispatchers.IO) {
// Sometimes the backdrop doesn't exist
// make sure to return a null value in those cases
try {
Glide.with(binding.root)
.load(url)
.submit()
.get()
} catch (err: ExecutionException) {
Timber.e(err)
null
}
}
}
private suspend fun update() {
val item = getRandomItem()
val background = item?.let { loadBackdrop(it) }
withContext(Dispatchers.Main) {
if (item == null || background == null) {
binding.itemSwitcher.hideAllViews()
} else with(binding.itemSwitcher) {
getNextView<LibraryDreamItemView>().setItem(
item = LibraryDreamItem(
baseItem = item,
background = background,
)
)
showNextView()
}
}
}
override fun onDreamingStopped() {
super.onDreamingStopped()
scope.cancel()
}
}

View File

@@ -0,0 +1,58 @@
package org.jellyfin.androidtv.integration.dream.composable
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.asImageBitmap
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.unit.sp
import org.jellyfin.androidtv.integration.dream.model.DreamContent
import org.jellyfin.androidtv.ui.composable.ZoomBox
import org.jellyfin.androidtv.ui.composable.overscan
@Composable
fun DreamContentLibraryShowcase(
content: DreamContent.LibraryShowcase,
) = Box(
modifier = Modifier.fillMaxSize(),
) {
ZoomBox(
initialValue = 1f,
targetValue = 1.1f,
delayMillis = 1_000,
durationMillis = 30_000,
) {
Image(
bitmap = content.backdrop.asImageBitmap(),
contentDescription = null,
alignment = Alignment.Center,
contentScale = ContentScale.Crop,
modifier = Modifier.fillMaxSize()
)
// Image vignette
DreamContentVignette()
}
// Overlay
Row(
modifier = Modifier
.align(Alignment.BottomStart)
.overscan(),
) {
Text(
text = content.item.name.orEmpty(),
style = TextStyle(
color = Color.White,
fontSize = 32.sp
),
)
}
}

View File

@@ -0,0 +1,30 @@
package org.jellyfin.androidtv.integration.dream.composable
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.width
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import org.jellyfin.androidtv.R
@Composable
fun DreamContentLogo() = Box(
modifier = Modifier
.fillMaxSize()
.background(Color.Black),
) {
Image(
painter = painterResource(R.drawable.app_logo),
contentDescription = stringResource(R.string.app_name),
modifier = Modifier
.align(Alignment.Center)
.width(400.dp)
)
}

View File

@@ -0,0 +1,155 @@
package org.jellyfin.androidtv.integration.dream.composable
import android.widget.ImageView
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.LinearProgressIndicator
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableFloatStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.StrokeCap
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.unit.IntSize
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import org.jellyfin.androidtv.integration.dream.model.DreamContent
import org.jellyfin.androidtv.ui.composable.AsyncImage
import org.jellyfin.androidtv.ui.composable.blurHashPainter
import org.jellyfin.androidtv.ui.composable.overscan
import org.jellyfin.androidtv.ui.playback.AudioEventListener
import org.jellyfin.androidtv.ui.playback.MediaManager
import org.jellyfin.sdk.api.client.ApiClient
import org.jellyfin.sdk.api.client.extensions.imageApi
import org.jellyfin.sdk.model.api.ImageFormat
import org.jellyfin.sdk.model.api.ImageType
import org.jellyfin.sdk.model.extensions.ticks
import org.koin.compose.rememberKoinInject
import kotlin.time.Duration
import kotlin.time.Duration.Companion.milliseconds
@Composable
fun DreamContentNowPlaying(
content: DreamContent.NowPlaying,
) = Box(
modifier = Modifier.fillMaxSize(),
) {
val api = rememberKoinInject<ApiClient>()
val mediaManager = rememberKoinInject<MediaManager>()
val item = content.item ?: return@Box
val primaryImageTag = item.imageTags?.get(ImageType.PRIMARY)
val (imageItemId, imageTag) = when {
primaryImageTag != null -> item.id to primaryImageTag
(item.albumId != null && item.albumPrimaryImageTag != null) -> item.albumId to item.albumPrimaryImageTag
else -> null to null
}
val imageBlurHash = imageTag?.let { tag -> item.imageBlurHashes?.get(ImageType.PRIMARY)?.get(tag) }
if (imageBlurHash != null) {
Image(
painter = blurHashPainter(imageBlurHash, IntSize(32, 32)),
contentDescription = null,
alignment = Alignment.Center,
contentScale = ContentScale.Crop,
modifier = Modifier.fillMaxSize(),
)
DreamContentVignette()
}
// Overlay
Row(
verticalAlignment = Alignment.Bottom,
horizontalArrangement = Arrangement.spacedBy(20.dp),
modifier = Modifier
.align(Alignment.BottomStart)
.overscan(),
) {
if (imageItemId != null) {
AsyncImage(
url = api.imageApi.getItemImageUrl(
itemId = imageItemId,
imageType = ImageType.PRIMARY,
tag = imageTag,
format = ImageFormat.WEBP,
),
blurHash = imageBlurHash,
scaleType = ImageView.ScaleType.CENTER_CROP,
modifier = Modifier
.size(128.dp)
.clip(RoundedCornerShape(5.dp))
)
}
Column(
modifier = Modifier
.padding(bottom = 10.dp)
) {
Text(
text = item.name.orEmpty(),
style = TextStyle(
color = Color.White,
fontSize = 26.sp,
),
)
Text(
text = item.run {
if (!artists.isNullOrEmpty()) return@run artists?.joinToString(", ")
val albumArtistNames = albumArtists?.mapNotNull { it.name }
if (!albumArtistNames.isNullOrEmpty()) return@run albumArtistNames.joinToString(", ")
return@run albumArtist
}.orEmpty(),
style = TextStyle(
color = Color(0.8f, 0.8f, 0.8f),
fontSize = 18.sp,
),
)
Spacer(modifier = Modifier.height(10.dp))
var progress by remember { mutableFloatStateOf(0f) }
DisposableEffect(Unit) {
val listener = object : AudioEventListener {
override fun onProgress(pos: Long) {
val duration = item.runTimeTicks?.ticks ?: Duration.ZERO
progress = (pos.milliseconds / duration).toFloat()
}
}
mediaManager.addAudioEventListener(listener)
onDispose {
mediaManager.removeAudioEventListener(listener)
}
}
LinearProgressIndicator(
progress = progress,
color = Color.White,
backgroundColor = Color.White.copy(alpha = 0.2f),
strokeCap = StrokeCap.Round,
modifier = Modifier.fillMaxWidth()
)
}
}
}

View File

@@ -0,0 +1,34 @@
package org.jellyfin.androidtv.integration.dream.composable
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Size
import androidx.compose.ui.geometry.center
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.RadialGradientShader
import androidx.compose.ui.graphics.Shader
import androidx.compose.ui.graphics.ShaderBrush
private val vignetteBrush = object : ShaderBrush() {
override fun createShader(size: Size): Shader = RadialGradientShader(
colors = listOf(
Color.Black.copy(alpha = 0.2f),
Color.Black.copy(alpha = 0.7f),
),
center = size.center,
radius = maxOf(size.width, size.height) / 2f,
colorStops = listOf(0f, 0.95f)
)
}
@Composable
fun DreamContentVignette(
modifier: Modifier = Modifier
) = Box(
modifier = modifier
.background(vignetteBrush)
.fillMaxSize()
)

View File

@@ -0,0 +1,72 @@
package org.jellyfin.androidtv.integration.dream.composable
import android.text.format.DateUtils
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import org.jellyfin.androidtv.R
import org.jellyfin.androidtv.ui.composable.overscan
import org.jellyfin.androidtv.ui.composable.rememberCurrentTime
@Composable
fun DreamHeader(
showLogo: Boolean,
showClock: Boolean,
) {
Row(
horizontalArrangement = Arrangement.SpaceBetween,
modifier = Modifier
.fillMaxWidth()
.overscan(),
) {
// Logo
AnimatedVisibility(
visible = showLogo,
enter = fadeIn(),
exit = fadeOut(),
modifier = Modifier.height(41.dp),
) {
Image(
painter = painterResource(R.drawable.app_logo),
contentDescription = stringResource(R.string.app_name),
)
}
Spacer(
modifier = Modifier
.fillMaxWidth(0f)
)
// Clock
AnimatedVisibility(
visible = showClock,
enter = fadeIn(),
exit = fadeOut(),
) {
val time = rememberCurrentTime()
Text(
text = DateUtils.formatDateTime(LocalContext.current, time, DateUtils.FORMAT_SHOW_TIME),
style = TextStyle(
color = Color.White,
fontSize = 20.sp
),
)
}
}
}

View File

@@ -0,0 +1,111 @@
package org.jellyfin.androidtv.integration.dream.composable
import android.content.Context
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.platform.LocalContext
import androidx.core.graphics.drawable.toBitmap
import coil.ImageLoader
import coil.request.ImageRequest
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.withContext
import org.jellyfin.androidtv.integration.dream.model.DreamContent
import org.jellyfin.androidtv.preference.UserPreferences
import org.jellyfin.androidtv.preference.constant.ClockBehavior
import org.jellyfin.androidtv.ui.composable.rememberMediaItem
import org.jellyfin.androidtv.ui.playback.MediaManager
import org.jellyfin.sdk.api.client.ApiClient
import org.jellyfin.sdk.api.client.exception.ApiClientException
import org.jellyfin.sdk.api.client.extensions.imageApi
import org.jellyfin.sdk.api.client.extensions.itemsApi
import org.jellyfin.sdk.model.api.BaseItemKind
import org.jellyfin.sdk.model.api.ImageFormat
import org.jellyfin.sdk.model.api.ImageType
import org.jellyfin.sdk.model.constant.ItemSortBy
import org.koin.compose.rememberKoinInject
import timber.log.Timber
import kotlin.time.Duration.Companion.seconds
@Composable
fun DreamHost() {
val api = rememberKoinInject<ApiClient>()
val userPreferences = rememberKoinInject<UserPreferences>()
val mediaManager = rememberKoinInject<MediaManager>()
val imageLoader = rememberKoinInject<ImageLoader>()
val context = LocalContext.current
var libraryShowcase by remember { mutableStateOf<DreamContent.LibraryShowcase?>(null) }
val mediaItem by rememberMediaItem(mediaManager)
LaunchedEffect(true) {
delay(2.seconds)
while (true) {
libraryShowcase = getRandomLibraryShowcase(api, imageLoader, context)
delay(30.seconds)
}
}
DreamView(
content = when {
mediaItem != null -> DreamContent.NowPlaying(mediaItem)
libraryShowcase != null -> libraryShowcase!!
else -> DreamContent.Logo
},
showClock = when (userPreferences[UserPreferences.clockBehavior]) {
ClockBehavior.ALWAYS, ClockBehavior.IN_MENUS -> true
else -> false
}
)
}
private suspend fun getRandomLibraryShowcase(
api: ApiClient,
imageLoader: ImageLoader,
context: Context
): DreamContent.LibraryShowcase? {
try {
val response by api.itemsApi.getItemsByUserId(
includeItemTypes = listOf(BaseItemKind.MOVIE, BaseItemKind.SERIES),
recursive = true,
sortBy = listOf(ItemSortBy.Random),
limit = 5,
imageTypes = listOf(ImageType.BACKDROP),
// TODO: Add preferences for these two settings
maxOfficialRating = "PG-13",
// hasParentalRating = true,
)
val item = response.items?.firstOrNull { item ->
!item.backdropImageTags.isNullOrEmpty()
} ?: return null
Timber.i("Loading random library showcase item ${item.id}")
val tag = item.backdropImageTags!!.randomOrNull()
?: item.imageTags?.get(ImageType.BACKDROP)
val backdropUrl = api.imageApi.getItemImageUrl(
itemId = item.id,
imageType = ImageType.BACKDROP,
tag = tag,
format = ImageFormat.WEBP,
)
val backdrop = withContext(Dispatchers.IO) {
imageLoader.execute(
request = ImageRequest.Builder(context).data(backdropUrl).build()
).drawable?.toBitmap()
} ?: return null
return DreamContent.LibraryShowcase(item, backdrop)
} catch (err: ApiClientException) {
Timber.e(err)
return null
}
}

View File

@@ -0,0 +1,44 @@
package org.jellyfin.androidtv.integration.dream.composable
import androidx.compose.animation.AnimatedContent
import androidx.compose.animation.ExperimentalAnimationApi
import androidx.compose.animation.core.snap
import androidx.compose.animation.core.tween
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.animation.togetherWith
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import org.jellyfin.androidtv.integration.dream.model.DreamContent
@OptIn(ExperimentalAnimationApi::class)
@Composable
fun DreamView(
content: DreamContent,
showClock: Boolean,
) = Box(
modifier = Modifier
.fillMaxSize()
) {
AnimatedContent(
targetState = content,
transitionSpec = {
fadeIn(tween(durationMillis = 1_000)) togetherWith fadeOut(snap(delayMillis = 1_000))
},
label = "DreamContentTransition"
) { content ->
when (content) {
DreamContent.Logo -> DreamContentLogo()
is DreamContent.LibraryShowcase -> DreamContentLibraryShowcase(content)
is DreamContent.NowPlaying -> DreamContentNowPlaying(content)
}
}
// Header overlay
DreamHeader(
showLogo = content != DreamContent.Logo,
showClock = showClock,
)
}

View File

@@ -0,0 +1,10 @@
package org.jellyfin.androidtv.integration.dream.model
import android.graphics.Bitmap
import org.jellyfin.sdk.model.api.BaseItemDto
sealed interface DreamContent {
data object Logo : DreamContent
data class LibraryShowcase(val item: BaseItemDto, val backdrop: Bitmap) : DreamContent
data class NowPlaying(val item: BaseItemDto?) : DreamContent
}

View File

@@ -7,18 +7,18 @@ import android.graphics.drawable.Drawable
import android.net.Uri
import android.os.Build
import android.os.ParcelFileDescriptor
import androidx.core.graphics.drawable.toBitmap
import androidx.core.net.toUri
import androidx.lifecycle.ProcessLifecycleOwner
import androidx.lifecycle.lifecycleScope
import com.bumptech.glide.Glide
import com.bumptech.glide.request.target.CustomTarget
import com.bumptech.glide.request.transition.Transition
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import coil.ImageLoader
import coil.request.ImageRequest
import org.jellyfin.androidtv.BuildConfig
import org.jellyfin.androidtv.R
import org.koin.android.ext.android.inject
import java.io.IOException
class ImageProvider : ContentProvider() {
private val imageLoader by inject<ImageLoader>()
override fun onCreate(): Boolean = true
override fun getType(uri: Uri) = null
@@ -33,30 +33,40 @@ class ImageProvider : ContentProvider() {
val (read, write) = ParcelFileDescriptor.createPipe()
val outputStream = ParcelFileDescriptor.AutoCloseOutputStream(write)
ProcessLifecycleOwner.get().lifecycleScope.launch(Dispatchers.IO) {
Glide.with(context!!)
.asBitmap()
.error(R.drawable.placeholder_icon)
.load(src)
.into(object : CustomTarget<Bitmap>() {
override fun onResourceReady(resource: Bitmap, transition: Transition<in Bitmap>?) {
@Suppress("DEPRECATION")
val format = when {
Build.VERSION.SDK_INT >= Build.VERSION_CODES.R -> Bitmap.CompressFormat.WEBP_LOSSY
else -> Bitmap.CompressFormat.WEBP
}
resource.compress(format, 95, outputStream)
outputStream.close()
}
override fun onLoadCleared(placeholder: Drawable?) = outputStream.close()
})
}
imageLoader.enqueue(ImageRequest.Builder(context!!).apply {
data(src)
error(R.drawable.placeholder_icon)
target(
onSuccess = { drawable -> writeDrawable(drawable, outputStream) },
onError = { drawable -> writeDrawable(requireNotNull(drawable), outputStream) }
)
}.build())
return read
}
private fun writeDrawable(
drawable: Drawable,
outputStream: ParcelFileDescriptor.AutoCloseOutputStream
) {
@Suppress("DEPRECATION")
val format = when {
Build.VERSION.SDK_INT >= Build.VERSION_CODES.R -> Bitmap.CompressFormat.WEBP_LOSSY
else -> Bitmap.CompressFormat.WEBP
}
try {
outputStream.use {
drawable.toBitmap().compress(format, COMPRESSION_QUALITY, outputStream)
}
} catch (_: IOException) {
// Ignore IOException as this is commonly thrown when the load request is cancelled
}
}
companion object {
private const val COMPRESSION_QUALITY = 95
/**
* Get a [Uri] that uses the [ImageProvider] to load an image. The input should be a valid
* Jellyfin image URL created using the SDK.

View File

@@ -69,5 +69,10 @@ class SystemPreferences(context: Context) : SharedPreferenceStore(
* The version name for the latest dismissed beta notification or empty if none.
*/
val dismissedBetaNotificationVersion = stringPreference("dismissed_beta_notification_version", "")
/**
* Whether to disable the "UI mode" warning that shows when using the app on non TV devices.
*/
val disableUiModeWarning = booleanPreference("disable_ui_mode_warning", false)
}
}

View File

@@ -4,7 +4,6 @@ import android.content.Context
import android.view.KeyEvent
import androidx.preference.PreferenceManager
import org.jellyfin.androidtv.preference.constant.AppTheme
import org.jellyfin.androidtv.preference.constant.AudioBehavior
import org.jellyfin.androidtv.preference.constant.ClockBehavior
import org.jellyfin.androidtv.preference.constant.NextUpBehavior
import org.jellyfin.androidtv.preference.constant.PreferredVideoPlayer
@@ -17,9 +16,9 @@ import org.jellyfin.preference.booleanPreference
import org.jellyfin.preference.enumPreference
import org.jellyfin.preference.intPreference
import org.jellyfin.preference.longPreference
import org.jellyfin.preference.migration.putEnum
import org.jellyfin.preference.store.SharedPreferenceStore
import org.jellyfin.preference.stringPreference
import kotlin.time.Duration.Companion.minutes
/**
* User preferences are configurable by the user and change behavior of the application.
@@ -52,6 +51,11 @@ class UserPreferences(context: Context) : SharedPreferenceStore(
*/
var premieresEnabled = booleanPreference("pref_enable_premieres", false)
/**
* Enable management of media like deleting items when the user has sufficient permisisons.
*/
var mediaManagementEnabled = booleanPreference("enable_media_management", false)
/* Playback - General*/
/**
* Maximum bitrate in megabit for playback. A value of [MAX_BITRATE_AUTO] is used when
@@ -122,6 +126,11 @@ class UserPreferences(context: Context) : SharedPreferenceStore(
*/
var libVLCAudioDelay = intPreference("libvlc_audio_delay", 0)
/**
* Use playback rewrite module for audio
*/
var playbackRewriteAudioEnabled = booleanPreference("playback_new_audio", true)
/* Live TV */
/**
* Use direct play
@@ -150,9 +159,9 @@ class UserPreferences(context: Context) : SharedPreferenceStore(
var debuggingEnabled = booleanPreference("pref_enable_debug", false)
/**
* Use playback rewrite module
* Use playback rewrite module for video
*/
var playbackRewriteEnabled = booleanPreference("playback_new", false)
var playbackRewriteVideoEnabled = booleanPreference("playback_new", false)
/**
* When to show the clock.
@@ -198,47 +207,32 @@ class UserPreferences(context: Context) : SharedPreferenceStore(
* Subtitles foreground color
*/
var subtitlesTextColor = longPreference("subtitles_text_color", 0xFFFFFFFF)
/**
* Show screensaver in app
*/
var screensaverInAppEnabled = booleanPreference("screensaver_inapp_enabled", true)
/**
* Timeout before showing the screensaver in app, depends on [screensaverInAppEnabled].
*/
var screensaverInAppTimeout = longPreference("screensaver_inapp_timeout", 5.minutes.inWholeMilliseconds)
/**
* Enable reactive homepage
*/
var homeReactive = booleanPreference("home_reactive", false)
}
init {
// Note: Create a single migration per app version
// Note: Migrations are never executed for fresh installs
// Note: Old migrations are removed occasionally
runMigrations {
// v0.10.x to v0.11.x
migration(toVersion = 2) {
// Migrate to video player enum
// Note: This is the only time we need to check if the value is not set yet because the version numbers were reset
if (!it.contains("video_player"))
putEnum("video_player", if (it.getBoolean("pref_video_use_external", false)) PreferredVideoPlayer.EXTERNAL else PreferredVideoPlayer.AUTO)
}
// v0.11.x to v0.12.x
migration(toVersion = 5) {
// Migrate to audio behavior enum
putEnum("audio_behavior", if (it.getString("pref_audio_option", "0") == "1") AudioBehavior.DOWNMIX_TO_STEREO else AudioBehavior.DIRECT_STREAM)
// Migrate live tv player to use enum
putEnum("live_tv_video_player",
when {
it.getBoolean("pref_live_tv_use_external", false) -> PreferredVideoPlayer.EXTERNAL
it.getBoolean("pref_enable_vlc_livetv", false) -> PreferredVideoPlayer.VLC
else -> PreferredVideoPlayer.AUTO
})
// Change audio delay type from long to int
putInt("libvlc_audio_delay", it.getLong("libvlc_audio_delay", 0).toInt())
// Disable AC3 (Dolby Digital) on Fire Stick Gen 1 devices
if (DeviceUtils.isFireTvStickGen1) putBoolean("pref_bitstream_ac3", false)
}
// v0.13.3 to v0.13.4
migration(toVersion = 6) {
putEnum("refresh_rate_switching_behavior",
when {
it.getBoolean("pref_refresh_switching", false) -> RefreshRateSwitchingBehavior.SCALE_ON_TV
else -> RefreshRateSwitchingBehavior.DISABLED
})
// v0.15.z to v0.16.0
migration(toVersion = 7) {
// Enable playback rewrite for music
putBoolean("playback_new_audio", true)
}
}
}

View File

@@ -14,8 +14,8 @@ class UserSettingPreferences(
app = "emby",
) {
companion object {
val skipBackLength = intPreference("skipBackLength", 10000)
val skipForwardLength = intPreference("skipForwardLength", 30000)
val skipBackLength = intPreference("skipBackLength", 10_000)
val skipForwardLength = intPreference("skipForwardLength", 30_000)
val homesection0 = enumPreference("homesection0", HomeSectionType.LIBRARY_TILES_SMALL)
val homesection1 = enumPreference("homesection1", HomeSectionType.RESUME)

View File

@@ -1,24 +1,23 @@
package org.jellyfin.androidtv.preference.constant
import org.jellyfin.androidtv.R
import org.jellyfin.androidtv.ui.preference.dsl.EnumDisplayOptions
import org.jellyfin.preference.PreferenceEnum
enum class AppTheme {
enum class AppTheme(
override val nameRes: Int,
) : PreferenceEnum {
/**
* The default dark theme
*/
@EnumDisplayOptions(R.string.pref_theme_dark)
DARK,
DARK(R.string.pref_theme_dark),
/**
* The "classic" emerald theme
*/
@EnumDisplayOptions(R.string.pref_theme_emerald)
EMERALD,
EMERALD(R.string.pref_theme_emerald),
/**
* A theme with a more muted accent color, inspired by CTalvio's Monochromic CSS theme for Jellyfin Web
*/
@EnumDisplayOptions(R.string.pref_theme_muted_purple)
MUTED_PURPLE,
MUTED_PURPLE(R.string.pref_theme_muted_purple),
}

View File

@@ -1,22 +1,22 @@
package org.jellyfin.androidtv.preference.constant
import org.jellyfin.androidtv.R
import org.jellyfin.androidtv.ui.preference.dsl.EnumDisplayOptions
import org.jellyfin.androidtv.util.DeviceUtils
import org.jellyfin.preference.PreferenceEnum
enum class AudioBehavior {
enum class AudioBehavior(
override val nameRes: Int,
) : PreferenceEnum {
/**
* Directly stream audio without any changes
*/
@EnumDisplayOptions(R.string.pref_audio_direct)
DIRECT_STREAM,
DIRECT_STREAM(R.string.pref_audio_direct),
/**
* Downnmix audio to stereo. Disables the AC3, EAC3 and AAC_LATM audio codecs.
*/
@EnumDisplayOptions(R.string.pref_audio_compat)
DOWNMIX_TO_STEREO
DOWNMIX_TO_STEREO(R.string.pref_audio_compat),
}
val defaultAudioBehavior = if (DeviceUtils.isChromecastWithGoogleTV) AudioBehavior.DOWNMIX_TO_STEREO
else AudioBehavior.DIRECT_STREAM
else AudioBehavior.DIRECT_STREAM

View File

@@ -1,30 +1,28 @@
package org.jellyfin.androidtv.preference.constant
import org.jellyfin.androidtv.R
import org.jellyfin.androidtv.ui.preference.dsl.EnumDisplayOptions
import org.jellyfin.preference.PreferenceEnum
enum class ClockBehavior {
enum class ClockBehavior(
override val nameRes: Int,
) : PreferenceEnum {
/**
* Always show clock.
*/
@EnumDisplayOptions(R.string.lbl_always)
ALWAYS,
ALWAYS(R.string.lbl_always),
/**
* Show clock in menus only.
*/
@EnumDisplayOptions(R.string.pref_clock_display_browsing)
IN_MENUS,
IN_MENUS(R.string.pref_clock_display_browsing),
/**
* Show clock in video only.
*/
@EnumDisplayOptions(R.string.pref_clock_display_playback)
IN_VIDEO,
IN_VIDEO(R.string.pref_clock_display_playback),
/**
* Show clock never.
*/
@EnumDisplayOptions(R.string.lbl_never)
NEVER
NEVER(R.string.lbl_never),
}

View File

@@ -1,17 +1,15 @@
package org.jellyfin.androidtv.preference.constant
import org.jellyfin.androidtv.R
import org.jellyfin.androidtv.ui.preference.dsl.EnumDisplayOptions
import org.jellyfin.preference.PreferenceEnum
import org.jellyfin.sdk.model.constant.ItemSortBy
enum class LiveTvChannelOrder(
val stringValue: String
) {
@EnumDisplayOptions(R.string.lbl_guide_option_played)
LAST_PLAYED(ItemSortBy.DatePlayed),
@EnumDisplayOptions(R.string.lbl_guide_option_number)
CHANNEL_NUMBER(ItemSortBy.SortName);
override val nameRes: Int,
val stringValue: String,
) : PreferenceEnum {
LAST_PLAYED(R.string.lbl_guide_option_played, ItemSortBy.DatePlayed),
CHANNEL_NUMBER(R.string.lbl_guide_option_number, ItemSortBy.SortName);
companion object {
fun fromString(value: String) = values()

View File

@@ -1,26 +1,25 @@
package org.jellyfin.androidtv.preference.constant
import org.jellyfin.androidtv.R
import org.jellyfin.androidtv.ui.preference.dsl.EnumDisplayOptions
import org.jellyfin.preference.PreferenceEnum
enum class NextUpBehavior {
enum class NextUpBehavior(
override val nameRes: Int,
) : PreferenceEnum {
/**
* Enable the Next Up screen - show the item's thumbnail
*/
@EnumDisplayOptions(R.string.lbl_next_up_extended)
EXTENDED,
EXTENDED(R.string.lbl_next_up_extended),
/**
* Enable the Next Up screen - hide the item's thumbnail
*/
@EnumDisplayOptions(R.string.lbl_next_up_minimal)
MINIMAL,
MINIMAL(R.string.lbl_next_up_minimal),
/**
* Disable the Next Up screen
*/
@EnumDisplayOptions(R.string.lbl_never)
DISABLED
DISABLED(R.string.lbl_never),
}
const val NEXTUP_TIMER_DISABLED: Int = 0

View File

@@ -1,36 +1,33 @@
package org.jellyfin.androidtv.preference.constant
import org.jellyfin.androidtv.R
import org.jellyfin.androidtv.ui.preference.dsl.EnumDisplayOptions
import org.jellyfin.preference.PreferenceEnum
enum class PreferredVideoPlayer {
enum class PreferredVideoPlayer(
override val nameRes: Int,
) : PreferenceEnum {
/**
* Automatically selects between exoplayer and vlc
*/
@EnumDisplayOptions(R.string.pref_video_player_auto)
AUTO,
AUTO(R.string.pref_video_player_auto),
/**
* Force ExoPlayer
*/
@EnumDisplayOptions(R.string.pref_video_player_exoplayer)
EXOPLAYER,
EXOPLAYER(R.string.pref_video_player_exoplayer),
/**
* Force libVLC
*/
@EnumDisplayOptions(R.string.pref_video_player_vlc)
VLC,
VLC(R.string.pref_video_player_vlc),
/**
* Use external player
*/
@EnumDisplayOptions(R.string.pref_video_player_external)
EXTERNAL,
EXTERNAL(R.string.pref_video_player_external),
/**
* Choose a player - play with button
*/
@EnumDisplayOptions(R.string.pref_video_player_choose)
CHOOSE
CHOOSE(R.string.pref_video_player_choose),
}

View File

@@ -1,24 +1,23 @@
package org.jellyfin.androidtv.preference.constant
import org.jellyfin.androidtv.R
import org.jellyfin.androidtv.ui.preference.dsl.EnumDisplayOptions
import org.jellyfin.preference.PreferenceEnum
enum class RatingType {
enum class RatingType(
override val nameRes: Int,
) : PreferenceEnum {
/**
* Sets default rating type to tomatoes.
*/
@EnumDisplayOptions(R.string.lbl_tomatoes)
RATING_TOMATOES,
RATING_TOMATOES(R.string.lbl_tomatoes),
/**
* Sets the default rating type to stars.
*/
@EnumDisplayOptions(R.string.lbl_stars)
RATING_STARS,
RATING_STARS(R.string.lbl_stars),
/**
* Sets the default rating type to hidden.
*/
@EnumDisplayOptions(R.string.lbl_hidden)
RATING_HIDDEN
RATING_HIDDEN(R.string.lbl_hidden),
}

View File

@@ -1,22 +1,21 @@
package org.jellyfin.androidtv.preference.constant
import org.jellyfin.androidtv.R
import org.jellyfin.androidtv.ui.preference.dsl.EnumDisplayOptions
import org.jellyfin.preference.PreferenceEnum
enum class RefreshRateSwitchingBehavior {
@EnumDisplayOptions(R.string.state_disabled)
DISABLED,
enum class RefreshRateSwitchingBehavior(
override val nameRes: Int,
) : PreferenceEnum {
DISABLED(R.string.state_disabled),
/**
* When comparing modes, use difference in resolution to rank modes.
*/
@EnumDisplayOptions(R.string.pref_refresh_rate_scale_on_tv)
SCALE_ON_TV,
SCALE_ON_TV(R.string.pref_refresh_rate_scale_on_tv),
/**
* When comparing modes, rank native resolution modes highest.
* Otherwise use difference in resolution to rank modes.
*/
@EnumDisplayOptions(R.string.pref_refresh_rate_scale_on_device)
SCALE_ON_DEVICE,
SCALE_ON_DEVICE(R.string.pref_refresh_rate_scale_on_device),
}

View File

@@ -1,30 +1,28 @@
package org.jellyfin.androidtv.preference.constant
import org.jellyfin.androidtv.R
import org.jellyfin.androidtv.ui.preference.dsl.EnumDisplayOptions
import org.jellyfin.preference.PreferenceEnum
enum class WatchedIndicatorBehavior {
enum class WatchedIndicatorBehavior(
override val nameRes: Int,
) : PreferenceEnum {
/**
* Always show watched indicators.
*/
@EnumDisplayOptions(R.string.lbl_always)
ALWAYS,
ALWAYS(R.string.lbl_always),
/**
* Hide unwatched count indicator, show watched check mark only.
*/
@EnumDisplayOptions(R.string.lbl_hide_unwatched_count)
HIDE_UNWATCHED,
HIDE_UNWATCHED(R.string.lbl_hide_unwatched_count),
/**
* Hide unwatched count indicator, show watched check mark on individual episodes only.
*/
@EnumDisplayOptions(R.string.lbl_episode_marks)
EPISODES_ONLY,
EPISODES_ONLY(R.string.lbl_episode_marks),
/**
* Never show watched indicators.
*/
@EnumDisplayOptions(R.string.lbl_never)
NEVER
NEVER(R.string.lbl_never),
}

View File

@@ -119,7 +119,7 @@ abstract class DisplayPreferencesStore(
override fun <V : Enum<V>> setEnum(preference: Preference<*>, value: Enum<V>) =
setString(
preference.key, when (value) {
is PreferenceEnum -> value.serializedName
is PreferenceEnum -> value.serializedName ?: value.toString()
else -> value.toString()
}
)

View File

@@ -32,6 +32,7 @@ object TelemetryService {
buildConfigClass = BuildConfig::class.java
sharedPreferencesName = TelemetryPreferences.SHARED_PREFERENCES_NAME
pluginLoader = AcraPluginLoader(AcraReportSenderFactory::class.java)
applicationLogFileLines = 250
toast {
text = context.getString(R.string.crash_report_toast)

View File

@@ -9,12 +9,16 @@ import androidx.core.graphics.drawable.toDrawable
import androidx.core.view.doOnAttach
import androidx.lifecycle.findViewTreeLifecycleOwner
import androidx.lifecycle.lifecycleScope
import com.bumptech.glide.Glide
import coil.ImageLoader
import coil.request.ImageRequest
import coil.transform.CircleCropTransformation
import com.vanniktech.blurhash.BlurHash
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.jellyfin.androidtv.R
import org.koin.core.component.KoinComponent
import org.koin.core.component.inject
import kotlin.math.round
import kotlin.time.Duration.Companion.milliseconds
@@ -27,9 +31,10 @@ class AsyncImageView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0,
) : AppCompatImageView(context, attrs, defStyleAttr) {
) : AppCompatImageView(context, attrs, defStyleAttr), KoinComponent {
private val lifeCycleOwner get() = findViewTreeLifecycleOwner()
private val styledAttributes = context.obtainStyledAttributes(attrs, R.styleable.AsyncImageView, defStyleAttr, 0)
private val imageLoader by inject<ImageLoader>()
/**
* The duration of the crossfade when changing switching the images of the url, blurhash and
@@ -69,15 +74,25 @@ class AsyncImageView @JvmOverloads constructor(
}
// Start loading image or placeholder
Glide.with(this@AsyncImageView)
.load(url ?: placeholder).apply {
if (url == null) {
imageLoader.enqueue(ImageRequest.Builder(context).apply {
target(this@AsyncImageView)
data(placeholder)
if (circleCrop) transformations(CircleCropTransformation())
}.build())
} else {
imageLoader.enqueue(ImageRequest.Builder(context).apply {
val crossFadeDurationMs = crossFadeDuration.inWholeMilliseconds.toInt()
if (crossFadeDurationMs > 0) crossfade(crossFadeDurationMs)
else crossfade(false)
target(this@AsyncImageView)
data(url)
placeholder(placeholderOrBlurHash)
if (circleCrop) transformations(CircleCropTransformation())
error(placeholder)
if (circleCrop) circleCrop()
// FIXME: Glide is unable to scale the image when transitions are enabled
//transition(DrawableTransitionOptions.withCrossFade(crossFadeDuration.inWholeMilliseconds.toInt()))
}
.into(this@AsyncImageView)
}.build())
}
}
}
}

View File

@@ -11,9 +11,7 @@ import org.jellyfin.androidtv.auth.repository.UserRepository
import org.jellyfin.androidtv.databinding.ClockUserBugBinding
import org.jellyfin.androidtv.preference.UserPreferences
import org.jellyfin.androidtv.preference.constant.ClockBehavior
import org.jellyfin.androidtv.ui.playback.PlaybackOverlayActivity
import org.jellyfin.androidtv.util.ImageUtils
import org.jellyfin.androidtv.util.getActivity
import org.koin.core.component.KoinComponent
import org.koin.core.component.inject
@@ -27,16 +25,15 @@ class ClockUserView @JvmOverloads constructor(
private val userPreferences by inject<UserPreferences>()
private val userRepository by inject<UserRepository>()
init {
val showClock = userPreferences[UserPreferences.clockBehavior]
binding.clock.isVisible = when (showClock) {
ClockBehavior.ALWAYS -> true
ClockBehavior.NEVER -> false
ClockBehavior.IN_VIDEO -> context.getActivity() is PlaybackOverlayActivity
ClockBehavior.IN_MENUS -> context.getActivity() !is PlaybackOverlayActivity
var isVideoPlayer = false
set(value) {
field = value
updateClockVisibility()
}
init {
updateClockVisibility()
val currentUser = userRepository.currentUser.value
binding.clockUserImage.load(
@@ -46,4 +43,15 @@ class ClockUserView @JvmOverloads constructor(
binding.clockUserImage.isVisible = currentUser != null
}
private fun updateClockVisibility() {
val showClock = userPreferences[UserPreferences.clockBehavior]
binding.clock.isVisible = when (showClock) {
ClockBehavior.ALWAYS -> true
ClockBehavior.NEVER -> false
ClockBehavior.IN_VIDEO -> isVideoPlayer
ClockBehavior.IN_MENUS -> !isVideoPlayer
}
}
}

View File

@@ -48,5 +48,6 @@ class DetailRowView @JvmOverloads constructor(
init {
binding.fdButtonRow.setOnHierarchyChangeListener(buttonsHierarchyChangeListener)
binding.mainImage.clipToOutline = true
}
}

View File

@@ -39,7 +39,7 @@ public class FriendlyDateButton extends FrameLayout {
super.onFocusChanged(gainFocus, direction, previouslyFocusedRect);
if (gainFocus) {
setBackgroundColor(getResources().getColor(R.color.lb_default_brand_color));
setBackgroundColor(getResources().getColor(androidx.leanback.R.color.lb_default_brand_color));
} else {
setBackgroundColor(0);
}

View File

@@ -2,10 +2,10 @@ package org.jellyfin.androidtv.ui
import androidx.annotation.DrawableRes
open class GridButton(
open class GridButton @JvmOverloads constructor(
val id: Int,
val text: String,
@DrawableRes val imageRes: Int,
@DrawableRes val imageRes: Int? = null,
) {
override fun toString() = text
}

View File

@@ -1,6 +1,5 @@
package org.jellyfin.androidtv.ui;
import android.app.Activity;
import android.content.Context;
import android.graphics.Rect;
import android.util.AttributeSet;
@@ -23,14 +22,14 @@ public class GuidePagingButton extends RelativeLayout {
super(context, attrs);
}
public GuidePagingButton(final Activity activity, final LiveTvGuide guide, int start, String label) {
super(activity);
public GuidePagingButton(Context context, final LiveTvGuide guide, int start, String label) {
super(context);
LayoutInflater inflater = LayoutInflater.from(activity);
LayoutInflater inflater = LayoutInflater.from(context);
ProgramGridCellBinding binding = ProgramGridCellBinding.inflate(inflater, this, true);
binding.programName.setText(label);
setBackgroundColor(Utils.getThemeColor(activity, R.attr.buttonDefaultNormalBackground));
setBackgroundColor(Utils.getThemeColor(context, R.attr.buttonDefaultNormalBackground));
setFocusable(true);
setOnClickListener(v -> guide.displayChannels(start, LiveTvGuideFragment.PAGE_SIZE));
}

View File

@@ -7,23 +7,17 @@ import android.view.View;
import android.widget.FrameLayout;
import android.widget.LinearLayout;
import org.jellyfin.androidtv.auth.repository.UserRepository;
import org.jellyfin.androidtv.data.querying.StdItemQuery;
import org.jellyfin.androidtv.databinding.ItemListBinding;
import org.jellyfin.apiclient.interaction.ApiClient;
import org.jellyfin.apiclient.interaction.Response;
import org.jellyfin.apiclient.model.dto.BaseItemDto;
import org.jellyfin.apiclient.model.querying.ItemFields;
import org.jellyfin.apiclient.model.querying.ItemsResult;
import org.koin.java.KoinJavaComponent;
import org.jellyfin.sdk.model.api.BaseItemDto;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
public class ItemListView extends FrameLayout {
Context mContext;
LinearLayout mList;
List<String> mItemIds = new ArrayList<>();
List<UUID> mItemIds = new ArrayList<>();
ItemRowView.RowSelectedListener mRowSelectedListener;
ItemRowView.RowClickedListener mRowClickedListener;
@@ -76,32 +70,4 @@ public class ItemListView extends FrameLayout {
}
return ret;
}
public void refresh() {
//update watched state for all items
//get them in batch for better performance
StdItemQuery query = new StdItemQuery(new ItemFields[] {
ItemFields.MediaSources,
ItemFields.ChildCount
});
query.setUserId(KoinJavaComponent.<UserRepository>get(UserRepository.class).getCurrentUser().getValue().getId().toString());
String[] ids = new String[mItemIds.size()];
query.setIds(mItemIds.toArray(ids));
KoinJavaComponent.<ApiClient>get(ApiClient.class).GetItemsAsync(query, new Response<ItemsResult>() {
@Override
public void onResponse(ItemsResult response) {
if (response.getItems() != null) {
int i = 0;
for (BaseItemDto item : response.getItems()) {
// we have title view as first one
View view = mList.getChildAt(i + 1);
if (view instanceof ItemRowView) {
ItemRowView row = (ItemRowView) view;
row.setItem(item, i++);
}
}
}
}
});
}
}

View File

@@ -0,0 +1,32 @@
package org.jellyfin.androidtv.ui
import androidx.lifecycle.findViewTreeLifecycleOwner
import androidx.lifecycle.lifecycleScope
import kotlinx.coroutines.launch
import org.jellyfin.sdk.api.client.ApiClient
import org.jellyfin.sdk.api.client.extensions.itemsApi
import org.jellyfin.sdk.model.api.ItemFields
import org.koin.java.KoinJavaComponent
fun ItemListView.refresh() {
val api by KoinJavaComponent.inject<ApiClient>(ApiClient::class.java)
findViewTreeLifecycleOwner()?.lifecycleScope?.launch {
val response by api.itemsApi.getItemsByUserId(
ids = mItemIds,
fields = setOf(
ItemFields.PRIMARY_IMAGE_ASPECT_RATIO,
ItemFields.OVERVIEW,
ItemFields.ITEM_COUNTS,
ItemFields.DISPLAY_PREFERENCES_ID,
ItemFields.CHILD_COUNT,
ItemFields.MEDIA_SOURCES,
)
)
response.items?.forEachIndexed { index, item ->
val view = mList.getChildAt(index)
if (view is ItemRowView) view.setItem(item, index)
}
}
}

View File

@@ -15,8 +15,7 @@ import org.jellyfin.androidtv.R;
import org.jellyfin.androidtv.databinding.ItemRowBinding;
import org.jellyfin.androidtv.util.TimeUtils;
import org.jellyfin.androidtv.util.sdk.BaseItemExtensionsKt;
import org.jellyfin.androidtv.util.sdk.compat.ModelCompat;
import org.jellyfin.apiclient.model.dto.BaseItemDto;
import org.jellyfin.sdk.model.api.BaseItemDto;
import org.jellyfin.sdk.model.constant.MediaType;
public class ItemRowView extends FrameLayout {
@@ -91,8 +90,8 @@ public class ItemRowView extends FrameLayout {
mBaseItem = item;
ourIndex = ndx + 1;
mIndexNo.setText(Integer.toString(ourIndex));
switch (item.getBaseItemType()) {
case Audio:
switch (item.getType()) {
case AUDIO:
mItemName.setText(item.getName());
String artist = item.getArtists() != null && item.getArtists().size() > 0 ? item.getArtists().get(0) : !TextUtils.isEmpty(item.getAlbumArtist()) ? item.getAlbumArtist() : null;
if (!TextUtils.isEmpty(artist)) {
@@ -102,7 +101,7 @@ public class ItemRowView extends FrameLayout {
}
break;
default:
String series = item.getSeriesName() != null ? BaseItemExtensionsKt.getFullName(ModelCompat.asSdk(item), mContext) : null;
String series = item.getSeriesName() != null ? BaseItemExtensionsKt.getFullName(item, mContext) : null;
if (!TextUtils.isEmpty(series)) {
mItemName.setText(series);
mExtraName.setText(item.getName());

View File

@@ -48,7 +48,7 @@ class NowPlayingView @JvmOverloads constructor(
if (mediaManager.hasAudioQueueItems()) {
isVisible = true
setInfo(mediaManager.currentAudioItem)
setInfo(mediaManager.currentAudioItem!!)
setStatus(mediaManager.currentAudioPosition)
} else isVisible = false
}
@@ -95,7 +95,7 @@ class NowPlayingView @JvmOverloads constructor(
if (hasQueue) {
// may have just added one so update display
setInfo(mediaManager.currentAudioItem)
setInfo(mediaManager.currentAudioItem!!)
setStatus(mediaManager.currentAudioPosition)
}
}

View File

@@ -82,7 +82,7 @@ public class ProgramGridCell extends RelativeLayout implements RecordingIndicato
}
if (liveTvPreferences.get(LiveTvPreferences.Companion.getShowRepeatIndicator()) && Utils.isTrue(program.getIsRepeat())) {
InfoLayoutHelper.addBlockText(context, mInfoRow, context.getString(R.string.lbl_repeat), 10, Color.GRAY, R.color.lb_default_brand_color);
InfoLayoutHelper.addBlockText(context, mInfoRow, context.getString(R.string.lbl_repeat), 10, Color.GRAY, androidx.leanback.R.color.lb_default_brand_color);
}
if (program.getOfficialRating() != null && !program.getOfficialRating().equals("0")) {

View File

@@ -26,6 +26,7 @@ import org.jellyfin.androidtv.R;
import org.jellyfin.androidtv.auth.repository.UserRepository;
import org.jellyfin.androidtv.constant.CustomMessage;
import org.jellyfin.androidtv.data.repository.CustomMessageRepository;
import org.jellyfin.androidtv.util.ContextExtensionsKt;
import org.jellyfin.androidtv.util.TimeUtils;
import org.jellyfin.androidtv.util.Utils;
import org.jellyfin.androidtv.util.apiclient.EmptyLifecycleAwareResponse;
@@ -81,14 +82,14 @@ public class RecordPopup {
mPaddingDisplayOptions = new ArrayList<>(Arrays.asList(
mActivity.getString(R.string.lbl_on_schedule),
"1 " + mActivity.getString(R.string.lbl_minute),
"5 " + mActivity.getString(R.string.lbl_minutes),
"15 " + mActivity.getString(R.string.lbl_minutes),
"30 " + mActivity.getString(R.string.lbl_minutes),
"60 " + mActivity.getString(R.string.lbl_minutes),
"90 " + mActivity.getString(R.string.lbl_minutes),
"2 " + mActivity.getString(R.string.lbl_hours),
"3 " + mActivity.getString(R.string.lbl_hours)
ContextExtensionsKt.getQuantityString(activity, R.plurals.minutes, 1),
ContextExtensionsKt.getQuantityString(activity, R.plurals.minutes, 5),
ContextExtensionsKt.getQuantityString(activity, R.plurals.minutes, 15),
ContextExtensionsKt.getQuantityString(activity, R.plurals.minutes, 30),
ContextExtensionsKt.getQuantityString(activity, R.plurals.minutes, 60),
ContextExtensionsKt.getQuantityString(activity, R.plurals.minutes, 90),
ContextExtensionsKt.getQuantityString(activity, R.plurals.hours, 2),
ContextExtensionsKt.getQuantityString(activity, R.plurals.hours, 3)
));
LayoutInflater inflater = (LayoutInflater) mActivity.getSystemService(Context.LAYOUT_INFLATER_SERVICE);

View File

@@ -0,0 +1,103 @@
package org.jellyfin.androidtv.ui
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleEventObserver
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.launch
import org.jellyfin.androidtv.preference.UserPreferences
import kotlin.time.Duration.Companion.milliseconds
class ScreensaverViewModel(
private val userPreferences: UserPreferences,
) : ViewModel() {
private var timer: Job? = null
private var locks = 0
// Preferences
val inAppEnabled get() = userPreferences[UserPreferences.screensaverInAppEnabled]
private val timeout get() = userPreferences[UserPreferences.screensaverInAppTimeout].milliseconds
// State
private val _visible = MutableStateFlow(false)
val visible get() = _visible.asStateFlow()
private val _keepScreenOn = MutableStateFlow(inAppEnabled)
val keepScreenOn get() = _keepScreenOn.asStateFlow()
var activityPaused: Boolean = false
set(value) {
field = value
notifyInteraction(true)
}
init {
notifyInteraction(true)
}
fun notifyInteraction(canCancel: Boolean) {
// Cancel pending timer (if any)
timer?.cancel()
// Hide when interacted with allowed cancelation or when disabled
if (_visible.value && (canCancel || !inAppEnabled || activityPaused)) {
_visible.value = false
}
// Create new timer to show screensaver when enabled
if (inAppEnabled && !activityPaused && locks == 0) {
timer = viewModelScope.launch {
delay(timeout)
_visible.value = true
}
}
// Update KEEP_SCREEN_ON flag value
_keepScreenOn.value = inAppEnabled || locks > 0
}
/**
* Create a lock that prevents the screensaver for running until the returned function is called
* or the lifecycle is destroyed.
*
* @return Function to cancel the lock
*/
fun addLifecycleLock(lifecycle: Lifecycle): () -> Unit {
if (lifecycle.currentState == Lifecycle.State.DESTROYED) return {}
val lock = ScreensaverLock(lifecycle)
lock.activate()
return lock::cancel
}
private inner class ScreensaverLock(private val lifecycle: Lifecycle) : LifecycleEventObserver {
private var active: Boolean = false
override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) {
if (event == Lifecycle.Event.ON_DESTROY) cancel()
}
fun activate() {
if (active) return
lifecycle.addObserver(this)
locks++
notifyInteraction(true)
active = true
}
fun cancel() {
if (!active) return
locks--
lifecycle.removeObserver(this)
notifyInteraction(false)
active = false
}
}
}

View File

@@ -0,0 +1,89 @@
package org.jellyfin.androidtv.ui.background
import android.graphics.drawable.ColorDrawable
import androidx.compose.animation.AnimatedContent
import androidx.compose.animation.ExperimentalAnimationApi
import androidx.compose.animation.core.snap
import androidx.compose.animation.core.tween
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.animation.togetherWith
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.BlendMode
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.ColorFilter
import androidx.compose.ui.graphics.asImageBitmap
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.colorResource
import androidx.core.graphics.drawable.toBitmap
import org.jellyfin.androidtv.R
import org.jellyfin.androidtv.data.service.BackgroundService
import org.koin.compose.rememberKoinInject
@Composable
private fun AppThemeBackground() {
val context = LocalContext.current
val themeBackground = remember(context.theme) {
val attributes = context.theme.obtainStyledAttributes(intArrayOf(android.R.attr.windowBackground))
val drawable = attributes.getDrawable(0)
attributes.recycle()
if (drawable is ColorDrawable) drawable.toBitmap(1, 1).asImageBitmap()
else drawable?.toBitmap()?.asImageBitmap()
}
if (themeBackground != null) {
Image(
bitmap = themeBackground,
contentDescription = null,
alignment = Alignment.Center,
contentScale = ContentScale.Crop,
modifier = Modifier.fillMaxSize()
)
} else {
Box(
modifier = Modifier
.fillMaxSize()
.background(Color.Black)
)
}
}
@Composable
@OptIn(ExperimentalAnimationApi::class)
fun AppBackground() {
val backgroundService = rememberKoinInject<BackgroundService>()
val currentBackground by backgroundService.currentBackground.collectAsState()
AnimatedContent(
targetState = currentBackground,
transitionSpec = {
val duration = (BackgroundService.TRANSITION_DURATION.inWholeMilliseconds / 2).toInt()
fadeIn(tween(durationMillis = duration)) togetherWith fadeOut(snap(delayMillis = duration))
},
label = "BackgroundTransition",
) { background ->
if (background != null) {
Image(
bitmap = background,
contentDescription = null,
alignment = Alignment.Center,
contentScale = ContentScale.Crop,
colorFilter = ColorFilter.tint(colorResource(R.color.background_filter), BlendMode.SrcAtop),
modifier = Modifier.fillMaxSize()
)
} else {
AppThemeBackground()
}
}
}

View File

@@ -17,9 +17,7 @@ import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import kotlinx.coroutines.launch
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.json.Json
import org.jellyfin.androidtv.R
import org.jellyfin.androidtv.constant.Extras
import org.jellyfin.androidtv.data.service.BackgroundService
import org.jellyfin.androidtv.preference.UserPreferences
@@ -52,9 +50,6 @@ abstract class BrowseFolderFragment : BrowseSupportFragment(), RowLoader {
folder = Json.decodeFromString<BaseItemDto>(arguments?.getString(Extras.Folder)!!)
includeType = arguments?.getString(Extras.IncludeType)
// Attach background service
backgroundService.attach(requireActivity())
// Set BrowseSupportFragment properties
title = folder?.name
headersState = HEADERS_DISABLED
@@ -66,7 +61,7 @@ abstract class BrowseFolderFragment : BrowseSupportFragment(), RowLoader {
item,
(row as ListRow).adapter as ItemRowAdapter,
item.index,
activity
requireContext()
)
}
}
@@ -125,10 +120,10 @@ abstract class BrowseFolderFragment : BrowseSupportFragment(), RowLoader {
val showClock = userPreferences[clockBehavior]
if (showClock !== ClockBehavior.ALWAYS && showClock !== ClockBehavior.IN_MENUS) return
val root = requireActivity().findViewById<ViewGroup>(R.id.browse_frame)
val root = requireActivity().findViewById<ViewGroup>(androidx.leanback.R.id.browse_frame)
// Move the title to the left to make way for the clock
val titleView = root.findViewById<TextView>(R.id.title_text)
val titleView = root.findViewById<TextView>(androidx.leanback.R.id.title_text)
if (titleView != null) {
val layoutParams = titleView.layoutParams as FrameLayout.LayoutParams
layoutParams.rightMargin = convertDpToPixel(root.context, 120)

View File

@@ -2,7 +2,6 @@ package org.jellyfin.androidtv.ui.browsing;
import static org.koin.java.KoinJavaComponent.inject;
import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import android.util.DisplayMetrics;
@@ -19,6 +18,7 @@ import android.widget.PopupWindow;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentActivity;
import androidx.leanback.widget.BaseGridView;
import androidx.leanback.widget.OnItemViewClickedListener;
import androidx.leanback.widget.OnItemViewSelectedListener;
@@ -38,6 +38,7 @@ import org.jellyfin.androidtv.constant.ImageType;
import org.jellyfin.androidtv.constant.PosterSize;
import org.jellyfin.androidtv.constant.QueryType;
import org.jellyfin.androidtv.data.model.FilterOptions;
import org.jellyfin.androidtv.data.querying.AlbumArtistsQuery;
import org.jellyfin.androidtv.data.querying.StdItemQuery;
import org.jellyfin.androidtv.data.querying.ViewQuery;
import org.jellyfin.androidtv.data.repository.CustomMessageRepository;
@@ -53,7 +54,6 @@ import org.jellyfin.androidtv.ui.itemhandling.ItemLauncher;
import org.jellyfin.androidtv.ui.itemhandling.ItemRowAdapter;
import org.jellyfin.androidtv.ui.navigation.Destinations;
import org.jellyfin.androidtv.ui.navigation.NavigationRepository;
import org.jellyfin.androidtv.ui.playback.MediaManager;
import org.jellyfin.androidtv.ui.presentation.CardPresenter;
import org.jellyfin.androidtv.ui.presentation.HorizontalGridPresenter;
import org.jellyfin.androidtv.util.CoroutineUtils;
@@ -62,6 +62,7 @@ import org.jellyfin.androidtv.util.InfoLayoutHelper;
import org.jellyfin.androidtv.util.KeyProcessor;
import org.jellyfin.androidtv.util.Utils;
import org.jellyfin.androidtv.util.apiclient.EmptyLifecycleAwareResponse;
import org.jellyfin.androidtv.util.apiclient.LifecycleAwareResponse;
import org.jellyfin.apiclient.model.querying.ArtistsQuery;
import org.jellyfin.apiclient.model.querying.ItemFields;
import org.jellyfin.sdk.model.api.BaseItemDto;
@@ -84,7 +85,7 @@ public class BrowseGridFragment extends Fragment implements View.OnKeyListener {
private final static int CHUNK_SIZE_MINIMUM = 25;
private String mainTitle;
private Activity mActivity;
private FragmentActivity mActivity;
private BaseRowItem mCurrentItem;
private CompositeClickedListener mClickedListener = new CompositeClickedListener();
private CompositeSelectedListener mSelectedListener = new CompositeSelectedListener();
@@ -117,7 +118,6 @@ public class BrowseGridFragment extends Fragment implements View.OnKeyListener {
private int mGridPaddingTop = 0;
private final Lazy<BackgroundService> backgroundService = inject(BackgroundService.class);
private final Lazy<MediaManager> mediaManager = inject(MediaManager.class);
private final Lazy<PreferencesRepository> preferencesRepository = inject(PreferencesRepository.class);
private final Lazy<UserViewsRepository> userViewsRepository = inject(UserViewsRepository.class);
private final Lazy<UserRepository> userRepository = inject(UserRepository.class);
@@ -157,7 +157,6 @@ public class BrowseGridFragment extends Fragment implements View.OnKeyListener {
}
mActivity = getActivity();
backgroundService.getValue().attach(requireActivity());
mFolder = Json.Default.decodeFromString(BaseItemDto.Companion.serializer(), getArguments().getString(Extras.Folder));
mParentId = mFolder.getId();
@@ -188,27 +187,6 @@ public class BrowseGridFragment extends Fragment implements View.OnKeyListener {
// Hide the description because we don't have room for it
binding.npBug.showDescription(false);
// NOTE: we only get the 100% correct grid size if we render it once, so hook into it here
binding.rowsFragment.post(() -> {
if (binding.rowsFragment.getHeight() > 0 && binding.rowsFragment.getWidth() > 0) {
if (mGridView == null) {
return;
}
// prevent adaption on minor size delta's
if (Math.abs(mGridHeight - binding.rowsFragment.getHeight()) > MIN_GRIDSIZE_CHANGE_DELTA || Math.abs(mGridWidth - binding.rowsFragment.getWidth()) > MIN_GRIDSIZE_CHANGE_DELTA) {
mGridHeight = Math.round(binding.rowsFragment.getHeight() / getResources().getDisplayMetrics().density);
mGridWidth = Math.round(binding.rowsFragment.getWidth() / getResources().getDisplayMetrics().density);
Timber.d("Auto-Adapting grid size to height <%s> width <%s>", binding.rowsFragment.getHeight(), binding.rowsFragment.getWidth());
mDirty = true;
determiningPosterSize = true;
setAutoCardGridValues();
createGrid();
loadGrid();
determiningPosterSize = false;
}
}
});
return binding.getRoot();
}
@@ -223,19 +201,15 @@ public class BrowseGridFragment extends Fragment implements View.OnKeyListener {
@Override
public boolean onKey(View v, int keyCode, KeyEvent event) {
if (event.getAction() != KeyEvent.ACTION_DOWN) return false;
if (keyCode == KeyEvent.KEYCODE_MEDIA_PLAY || keyCode == KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE) {
mediaManager.getValue().setCurrentMediaAdapter(mAdapter);
mediaManager.getValue().setCurrentMediaPosition(mCurrentItem.getIndex());
mediaManager.getValue().setCurrentMediaTitle(mFolder.getName());
}
if (event.getAction() != KeyEvent.ACTION_UP) return false;
return KeyProcessor.HandleKey(keyCode, mCurrentItem, mActivity);
}
@Override
public void onDestroyView() {
super.onDestroyView();
binding = null;
mGridView = null;
}
@@ -244,7 +218,9 @@ public class BrowseGridFragment extends Fragment implements View.OnKeyListener {
if (mGridViewHolder instanceof HorizontalGridPresenter.ViewHolder) {
mGridView = ((HorizontalGridPresenter.ViewHolder) mGridViewHolder).getGridView();
mGridView.setGravity(Gravity.CENTER_VERTICAL);
mGridView.setPadding(mGridPaddingLeft, mGridPaddingTop, mGridPaddingLeft, mGridPaddingTop); // prevent initial card cutoffs
ViewGroup.MarginLayoutParams titleMargin = (ViewGroup.MarginLayoutParams) binding.title.getLayoutParams();
ViewGroup.MarginLayoutParams clockMargin = (ViewGroup.MarginLayoutParams) binding.clock.getLayoutParams();
mGridView.setPadding(titleMargin.getMarginStart(), mGridPaddingTop, clockMargin.getMarginEnd(), mGridPaddingTop); // prevent initial card cutoffs
} else if (mGridViewHolder instanceof VerticalGridPresenter.ViewHolder) {
mGridView = ((VerticalGridPresenter.ViewHolder) mGridViewHolder).getGridView();
mGridView.setGravity(Gravity.CENTER_HORIZONTAL);
@@ -594,7 +570,7 @@ public class BrowseGridFragment extends Fragment implements View.OnKeyListener {
//Special queries needed for album artists
String includeType = getArguments().getString(Extras.IncludeType);
if ("AlbumArtist".equals(includeType)) {
ArtistsQuery albumArtists = new ArtistsQuery();
AlbumArtistsQuery albumArtists = new AlbumArtistsQuery();
albumArtists.setUserId(userRepository.getValue().getCurrentUser().getValue().getId().toString());
albumArtists.setFields(new ItemFields[]{
ItemFields.PrimaryImageAspectRatio,
@@ -604,6 +580,17 @@ public class BrowseGridFragment extends Fragment implements View.OnKeyListener {
albumArtists.setParentId(mParentId.toString());
setRowDef(new BrowseRowDef("", albumArtists, CHUNK_SIZE_MINIMUM, new ChangeTriggerType[]{}));
return;
} else if ("Artist".equals(includeType)) {
ArtistsQuery artists = new ArtistsQuery();
artists.setUserId(userRepository.getValue().getCurrentUser().getValue().getId().toString());
artists.setFields(new ItemFields[]{
ItemFields.PrimaryImageAspectRatio,
ItemFields.ItemCounts,
ItemFields.ChildCount
});
artists.setParentId(mParentId.toString());
setRowDef(new BrowseRowDef("", artists, CHUNK_SIZE_MINIMUM, new ChangeTriggerType[]{}));
return;
}
query.setIncludeItemTypes(new String[]{includeType != null ? includeType : "MusicAlbum"});
query.setRecursive(true);
@@ -705,9 +692,12 @@ public class BrowseGridFragment extends Fragment implements View.OnKeyListener {
case LiveTvRecording:
mAdapter = new ItemRowAdapter(requireContext(), mRowDef.getRecordingQuery(), chunkSize, mCardPresenter, null);
break;
case AlbumArtists:
case Artists:
mAdapter = new ItemRowAdapter(requireContext(), mRowDef.getArtistsQuery(), chunkSize, mCardPresenter, null);
break;
case AlbumArtists:
mAdapter = new ItemRowAdapter(requireContext(), mRowDef.getAlbumArtistsQuery(), chunkSize, mCardPresenter, null);
break;
default:
mAdapter = new ItemRowAdapter(requireContext(), mRowDef.getQuery(), chunkSize, mRowDef.getPreferParentThumb(), mRowDef.isStaticHeight(), mCardPresenter, null);
break;
@@ -729,16 +719,17 @@ public class BrowseGridFragment extends Fragment implements View.OnKeyListener {
updateCounter(mAdapter.getTotalItems() > 0 ? 1 : 0);
}
mLetterButton.setVisibility(ItemSortBy.SortName.equals(mAdapter.getSortBy()) ? View.VISIBLE : View.GONE);
if (mAdapter.getTotalItems() == 0) {
binding.toolBar.requestFocus();
if (mAdapter.getItemsLoaded() == 0) {
mGridView.setFocusable(false);
mHandler.postDelayed(() -> {
if (!getLifecycle().getCurrentState().isAtLeast(Lifecycle.State.STARTED))
return;
binding.title.setText(mFolder.getName());
}, 500);
} else {
if (mGridView != null) mGridView.requestFocus();
} else if (mGridView != null) {
mGridView.setFocusable(true);
mGridView.requestFocus();
}
}
});
@@ -767,7 +758,7 @@ public class BrowseGridFragment extends Fragment implements View.OnKeyListener {
libraryPreferences.set(LibraryPreferences.Companion.getFilterUnwatchedOnly(), mAdapter.getFilters().isUnwatchedOnly());
libraryPreferences.set(LibraryPreferences.Companion.getSortBy(), mAdapter.getSortBy());
libraryPreferences.set(LibraryPreferences.Companion.getSortOrder(), getSortOption(mAdapter.getSortBy()).order);
CoroutineUtils.runBlocking((coroutineScope, continuation) -> libraryPreferences.commit(continuation));
CoroutineUtils.runOnLifecycle(getLifecycle(), (coroutineScope, continuation) -> libraryPreferences.commit(continuation));
}
private void addTools() {
@@ -937,32 +928,26 @@ public class BrowseGridFragment extends Fragment implements View.OnKeyListener {
}
private void refreshCurrentItem() {
if (mediaManager.getValue().getCurrentMediaPosition() >= 0) {
mCurrentItem = mediaManager.getValue().getCurrentMediaItem();
if (mGridPresenter instanceof HorizontalGridPresenter)
((HorizontalGridPresenter) mGridPresenter).setPosition(mediaManager.getValue().getCurrentMediaPosition());
// Don't do anything for vertical grids as the presenter does not allow setting the position
mediaManager.getValue().setCurrentMediaPosition(-1); // re-set so it doesn't mess with parent views
}
if (mCurrentItem != null && mCurrentItem.getBaseItemType() != BaseItemKind.PHOTO && mCurrentItem.getBaseItemType() != BaseItemKind.PHOTO_ALBUM
&& mCurrentItem.getBaseItemType() != BaseItemKind.MUSIC_ARTIST && mCurrentItem.getBaseItemType() != BaseItemKind.MUSIC_ALBUM) {
Timber.d("Refresh item \"%s\"", mCurrentItem.getFullName(requireContext()));
mCurrentItem.refresh(new EmptyLifecycleAwareResponse(getLifecycle()) {
BaseRowItem item = mCurrentItem;
item.refresh(new LifecycleAwareResponse<BaseItemDto>(getLifecycle()) {
@Override
public void onResponse() {
public void onResponse(BaseItemDto response) {
if (!getActive()) return;
mAdapter.notifyItemRangeChanged(mAdapter.indexOf(mCurrentItem), 1);
if (response == null) mAdapter.removeAt(mAdapter.indexOf(item), 1);
else mAdapter.notifyItemRangeChanged(mAdapter.indexOf(item), 1);
//Now - if filtered make sure we still pass
if (mAdapter.getFilters() != null) {
if ((mAdapter.getFilters().isFavoriteOnly() && !mCurrentItem.isFavorite()) || (mAdapter.getFilters().isUnwatchedOnly() && mCurrentItem.isPlayed())) {
//if we are about to remove last item, throw focus to toolbar so framework doesn't crash
if (mAdapter.size() == 1) binding.toolBar.requestFocus();
mAdapter.remove(mCurrentItem);
if (response != null && mAdapter.getFilters() != null) {
if ((mAdapter.getFilters().isFavoriteOnly() && !item.isFavorite()) || (mAdapter.getFilters().isUnwatchedOnly() && item.isPlayed())) {
// if we are about to remove the current item, throw focus to toolbar so framework doesn't crash
binding.toolBar.requestFocus();
mAdapter.remove(item);
mAdapter.setTotalItems(mAdapter.getTotalItems() - 1);
updateCounter(mCurrentItem.getIndex());
updateCounter(item.getIndex());
}
}
}
@@ -976,7 +961,7 @@ public class BrowseGridFragment extends Fragment implements View.OnKeyListener {
RowPresenter.ViewHolder rowViewHolder, Row row) {
if (!(item instanceof BaseRowItem)) return;
ItemLauncher.launch((BaseRowItem) item, mAdapter, ((BaseRowItem) item).getIndex(), getActivity());
ItemLauncher.launch((BaseRowItem) item, mAdapter, ((BaseRowItem) item).getIndex(), requireContext());
}
}

View File

@@ -182,8 +182,8 @@ public class BrowseRecordingsFragment extends EnhancedBrowseFragment {
GridButtonPresenter mGridPresenter = new GridButtonPresenter();
ArrayObjectAdapter gridRowAdapter = new ArrayObjectAdapter(mGridPresenter);
gridRowAdapter.add(new GridButton(SCHEDULE, getString(R.string.lbl_schedule), R.drawable.tile_port_time));
gridRowAdapter.add(new GridButton(SERIES, getString(R.string.lbl_series_recordings), R.drawable.tile_port_series_timer));
gridRowAdapter.add(new GridButton(SCHEDULE, getString(R.string.lbl_schedule)));
gridRowAdapter.add(new GridButton(SERIES, getString(R.string.lbl_series_recordings)));
rowAdapter.add(new ListRow(gridHeader, gridRowAdapter));
}
}

View File

@@ -2,6 +2,7 @@ package org.jellyfin.androidtv.ui.browsing;
import org.jellyfin.androidtv.constant.ChangeTriggerType;
import org.jellyfin.androidtv.constant.QueryType;
import org.jellyfin.androidtv.data.querying.AlbumArtistsQuery;
import org.jellyfin.androidtv.data.querying.ViewQuery;
import org.jellyfin.apiclient.model.livetv.LiveTvChannelQuery;
import org.jellyfin.apiclient.model.livetv.RecommendedProgramQuery;
@@ -15,6 +16,7 @@ import org.jellyfin.apiclient.model.querying.PersonsQuery;
import org.jellyfin.apiclient.model.querying.SeasonQuery;
import org.jellyfin.apiclient.model.querying.SimilarItemsQuery;
import org.jellyfin.apiclient.model.querying.UpcomingEpisodesQuery;
import org.jellyfin.sdk.model.api.request.GetResumeItemsRequest;
public class BrowseRowDef {
private String headerText;
@@ -32,7 +34,9 @@ public class BrowseRowDef {
private SeriesTimerQuery seriesTimerQuery;
private ArtistsQuery artistsQuery;
private AlbumArtistsQuery albumArtistsQuery;
private SeasonQuery seasonQuery;
private GetResumeItemsRequest resumeQuery;
private QueryType queryType;
private int chunkSize = 0;
@@ -79,9 +83,16 @@ public class BrowseRowDef {
headerText = header;
this.artistsQuery = query;
this.chunkSize = chunkSize;
this.queryType = QueryType.Artists;
this.changeTriggers = changeTriggers;
}
public BrowseRowDef(String header, AlbumArtistsQuery query, int chunkSize, ChangeTriggerType[] changeTriggers) {
headerText = header;
this.albumArtistsQuery = query;
this.chunkSize = chunkSize;
this.queryType = QueryType.AlbumArtists;
this.changeTriggers = changeTriggers;
}
public BrowseRowDef(String header, NextUpQuery query) {
@@ -174,6 +185,16 @@ public class BrowseRowDef {
this.queryType = QueryType.Views;
}
public BrowseRowDef(String header, GetResumeItemsRequest query, int chunkSize, boolean preferParentThumb, boolean staticHeight, ChangeTriggerType[] changeTriggers) {
headerText = header;
this.resumeQuery = query;
this.chunkSize = chunkSize;
this.queryType = QueryType.Resume;
this.staticHeight = staticHeight;
this.preferParentThumb = preferParentThumb;
this.changeTriggers = changeTriggers;
}
public int getChunkSize() {
return chunkSize;
}
@@ -227,9 +248,12 @@ public class BrowseRowDef {
}
public ArtistsQuery getArtistsQuery() { return artistsQuery; }
public AlbumArtistsQuery getAlbumArtistsQuery() { return albumArtistsQuery; }
public SeriesTimerQuery getSeriesTimerQuery() { return seriesTimerQuery; }
public GetResumeItemsRequest getResumeQuery() { return resumeQuery; }
public ChangeTriggerType[] getChangeTriggers() {
return changeTriggers;
}

View File

@@ -133,6 +133,24 @@ public class BrowseViewFragment extends EnhancedBrowseFragment {
case CollectionType.TvShows:
itemTypeString = "Series";
//Resume
StdItemQuery resumeEpisodes = new StdItemQuery(new ItemFields[]{
ItemFields.PrimaryImageAspectRatio,
ItemFields.Overview,
ItemFields.ChildCount,
ItemFields.MediaSources,
ItemFields.MediaStreams
});
resumeEpisodes.setIncludeItemTypes(new String[]{"Episode"});
resumeEpisodes.setLimit(50);
resumeEpisodes.setParentId(mFolder.getId().toString());
resumeEpisodes.setRecursive(true);
resumeEpisodes.setImageTypeLimit(1);
resumeEpisodes.setFilters(new ItemFilter[]{ItemFilter.IsResumable});
resumeEpisodes.setSortBy(new String[]{ItemSortBy.DatePlayed});
resumeEpisodes.setSortOrder(SortOrder.Descending);
mRows.add(new BrowseRowDef(getString(R.string.lbl_continue_watching), resumeEpisodes, 0, new ChangeTriggerType[]{ChangeTriggerType.TvPlayback}));
//Next up
NextUpQuery nextUpQuery = new NextUpQuery();
nextUpQuery.setUserId(KoinJavaComponent.<UserRepository>get(UserRepository.class).getCurrentUser().getValue().getId().toString());
@@ -475,27 +493,11 @@ public class BrowseViewFragment extends EnhancedBrowseFragment {
GridButtonPresenter mGridPresenter = new GridButtonPresenter();
ArrayObjectAdapter gridRowAdapter = new ArrayObjectAdapter(mGridPresenter);
gridRowAdapter.add(new GridButton(
LiveTvOption.LIVE_TV_GUIDE_OPTION_ID,
getString(R.string.lbl_live_tv_guide),
R.drawable.tile_port_guide
));
gridRowAdapter.add(new GridButton(
LiveTvOption.LIVE_TV_RECORDINGS_OPTION_ID,
getString(R.string.lbl_recorded_tv),
R.drawable.tile_port_record
));
gridRowAdapter.add(new GridButton(LiveTvOption.LIVE_TV_GUIDE_OPTION_ID, getString(R.string.lbl_live_tv_guide)));
gridRowAdapter.add(new GridButton(LiveTvOption.LIVE_TV_RECORDINGS_OPTION_ID, getString(R.string.lbl_recorded_tv)));
if (Utils.canManageRecordings(KoinJavaComponent.<UserRepository>get(UserRepository.class).getCurrentUser().getValue())) {
gridRowAdapter.add(new GridButton(
LiveTvOption.LIVE_TV_SCHEDULE_OPTION_ID,
getString(R.string.lbl_schedule),
R.drawable.tile_port_time
));
gridRowAdapter.add(new GridButton(
LiveTvOption.LIVE_TV_SERIES_OPTION_ID,
getString(R.string.lbl_series),
R.drawable.tile_port_series_timer
));
gridRowAdapter.add(new GridButton(LiveTvOption.LIVE_TV_SCHEDULE_OPTION_ID, getString(R.string.lbl_schedule)));
gridRowAdapter.add(new GridButton(LiveTvOption.LIVE_TV_SERIES_OPTION_ID, getString(R.string.lbl_series)));
}
mRowsAdapter.add(new ListRow(gridHeader, gridRowAdapter));

View File

@@ -15,6 +15,7 @@ class ByLetterFragment : BrowseFolderFragment() {
val numbersQuery = StdItemQuery().apply {
parentId = folder?.id?.toString()
sortBy = arrayOf(ItemSortBy.SortName)
includeType?.let { includeItemTypes = arrayOf(it) }
nameLessThan = letters.substring(0, 1)
recursive = true
}

View File

@@ -1,52 +1,52 @@
package org.jellyfin.androidtv.ui.browsing;
import android.os.Bundle;
import org.jellyfin.androidtv.R;
import org.jellyfin.androidtv.data.querying.StdItemQuery;
import org.jellyfin.androidtv.util.Utils;
import org.jellyfin.apiclient.model.querying.ItemFields;
import org.jellyfin.sdk.model.api.BaseItemKind;
public class CollectionFragment extends EnhancedBrowseFragment {
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
}
@Override
protected void setupQueries(RowLoader rowLoader) {
if (Utils.getSafeValue(mFolder.getChildCount(), 0) > 0) {
StdItemQuery movies = new StdItemQuery(new ItemFields[]{
ItemFields.PrimaryImageAspectRatio,
ItemFields.Overview,
ItemFields.ItemCounts,
ItemFields.DisplayPreferencesId,
ItemFields.ChildCount,
ItemFields.MediaStreams,
ItemFields.MediaSources
});
movies.setParentId(mFolder.getId().toString());
movies.setIncludeItemTypes(new String[]{"Movie"});
mRows.add(new BrowseRowDef(getString(R.string.lbl_movies), movies, 100));
StdItemQuery movies = new StdItemQuery(new ItemFields[]{
ItemFields.PrimaryImageAspectRatio,
ItemFields.Overview,
ItemFields.ItemCounts,
ItemFields.DisplayPreferencesId,
ItemFields.ChildCount,
ItemFields.MediaStreams,
ItemFields.MediaSources
});
movies.setParentId(mFolder.getId().toString());
movies.setIncludeItemTypes(new String[]{BaseItemKind.MOVIE.getSerialName()});
mRows.add(new BrowseRowDef(getString(R.string.lbl_movies), movies, 100));
StdItemQuery series = new StdItemQuery();
series.setParentId(mFolder.getId().toString());
series.setIncludeItemTypes(new String[]{"Series"});
mRows.add(new BrowseRowDef(getString(R.string.lbl_tv_series), series, 100));
StdItemQuery others = new StdItemQuery();
others.setParentId(mFolder.getId().toString());
others.setExcludeItemTypes(new String[]{"Movie", "Series"});
mRows.add(new BrowseRowDef(getString(R.string.lbl_other), others, 100));
rowLoader.loadRows(mRows);
}
StdItemQuery series = new StdItemQuery(new ItemFields[]{
ItemFields.PrimaryImageAspectRatio,
ItemFields.Overview,
ItemFields.ItemCounts,
ItemFields.DisplayPreferencesId,
ItemFields.ChildCount,
ItemFields.MediaStreams,
ItemFields.MediaSources
});
series.setParentId(mFolder.getId().toString());
series.setIncludeItemTypes(new String[]{BaseItemKind.SERIES.getSerialName()});
mRows.add(new BrowseRowDef(getString(R.string.lbl_tv_series), series, 100));
StdItemQuery others = new StdItemQuery(new ItemFields[]{
ItemFields.PrimaryImageAspectRatio,
ItemFields.Overview,
ItemFields.ItemCounts,
ItemFields.DisplayPreferencesId,
ItemFields.ChildCount,
ItemFields.MediaStreams,
ItemFields.MediaSources
});
others.setParentId(mFolder.getId().toString());
others.setExcludeItemTypes(new String[]{BaseItemKind.MOVIE.getSerialName(), BaseItemKind.SERIES.getSerialName()});
mRows.add(new BrowseRowDef(getString(R.string.lbl_other), others, 100));
rowLoader.loadRows(mRows);
}
}

View File

@@ -43,7 +43,6 @@ import org.jellyfin.androidtv.ui.itemhandling.ItemLauncher;
import org.jellyfin.androidtv.ui.itemhandling.ItemRowAdapter;
import org.jellyfin.androidtv.ui.navigation.Destinations;
import org.jellyfin.androidtv.ui.navigation.NavigationRepository;
import org.jellyfin.androidtv.ui.playback.MediaManager;
import org.jellyfin.androidtv.ui.presentation.CardPresenter;
import org.jellyfin.androidtv.ui.presentation.GridButtonPresenter;
import org.jellyfin.androidtv.ui.presentation.MutableObjectAdapter;
@@ -52,7 +51,7 @@ import org.jellyfin.androidtv.util.CoroutineUtils;
import org.jellyfin.androidtv.util.InfoLayoutHelper;
import org.jellyfin.androidtv.util.KeyProcessor;
import org.jellyfin.androidtv.util.MarkdownRenderer;
import org.jellyfin.androidtv.util.apiclient.EmptyLifecycleAwareResponse;
import org.jellyfin.androidtv.util.apiclient.LifecycleAwareResponse;
import org.jellyfin.androidtv.util.sdk.compat.FakeBaseItem;
import org.jellyfin.androidtv.util.sdk.compat.JavaCompat;
import org.jellyfin.sdk.model.api.BaseItemDto;
@@ -80,6 +79,7 @@ public class EnhancedBrowseFragment extends Fragment implements RowLoader, View.
public static final int FAVSONGS = 9;
protected static final int SCHEDULE = 10;
protected static final int SERIES = 11;
protected static final int ALBUM_ARTISTS = 12;
protected BaseItemDto mFolder;
protected String itemTypeString;
protected boolean showViews = true;
@@ -97,7 +97,6 @@ public class EnhancedBrowseFragment extends Fragment implements RowLoader, View.
protected ListRow mCurrentRow;
private Lazy<BackgroundService> backgroundService = inject(BackgroundService.class);
private Lazy<MediaManager> mediaManager = inject(MediaManager.class);
private Lazy<MarkdownRenderer> markdownRenderer = inject(MarkdownRenderer.class);
private final Lazy<CustomMessageRepository> customMessageRepository = inject(CustomMessageRepository.class);
private final Lazy<NavigationRepository> navigationRepository = inject(NavigationRepository.class);
@@ -108,7 +107,6 @@ public class EnhancedBrowseFragment extends Fragment implements RowLoader, View.
favSongsRowItem = new BaseRowItem(FakeBaseItem.INSTANCE.getFAV_SONGS());
mRowsAdapter = new MutableObjectAdapter<Row>(new PositionableListRowPresenter());
backgroundService.getValue().attach(requireActivity());
setupViews();
setupQueries(this);
@@ -190,7 +188,7 @@ public class EnhancedBrowseFragment extends Fragment implements RowLoader, View.
// React to deletion
DataRefreshService dataRefreshService = KoinJavaComponent.<DataRefreshService>get(DataRefreshService.class);
if (mCurrentRow != null && mCurrentItem != null && mCurrentItem.getItemId() != null && mCurrentItem.getItemId().equals(dataRefreshService.getLastDeletedItemId())) {
if (mCurrentRow != null && mCurrentItem != null && mCurrentItem.getItemId() != null && mCurrentItem.getBaseItem().getId().equals(dataRefreshService.getLastDeletedItemId())) {
((ItemRowAdapter) mCurrentRow.getAdapter()).remove(mCurrentItem);
dataRefreshService.setLastDeletedItemId(null);
}
@@ -225,7 +223,7 @@ public class EnhancedBrowseFragment extends Fragment implements RowLoader, View.
mCardPresenter = new CardPresenter(false, 140);
ClassPresenterSelector ps = new ClassPresenterSelector();
ps.addClassPresenter(BaseRowItem.class, mCardPresenter);
ps.addClassPresenter(GridButton.class, new GridButtonPresenter(false, 155, 140));
ps.addClassPresenter(GridButton.class, new GridButtonPresenter(155, 140));
for (BrowseRowDef def : rows) {
HeaderItem header = new HeaderItem(def.getHeaderText());
@@ -297,14 +295,15 @@ public class EnhancedBrowseFragment extends Fragment implements RowLoader, View.
switch (itemTypeString) {
case "Movie":
gridRowAdapter.add(new GridButton(SUGGESTED, getString(R.string.lbl_suggested), R.drawable.tile_suggestions));
gridRowAdapter.add(new GridButton(SUGGESTED, getString(R.string.lbl_suggested)));
addStandardViewButtons(gridRowAdapter);
break;
case "MusicAlbum":
gridRowAdapter.add(new GridButton(ALBUMS, getString(R.string.lbl_albums), R.drawable.tile_audio));
gridRowAdapter.add(new GridButton(ARTISTS, getString(R.string.lbl_artists), R.drawable.tile_artists));
gridRowAdapter.add(new GridButton(GENRES, getString(R.string.lbl_genres), R.drawable.tile_genres));
gridRowAdapter.add(new GridButton(ALBUMS, getString(R.string.lbl_albums)));
gridRowAdapter.add(new GridButton(ALBUM_ARTISTS, getString(R.string.lbl_album_artists)));
gridRowAdapter.add(new GridButton(ARTISTS, getString(R.string.lbl_artists)));
gridRowAdapter.add(new GridButton(GENRES, getString(R.string.lbl_genres)));
break;
default:
@@ -316,11 +315,11 @@ public class EnhancedBrowseFragment extends Fragment implements RowLoader, View.
}
protected void addStandardViewButtons(ArrayObjectAdapter gridRowAdapter) {
gridRowAdapter.add(new GridButton(GRID, getString(R.string.lbl_all_items), R.drawable.tile_port_grid));
gridRowAdapter.add(new GridButton(BY_LETTER, getString(R.string.lbl_by_letter), R.drawable.tile_letters));
gridRowAdapter.add(new GridButton(GENRES, getString(R.string.lbl_genres), R.drawable.tile_genres));
gridRowAdapter.add(new GridButton(GRID, getString(R.string.lbl_all_items)));
gridRowAdapter.add(new GridButton(BY_LETTER, getString(R.string.lbl_by_letter)));
gridRowAdapter.add(new GridButton(GENRES, getString(R.string.lbl_genres)));
// Disabled because the screen doesn't behave properly
// gridRowAdapter.add(new GridButton(PERSONS, getString(R.string.lbl_performers), R.drawable.tile_actors));
// gridRowAdapter.add(new GridButton(PERSONS, getString(R.string.lbl_performers)));
}
protected void setupEventListeners() {
@@ -339,7 +338,7 @@ public class EnhancedBrowseFragment extends Fragment implements RowLoader, View.
@Override
public boolean onKey(View v, int keyCode, KeyEvent event) {
if (event.getAction() != KeyEvent.ACTION_DOWN) return false;
if (event.getAction() != KeyEvent.ACTION_UP) return false;
return KeyProcessor.HandleKey(keyCode, mCurrentItem, requireActivity());
}
@@ -350,13 +349,15 @@ public class EnhancedBrowseFragment extends Fragment implements RowLoader, View.
mCurrentItem.getBaseItemType() != BaseItemKind.MUSIC_ALBUM &&
mCurrentItem.getBaseItemType() != BaseItemKind.PLAYLIST
) {
mCurrentItem.refresh(new EmptyLifecycleAwareResponse(getLifecycle()) {
BaseRowItem item = mCurrentItem;
item.refresh(new LifecycleAwareResponse<BaseItemDto>(getLifecycle()) {
@Override
public void onResponse() {
public void onResponse(BaseItemDto response) {
if (!getActive()) return;
ItemRowAdapter adapter = (ItemRowAdapter) mCurrentRow.getAdapter();
adapter.notifyItemRangeChanged(adapter.indexOf(mCurrentItem), 1);
if (response == null) adapter.removeAt(adapter.indexOf(item), 1);
else adapter.notifyItemRangeChanged(adapter.indexOf(item), 1);
}
});
}
@@ -377,12 +378,18 @@ public class EnhancedBrowseFragment extends Fragment implements RowLoader, View.
navigationRepository.getValue().navigate(Destinations.INSTANCE.libraryBrowser(mFolder, "MusicAlbum"));
break;
case ARTISTS:
case ALBUM_ARTISTS:
mFolder = JavaCompat.copyWithDisplayPreferencesId(mFolder, mFolder.getId() + "AR");
navigationRepository.getValue().navigate(Destinations.INSTANCE.libraryBrowser(mFolder, "AlbumArtist"));
break;
case ARTISTS:
mFolder = JavaCompat.copyWithDisplayPreferencesId(mFolder, mFolder.getId() + "AR");
navigationRepository.getValue().navigate(Destinations.INSTANCE.libraryBrowser(mFolder, "Artist"));
break;
case BY_LETTER:
navigationRepository.getValue().navigate(Destinations.INSTANCE.libraryByLetter(mFolder, itemTypeString));
break;
@@ -430,8 +437,7 @@ public class EnhancedBrowseFragment extends Fragment implements RowLoader, View.
public void onItemClicked(final Presenter.ViewHolder itemViewHolder, Object item, RowPresenter.ViewHolder rowViewHolder, Row row) {
if (!(item instanceof BaseRowItem)) return;
mediaManager.getValue().setCurrentMediaTitle(row.getHeaderItem().getName());
ItemLauncher.launch((BaseRowItem) item, (ItemRowAdapter) ((ListRow) row).getAdapter(), ((BaseRowItem) item).getIndex(), getActivity());
ItemLauncher.launch((BaseRowItem) item, (ItemRowAdapter) ((ListRow) row).getAdapter(), ((BaseRowItem) item).getIndex(), requireContext());
}
}

View File

@@ -51,7 +51,7 @@ public class GenericFolderFragment extends EnhancedBrowseFragment {
latest.setFilters(new ItemFilter[]{ItemFilter.IsUnplayed});
latest.setSortBy(new String[]{ItemSortBy.DateCreated});
latest.setSortOrder(SortOrder.Descending);
mRows.add(new BrowseRowDef(getString(R.string.lbl_latest_additions), latest, 0));
mRows.add(new BrowseRowDef(getString(R.string.lbl_latest), latest, 0));
}

View File

@@ -3,30 +3,47 @@ package org.jellyfin.androidtv.ui.browsing
import android.content.Intent
import android.os.Bundle
import android.view.KeyEvent
import android.view.MotionEvent
import android.view.View
import android.view.WindowManager
import androidx.activity.OnBackPressedCallback
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentActivity
import androidx.fragment.app.commit
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.flowWithLifecycle
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch
import org.jellyfin.androidtv.R
import org.jellyfin.androidtv.auth.ui.validateAuthentication
import org.jellyfin.androidtv.data.service.BackgroundService
import org.jellyfin.androidtv.auth.repository.SessionRepository
import org.jellyfin.androidtv.auth.repository.UserRepository
import org.jellyfin.androidtv.databinding.ActivityMainBinding
import org.jellyfin.androidtv.ui.ScreensaverViewModel
import org.jellyfin.androidtv.ui.background.AppBackground
import org.jellyfin.androidtv.ui.navigation.NavigationAction
import org.jellyfin.androidtv.ui.navigation.NavigationRepository
import org.jellyfin.androidtv.ui.screensaver.InAppScreensaver
import org.jellyfin.androidtv.ui.startup.StartupActivity
import org.jellyfin.androidtv.util.applyTheme
import org.jellyfin.androidtv.util.isMediaSessionKeyEvent
import org.koin.android.ext.android.inject
import org.koin.androidx.viewmodel.ext.android.viewModel
import timber.log.Timber
class MainActivity : FragmentActivity(R.layout.fragment_content_view) {
class MainActivity : FragmentActivity() {
companion object {
private const val FRAGMENT_TAG_CONTENT = "content"
}
private val backgroundService by inject<BackgroundService>()
private val navigationRepository by inject<NavigationRepository>()
private val sessionRepository by inject<SessionRepository>()
private val userRepository by inject<UserRepository>()
private val screensaverViewModel by viewModel<ScreensaverViewModel>()
private var inTransaction = false
private lateinit var binding: ActivityMainBinding
private val backPressedCallback = object : OnBackPressedCallback(false) {
override fun handleOnBackPressed() {
@@ -35,30 +52,39 @@ class MainActivity : FragmentActivity(R.layout.fragment_content_view) {
}
override fun onCreate(savedInstanceState: Bundle?) {
applyTheme()
super.onCreate(savedInstanceState)
if (!validateAuthentication()) return
applyTheme()
screensaverViewModel.keepScreenOn.flowWithLifecycle(lifecycle, Lifecycle.State.RESUMED)
.onEach { keepScreenOn ->
if (keepScreenOn) window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
else window.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
}.launchIn(lifecycleScope)
backgroundService.attach(this)
onBackPressedDispatcher.addCallback(this, backPressedCallback)
supportFragmentManager.addOnBackStackChangedListener {
if (supportFragmentManager.backStackEntryCount == 0)
if (!inTransaction && supportFragmentManager.backStackEntryCount == 0)
navigationRepository.reset()
}
if (savedInstanceState == null && navigationRepository.canGoBack) navigationRepository.reset()
lifecycleScope.launch {
lifecycle.repeatOnLifecycle(Lifecycle.State.RESUMED) {
navigationRepository.currentAction.collect { action ->
handleNavigationAction(action)
backPressedCallback.isEnabled = navigationRepository.canGoBack
}
}
}
navigationRepository.currentAction
.flowWithLifecycle(lifecycle, Lifecycle.State.RESUMED)
.onEach { action ->
handleNavigationAction(action)
backPressedCallback.isEnabled = navigationRepository.canGoBack
screensaverViewModel.notifyInteraction(false)
}.launchIn(lifecycleScope)
binding = ActivityMainBinding.inflate(layoutInflater)
binding.background.setContent { AppBackground() }
binding.screensaver.setContent { InAppScreensaver() }
setContentView(binding.root)
}
override fun onResume() {
@@ -67,24 +93,61 @@ class MainActivity : FragmentActivity(R.layout.fragment_content_view) {
if (!validateAuthentication()) return
applyTheme()
screensaverViewModel.activityPaused = false
}
private fun validateAuthentication(): Boolean {
if (sessionRepository.currentSession.value == null || userRepository.currentUser.value == null) {
Timber.w("Activity ${this::class.qualifiedName} started without a session, bouncing to StartupActivity")
startActivity(Intent(this, StartupActivity::class.java))
finish()
return false
}
return true
}
override fun onPause() {
super.onPause()
screensaverViewModel.activityPaused = true
}
override fun onStop() {
super.onStop()
lifecycleScope.launch {
Timber.d("MainActivity stopped")
sessionRepository.restoreSession(destroyOnly = true)
}
}
private fun handleNavigationAction(action: NavigationAction) = when (action) {
is NavigationAction.NavigateFragment -> supportFragmentManager.commit {
val destination = action.destination
val currentFragment = supportFragmentManager.findFragmentByTag(FRAGMENT_TAG_CONTENT)
val isSameFragment = currentFragment != null &&
destination.fragment.isInstance(currentFragment) &&
currentFragment.arguments == destination.arguments
if (!isSameFragment) {
setCustomAnimations(R.anim.fade_in, R.anim.fade_out, R.anim.fade_in, R.anim.fade_out)
if (currentFragment != null) remove(currentFragment)
add(R.id.content_view, destination.fragment.java, destination.arguments, FRAGMENT_TAG_CONTENT)
is NavigationAction.NavigateFragment -> {
if (action.replace) {
// Prevent back stack changed listener from resetting when popping to
// the initial destination
inTransaction = true
supportFragmentManager.popBackStack()
inTransaction = false
}
if (action.addToBackStack) addToBackStack(null)
supportFragmentManager.commit {
val destination = action.destination
val currentFragment = supportFragmentManager.findFragmentByTag(FRAGMENT_TAG_CONTENT)
val isSameFragment = currentFragment != null &&
destination.fragment.isInstance(currentFragment) &&
currentFragment.arguments == destination.arguments
if (!isSameFragment) {
setCustomAnimations(R.anim.fade_in, R.anim.fade_out, R.anim.fade_in, R.anim.fade_out)
replace(R.id.content_view, destination.fragment.java, destination.arguments, FRAGMENT_TAG_CONTENT)
}
if (action.addToBackStack) addToBackStack(null)
}
}
is NavigationAction.NavigateActivity -> {
@@ -107,12 +170,63 @@ class MainActivity : FragmentActivity(R.layout.fragment_content_view) {
return result
}
private fun onKeyEvent(keyCode: Int, event: KeyEvent?): Boolean = supportFragmentManager.fragments
.any { it.onKeyEvent(keyCode, event) }
private fun onKeyEvent(keyCode: Int, event: KeyEvent?): Boolean {
// Ignore the key event that closes the screensaver
if (screensaverViewModel.visible.value) {
screensaverViewModel.notifyInteraction(canCancel = event?.action == KeyEvent.ACTION_UP)
return true
}
return supportFragmentManager.fragments
.any { it.onKeyEvent(keyCode, event) }
}
override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean =
onKeyEvent(keyCode, event) || super.onKeyDown(keyCode, event)
override fun onKeyUp(keyCode: Int, event: KeyEvent?): Boolean =
onKeyEvent(keyCode, event) || super.onKeyUp(keyCode, event)
override fun onKeyLongPress(keyCode: Int, event: KeyEvent?): Boolean =
onKeyEvent(keyCode, event) || super.onKeyUp(keyCode, event)
override fun onUserInteraction() {
super.onUserInteraction()
screensaverViewModel.notifyInteraction(false)
}
@Suppress("RestrictedApi") // False positive
override fun dispatchKeyEvent(event: KeyEvent): Boolean {
// Ignore the key event that closes the screensaver
if (!event.isMediaSessionKeyEvent() && screensaverViewModel.visible.value) {
screensaverViewModel.notifyInteraction(canCancel = event.action == KeyEvent.ACTION_UP)
return true
}
@Suppress("RestrictedApi") // False positive
return super.dispatchKeyEvent(event)
}
@Suppress("RestrictedApi") // False positive
override fun dispatchKeyShortcutEvent(event: KeyEvent): Boolean {
// Ignore the key event that closes the screensaver
if (!event.isMediaSessionKeyEvent() && screensaverViewModel.visible.value) {
screensaverViewModel.notifyInteraction(canCancel = event.action == KeyEvent.ACTION_UP)
return true
}
@Suppress("RestrictedApi") // False positive
return super.dispatchKeyShortcutEvent(event)
}
override fun dispatchTouchEvent(ev: MotionEvent?): Boolean {
// Ignore the touch event that closes the screensaver
if (screensaverViewModel.visible.value) {
screensaverViewModel.notifyInteraction(true)
return true
}
return super.dispatchTouchEvent(ev)
}
}

Some files were not shown because too many files have changed in this diff Show More