Skip to content

Commit 2006652

Browse files
authored
Merge branch 'master' into basraven-add-screensaver-types
2 parents 72373bf + db06ff4 commit 2006652

File tree

84 files changed

+1451
-840
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

84 files changed

+1451
-840
lines changed

Diff for: .github/workflows/app-build.yaml

+2-2
Original file line numberDiff line numberDiff line change
@@ -22,13 +22,13 @@ jobs:
2222
distribution: temurin
2323
java-version: 21
2424
- name: Setup Gradle
25-
uses: gradle/actions/setup-gradle@94baf225fe0a508e581a564467443d0e2379123b # v4.3.0
25+
uses: gradle/actions/setup-gradle@06832c7b30a0129d7fb559bcc6e43d26f6374244 # v4.3.1
2626
- name: Assemble debug APKs
2727
run: ./gradlew assembleDebug
2828
- name: Create publish bundle
2929
run: mkdir -p build/gh-app-publish/; find app/build/ -iname "*.apk" -exec mv "{}" build/gh-app-publish/ \;
3030
- name: Upload artifacts
31-
uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # v4.6.1
31+
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
3232
with:
3333
name: build-artifacts
3434
retention-days: 14

Diff for: .github/workflows/app-lint.yaml

+2-2
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,11 @@ jobs:
2424
distribution: temurin
2525
java-version: 21
2626
- name: Setup Gradle
27-
uses: gradle/actions/setup-gradle@94baf225fe0a508e581a564467443d0e2379123b # v4.3.0
27+
uses: gradle/actions/setup-gradle@06832c7b30a0129d7fb559bcc6e43d26f6374244 # v4.3.1
2828
- name: Run detekt and lint tasks
2929
run: ./gradlew detekt lint
3030
- name: Upload SARIF files
31-
uses: github/codeql-action/upload-sarif@6bb031afdd8eb862ea3fc1848194185e076637e5 # v3.28.11
31+
uses: github/codeql-action/upload-sarif@1b549b9259bda1cb5ddde3b41741a82a2d15a841 # v3.28.13
3232
if: ${{ always() }}
3333
with:
3434
sarif_file: .

Diff for: .github/workflows/app-publish.yaml

+1-1
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ jobs:
1919
distribution: temurin
2020
java-version: 21
2121
- name: Setup Gradle
22-
uses: gradle/actions/setup-gradle@94baf225fe0a508e581a564467443d0e2379123b # v4.3.0
22+
uses: gradle/actions/setup-gradle@06832c7b30a0129d7fb559bcc6e43d26f6374244 # v4.3.1
2323
- name: Set JELLYFIN_VERSION
2424
run: echo "JELLYFIN_VERSION=$(echo ${GITHUB_REF#refs/tags/v} | tr / -)" >> $GITHUB_ENV
2525
- name: Assemble release files

Diff for: .github/workflows/app-test.yaml

+1-1
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,6 @@ jobs:
2323
distribution: temurin
2424
java-version: 21
2525
- name: Setup Gradle
26-
uses: gradle/actions/setup-gradle@94baf225fe0a508e581a564467443d0e2379123b # v4.3.0
26+
uses: gradle/actions/setup-gradle@06832c7b30a0129d7fb559bcc6e43d26f6374244 # v4.3.1
2727
- name: Run test task
2828
run: ./gradlew test

Diff for: .github/workflows/gradlew-validate.yaml

+1-1
Original file line numberDiff line numberDiff line change
@@ -19,4 +19,4 @@ jobs:
1919
- name: Checkout repository
2020
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
2121
- name: Validate Gradle Wrapper
22-
uses: gradle/actions/wrapper-validation@94baf225fe0a508e581a564467443d0e2379123b # v4.3.0
22+
uses: gradle/actions/wrapper-validation@06832c7b30a0129d7fb559bcc6e43d26f6374244 # v4.3.1

Diff for: app/build.gradle.kts

-1
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,6 @@ dependencies {
123123
implementation(libs.androidx.cardview)
124124
implementation(libs.androidx.startup)
125125
implementation(libs.bundles.androidx.compose)
126-
implementation(libs.androidx.tv.material)
127126

128127
// Dependency Injection
129128
implementation(libs.bundles.koin)

Diff for: app/src/main/java/org/jellyfin/androidtv/SessionInitializer.kt

+2-1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import androidx.lifecycle.ProcessLifecycleOwner
55
import androidx.lifecycle.lifecycleScope
66
import androidx.startup.AppInitializer
77
import androidx.startup.Initializer
8+
import kotlinx.coroutines.Dispatchers
89
import kotlinx.coroutines.launch
910
import org.jellyfin.androidtv.auth.repository.SessionRepository
1011
import org.jellyfin.androidtv.di.KoinInitializer
@@ -17,7 +18,7 @@ class SessionInitializer : Initializer<Unit> {
1718
.koin
1819

1920
// Restore system session
20-
ProcessLifecycleOwner.get().lifecycleScope.launch {
21+
ProcessLifecycleOwner.get().lifecycleScope.launch(Dispatchers.IO) {
2122
koin.get<SessionRepository>().restoreSession(destroyOnly = false)
2223
}
2324
}

Diff for: app/src/main/java/org/jellyfin/androidtv/auth/repository/AuthenticationRepository.kt

+6-4
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
package org.jellyfin.androidtv.auth.repository
22

3+
import kotlinx.coroutines.Dispatchers
34
import kotlinx.coroutines.flow.Flow
45
import kotlinx.coroutines.flow.emitAll
56
import kotlinx.coroutines.flow.flow
67
import kotlinx.coroutines.flow.flowOf
8+
import kotlinx.coroutines.flow.flowOn
79
import org.jellyfin.androidtv.auth.model.ApiClientErrorLoginState
810
import org.jellyfin.androidtv.auth.model.AuthenticateMethod
911
import org.jellyfin.androidtv.auth.model.AuthenticatedState
@@ -94,7 +96,7 @@ class AuthenticationRepositoryImpl(
9496
}
9597

9698
emitAll(authenticateAuthenticationResult(server, result))
97-
}
99+
}.flowOn(Dispatchers.IO)
98100

99101
private fun authenticateQuickConnect(server: Server, secret: String) = flow {
100102
val api = jellyfin.createApi(server.address, deviceInfo = defaultDeviceInfo)
@@ -112,7 +114,7 @@ class AuthenticationRepositoryImpl(
112114
}
113115

114116
emitAll(authenticateAuthenticationResult(server, result))
115-
}
117+
}.flowOn(Dispatchers.IO)
116118

117119
private fun authenticateAuthenticationResult(server: Server, result: AuthenticationResult) = flow {
118120
val accessToken = result.accessToken ?: return@flow emit(RequireSignInState)
@@ -134,7 +136,7 @@ class AuthenticationRepositoryImpl(
134136
Timber.w("Failed to set active session after authenticating")
135137
emit(RequireSignInState)
136138
}
137-
}
139+
}.flowOn(Dispatchers.IO)
138140

139141
private fun authenticateToken(server: Server, user: User) = flow {
140142
emit(AuthenticatingState)
@@ -155,7 +157,7 @@ class AuthenticationRepositoryImpl(
155157
Timber.e(err, "Unable to get current user data")
156158
emit(ApiClientErrorLoginState(err))
157159
}
158-
}
160+
}.flowOn(Dispatchers.IO)
159161

160162
private suspend fun authenticateFinish(server: Server, userInfo: UserDto, accessToken: String) {
161163
val currentUser = authenticationStore.getUser(server.id, userInfo.id)

Diff for: app/src/main/java/org/jellyfin/androidtv/auth/repository/ServerRepository.kt

+20-14
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
11
package org.jellyfin.androidtv.auth.repository
22

3+
import kotlinx.coroutines.Dispatchers
34
import kotlinx.coroutines.flow.Flow
45
import kotlinx.coroutines.flow.MutableStateFlow
56
import kotlinx.coroutines.flow.StateFlow
67
import kotlinx.coroutines.flow.asStateFlow
78
import kotlinx.coroutines.flow.flow
9+
import kotlinx.coroutines.flow.flowOn
810
import kotlinx.coroutines.flow.map
11+
import kotlinx.coroutines.withContext
912
import org.jellyfin.androidtv.auth.model.AuthenticationStoreServer
1013
import org.jellyfin.androidtv.auth.model.ConnectedState
1114
import org.jellyfin.androidtv.auth.model.ConnectingState
@@ -159,7 +162,7 @@ class ServerRepositoryImpl(
159162
.mapValues { (_, entry) -> entry.flatMap { server -> server.issues } }
160163
emit(UnableToConnectState(addressCandidatesWithIssues))
161164
}
162-
}
165+
}.flowOn(Dispatchers.IO)
163166

164167
override suspend fun getServer(id: UUID): Server? {
165168
val server = authenticationStore.getServer(id) ?: return null
@@ -193,19 +196,22 @@ class ServerRepositoryImpl(
193196
// Only update every 10 minutes
194197
if (now - server.lastRefreshed < 600000 && server.version != null) return null
195198

196-
val api = jellyfin.createApi(server.address)
197-
// Get login disclaimer
198-
val branding = api.getBrandingOptionsOrDefault()
199-
val systemInfo by api.systemApi.getPublicSystemInfo()
200-
201-
val newServer = server.copy(
202-
name = systemInfo.serverName ?: server.name,
203-
version = systemInfo.version ?: server.version,
204-
loginDisclaimer = branding.loginDisclaimer ?: server.loginDisclaimer,
205-
splashscreenEnabled = branding.splashscreenEnabled,
206-
setupCompleted = systemInfo.startupWizardCompleted ?: server.setupCompleted,
207-
lastRefreshed = now
208-
)
199+
val newServer = withContext(Dispatchers.IO) {
200+
val api = jellyfin.createApi(server.address)
201+
202+
// Get login disclaimer
203+
val branding = api.getBrandingOptionsOrDefault()
204+
val systemInfo by api.systemApi.getPublicSystemInfo()
205+
206+
server.copy(
207+
name = systemInfo.serverName ?: server.name,
208+
version = systemInfo.version ?: server.version,
209+
loginDisclaimer = branding.loginDisclaimer ?: server.loginDisclaimer,
210+
splashscreenEnabled = branding.splashscreenEnabled,
211+
setupCompleted = systemInfo.startupWizardCompleted ?: server.setupCompleted,
212+
lastRefreshed = now
213+
)
214+
}
209215
authenticationStore.putServer(id, newServer)
210216

211217
return newServer

Diff for: app/src/main/java/org/jellyfin/androidtv/auth/repository/ServerUserRepository.kt

+5-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package org.jellyfin.androidtv.auth.repository
22

3+
import kotlinx.coroutines.Dispatchers
4+
import kotlinx.coroutines.withContext
35
import org.jellyfin.androidtv.auth.model.PrivateUser
46
import org.jellyfin.androidtv.auth.model.PublicUser
57
import org.jellyfin.androidtv.auth.model.Server
@@ -47,7 +49,9 @@ class ServerUserRepositoryImpl(
4749
val api = jellyfin.createApi(server.address)
4850

4951
return try {
50-
val users by api.userApi.getPublicUsers()
52+
val users = withContext(Dispatchers.IO) {
53+
api.userApi.getPublicUsers().content
54+
}
5155
users.mapNotNull(UserDto::toPublicUser)
5256
} catch (err: ApiClientException) {
5357
Timber.e(err, "Unable to retrieve public users")

Diff for: app/src/main/java/org/jellyfin/androidtv/auth/repository/SessionRepository.kt

+4-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package org.jellyfin.androidtv.auth.repository
22

3+
import kotlinx.coroutines.Dispatchers
34
import kotlinx.coroutines.NonCancellable
45
import kotlinx.coroutines.flow.MutableStateFlow
56
import kotlinx.coroutines.flow.StateFlow
@@ -136,7 +137,9 @@ class SessionRepositoryImpl(
136137
val applied = userApiClient.applySession(session, deviceInfo)
137138
if (applied && session != null) {
138139
try {
139-
val user by userApiClient.userApi.getCurrentUser()
140+
val user = withContext(Dispatchers.IO) {
141+
userApiClient.userApi.getCurrentUser().content
142+
}
140143
userRepository.updateCurrentUser(user)
141144
} catch (err: ApiClientException) {
142145
Timber.e(err, "Unable to authenticate: bad response when getting user info")

Diff for: app/src/main/java/org/jellyfin/androidtv/data/eventhandling/SocketHandler.kt

+25-24
Original file line numberDiff line numberDiff line change
@@ -55,32 +55,33 @@ class SocketHandler(
5555

5656
suspend fun updateSession() {
5757
try {
58-
api.sessionApi.postCapabilities(
59-
playableMediaTypes = listOf(MediaType.VIDEO, MediaType.AUDIO),
60-
supportsMediaControl = true,
61-
supportedCommands = buildList {
62-
add(GeneralCommandType.DISPLAY_CONTENT)
63-
add(GeneralCommandType.SET_SUBTITLE_STREAM_INDEX)
64-
add(GeneralCommandType.SET_AUDIO_STREAM_INDEX)
65-
66-
add(GeneralCommandType.DISPLAY_MESSAGE)
67-
add(GeneralCommandType.SEND_STRING)
68-
69-
// Note: These are used in the PlaySessionSocketService
70-
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && !audioManager.isVolumeFixed) {
71-
add(GeneralCommandType.VOLUME_UP)
72-
add(GeneralCommandType.VOLUME_DOWN)
73-
add(GeneralCommandType.SET_VOLUME)
74-
75-
add(GeneralCommandType.MUTE)
76-
add(GeneralCommandType.UNMUTE)
77-
add(GeneralCommandType.TOGGLE_MUTE)
78-
}
79-
},
80-
)
58+
withContext(Dispatchers.IO) {
59+
api.sessionApi.postCapabilities(
60+
playableMediaTypes = listOf(MediaType.VIDEO, MediaType.AUDIO),
61+
supportsMediaControl = true,
62+
supportedCommands = buildList {
63+
add(GeneralCommandType.DISPLAY_CONTENT)
64+
add(GeneralCommandType.SET_SUBTITLE_STREAM_INDEX)
65+
add(GeneralCommandType.SET_AUDIO_STREAM_INDEX)
66+
67+
add(GeneralCommandType.DISPLAY_MESSAGE)
68+
add(GeneralCommandType.SEND_STRING)
69+
70+
// Note: These are used in the PlaySessionSocketService
71+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && !audioManager.isVolumeFixed) {
72+
add(GeneralCommandType.VOLUME_UP)
73+
add(GeneralCommandType.VOLUME_DOWN)
74+
add(GeneralCommandType.SET_VOLUME)
75+
76+
add(GeneralCommandType.MUTE)
77+
add(GeneralCommandType.UNMUTE)
78+
add(GeneralCommandType.TOGGLE_MUTE)
79+
}
80+
},
81+
)
82+
}
8183
} catch (err: ApiClientException) {
8284
Timber.e(err, "Unable to update capabilities")
83-
return
8485
}
8586
}
8687

Diff for: app/src/main/java/org/jellyfin/androidtv/data/repository/ItemMutationRepository.kt

+6-4
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package org.jellyfin.androidtv.data.repository
22

3+
import kotlinx.coroutines.Dispatchers
4+
import kotlinx.coroutines.withContext
35
import org.jellyfin.androidtv.data.model.DataRefreshService
46
import org.jellyfin.sdk.api.client.ApiClient
57
import org.jellyfin.sdk.api.client.extensions.playStateApi
@@ -19,8 +21,8 @@ class ItemMutationRepositoryImpl(
1921
) : ItemMutationRepository {
2022
override suspend fun setFavorite(item: UUID, favorite: Boolean): UserItemDataDto {
2123
val response by when {
22-
favorite -> api.userLibraryApi.markFavoriteItem(itemId = item)
23-
else -> api.userLibraryApi.unmarkFavoriteItem(itemId = item)
24+
favorite -> withContext(Dispatchers.IO) { api.userLibraryApi.markFavoriteItem(itemId = item) }
25+
else -> withContext(Dispatchers.IO) { api.userLibraryApi.unmarkFavoriteItem(itemId = item) }
2426
}
2527

2628
dataRefreshService.lastFavoriteUpdate = Instant.now()
@@ -29,8 +31,8 @@ class ItemMutationRepositoryImpl(
2931

3032
override suspend fun setPlayed(item: UUID, played: Boolean): UserItemDataDto {
3133
val response by when {
32-
played -> api.playStateApi.markPlayedItem(itemId = item)
33-
else -> api.playStateApi.markUnplayedItem(itemId = item)
34+
played -> withContext(Dispatchers.IO) { api.playStateApi.markPlayedItem(itemId = item) }
35+
else -> withContext(Dispatchers.IO) { api.playStateApi.markUnplayedItem(itemId = item) }
3436
}
3537

3638
return response

Diff for: app/src/main/java/org/jellyfin/androidtv/data/repository/UserViewsRepository.kt

+3-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
package org.jellyfin.androidtv.data.repository
22

3+
import kotlinx.coroutines.Dispatchers
34
import kotlinx.coroutines.flow.Flow
45
import kotlinx.coroutines.flow.flow
6+
import kotlinx.coroutines.flow.flowOn
57
import org.jellyfin.sdk.api.client.ApiClient
68
import org.jellyfin.sdk.api.client.extensions.userViewsApi
79
import org.jellyfin.sdk.model.api.BaseItemDto
@@ -23,7 +25,7 @@ class UserViewsRepositoryImpl(
2325
val filteredViews = views.items
2426
.filter { isSupported(it.collectionType) }
2527
emit(filteredViews)
26-
}
28+
}.flowOn(Dispatchers.IO)
2729

2830
override fun isSupported(collectionType: CollectionType?) = collectionType !in unsupportedCollectionTypes
2931
override fun allowViewSelection(collectionType: CollectionType?) = collectionType !in disallowViewSelectionCollectionTypes

Diff for: app/src/main/java/org/jellyfin/androidtv/integration/MediaContentProvider.kt

+5-2
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,9 @@ import android.database.Cursor
99
import android.database.MatrixCursor
1010
import android.net.Uri
1111
import android.provider.BaseColumns
12+
import kotlinx.coroutines.Dispatchers
1213
import kotlinx.coroutines.runBlocking
14+
import kotlinx.coroutines.withContext
1315
import org.jellyfin.androidtv.BuildConfig
1416
import org.jellyfin.androidtv.R
1517
import org.jellyfin.androidtv.data.repository.ItemRepository
@@ -61,8 +63,9 @@ class MediaContentProvider : ContentProvider(), KoinComponent {
6163

6264
val limit = uri.getQueryParameter(SearchManager.SUGGEST_PARAMETER_LIMIT)?.toIntOrNull()
6365
?: DEFAULT_LIMIT
64-
return getSuggestions(query, limit)
66+
return runBlocking { getSuggestions(query, limit) }
6567
}
68+
6669
else -> throw IllegalArgumentException("Unknown Uri: $uri")
6770
}
6871
}
@@ -84,7 +87,7 @@ class MediaContentProvider : ContentProvider(), KoinComponent {
8487
null
8588
}
8689

87-
private fun getSuggestions(query: String, limit: Int) = runBlocking {
90+
private suspend fun getSuggestions(query: String, limit: Int) = withContext(Dispatchers.IO) {
8891
val searchResult = searchItems(query, limit)
8992
if (searchResult != null) Timber.d("Query resulted in %d items", searchResult.totalRecordCount)
9093

Diff for: app/src/main/java/org/jellyfin/androidtv/integration/dream/DreamViewModel.kt

+1-1
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ class DreamViewModel(
5959
delay(2.seconds)
6060

6161
while (true) {
62-
val next = getRandomLibraryShowcase()
62+
val next = withContext(Dispatchers.IO) { getRandomLibraryShowcase() }
6363
if (next != null) {
6464
emit(next)
6565
delay(30.seconds)

Diff for: app/src/main/java/org/jellyfin/androidtv/integration/dream/composable/DreamContentLibraryShowcase.kt

+1-1
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,8 @@ import androidx.compose.ui.layout.ContentScale
1414
import androidx.compose.ui.text.TextStyle
1515
import androidx.compose.ui.unit.dp
1616
import androidx.compose.ui.unit.sp
17-
import androidx.tv.material3.Text
1817
import org.jellyfin.androidtv.integration.dream.model.DreamContent
18+
import org.jellyfin.androidtv.ui.base.Text
1919
import org.jellyfin.androidtv.ui.composable.ZoomBox
2020
import org.jellyfin.androidtv.ui.composable.modifier.overscan
2121

Diff for: app/src/main/java/org/jellyfin/androidtv/integration/dream/composable/DreamContentNowPlaying.kt

+1-1
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,8 @@ import androidx.compose.ui.text.TextStyle
2626
import androidx.compose.ui.unit.IntSize
2727
import androidx.compose.ui.unit.dp
2828
import androidx.compose.ui.unit.sp
29-
import androidx.tv.material3.Text
3029
import org.jellyfin.androidtv.integration.dream.model.DreamContent
30+
import org.jellyfin.androidtv.ui.base.Text
3131
import org.jellyfin.androidtv.ui.composable.AsyncImage
3232
import org.jellyfin.androidtv.ui.composable.LyricsDtoBox
3333
import org.jellyfin.androidtv.ui.composable.blurHashPainter

Diff for: app/src/main/java/org/jellyfin/androidtv/integration/dream/composable/DreamHeader.kt

+1-1
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,8 @@ import androidx.compose.ui.res.stringResource
1919
import androidx.compose.ui.text.TextStyle
2020
import androidx.compose.ui.unit.dp
2121
import androidx.compose.ui.unit.sp
22-
import androidx.tv.material3.Text
2322
import org.jellyfin.androidtv.R
23+
import org.jellyfin.androidtv.ui.base.Text
2424
import org.jellyfin.androidtv.ui.composable.modifier.overscan
2525
import org.jellyfin.androidtv.ui.composable.rememberCurrentTime
2626

0 commit comments

Comments
 (0)