Use Wholphin-specific dispatchers (#1583)

## Description
Switches all usages of `kotlinx.coroutines.Dispatchers` to
`WholphinDispatchers`.

`WholphinDispatchers` provides an easier and more reliable mechanism to
use a test dispatcher in unit tests. For regular app usage,
`WholphinDispatchers` just delegates to `Dispatchers`.

There are no user facing changes

### Related issues
None

### Testing
Basic emulator testing & unit tests

## Screenshots
N/A

## AI or LLM usage
None
This commit is contained in:
Damontecres 2026-06-18 11:17:19 -04:00 • committed by GitHub
parent ddbec0da73
commit e17ae3eb8a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
68 changed files with 252 additions and 226 deletions

View file

@ -60,10 +60,10 @@ import com.github.damontecres.wholphin.ui.theme.WholphinTheme
import com.github.damontecres.wholphin.ui.util.ProvideLocalClock import com.github.damontecres.wholphin.ui.util.ProvideLocalClock
import com.github.damontecres.wholphin.util.DebugLogTree import com.github.damontecres.wholphin.util.DebugLogTree
import com.github.damontecres.wholphin.util.ExceptionHandler import com.github.damontecres.wholphin.util.ExceptionHandler
import com.github.damontecres.wholphin.util.WholphinDispatchers
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.first
@ -170,7 +170,7 @@ class MainActivity : AppCompatActivity() {
viewModel.serverRepository.currentUserFlow viewModel.serverRepository.currentUserFlow
.onEach { user -> .onEach { user ->
withContext(Dispatchers.Main) { withContext(WholphinDispatchers.Main) {
if (user?.hasPin == true) { if (user?.hasPin == true) {
window.addFlags(WindowManager.LayoutParams.FLAG_SECURE) window.addFlags(WindowManager.LayoutParams.FLAG_SECURE)
} else { } else {
@ -184,7 +184,7 @@ class MainActivity : AppCompatActivity() {
screensaverService.keepScreenOn screensaverService.keepScreenOn
.onEach { keepScreenOn -> .onEach { keepScreenOn ->
Timber.v("keepScreenOn: %s", keepScreenOn) Timber.v("keepScreenOn: %s", keepScreenOn)
withContext(Dispatchers.Main) { withContext(WholphinDispatchers.Main) {
if (keepScreenOn) { if (keepScreenOn) {
window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON) window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
} else { } else {
@ -383,7 +383,7 @@ class MainActivity : AppCompatActivity() {
} }
fun changeDisplayMode(modeId: Int) { fun changeDisplayMode(modeId: Int) {
lifecycleScope.launch(Dispatchers.Main + ExceptionHandler(autoToast = true)) { lifecycleScope.launch(WholphinDispatchers.Main + ExceptionHandler(autoToast = true)) {
val attrs = window.attributes val attrs = window.attributes
if (attrs.preferredDisplayModeId != modeId) { if (attrs.preferredDisplayModeId != modeId) {
Timber.d("Switch preferredDisplayModeId to %s", modeId) Timber.d("Switch preferredDisplayModeId to %s", modeId)

View file

@ -7,7 +7,7 @@ import com.github.damontecres.wholphin.data.model.PlaybackLanguageChoice
import com.github.damontecres.wholphin.data.model.TrackIndex import com.github.damontecres.wholphin.data.model.TrackIndex
import com.github.damontecres.wholphin.preferences.UserPreferences import com.github.damontecres.wholphin.preferences.UserPreferences
import com.github.damontecres.wholphin.services.StreamChoiceService import com.github.damontecres.wholphin.services.StreamChoiceService
import kotlinx.coroutines.Dispatchers import com.github.damontecres.wholphin.util.WholphinDispatchers
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import org.jellyfin.sdk.model.api.MediaSourceInfo import org.jellyfin.sdk.model.api.MediaSourceInfo
import org.jellyfin.sdk.model.api.MediaStream import org.jellyfin.sdk.model.api.MediaStream
@ -108,7 +108,7 @@ class ItemPlaybackRepository
itemId: UUID, itemId: UUID,
sourceId: UUID, sourceId: UUID,
): ItemPlayback? = ): ItemPlayback? =
withContext(Dispatchers.IO) { withContext(WholphinDispatchers.IO) {
serverRepository.currentUser?.let { user -> serverRepository.currentUser?.let { user ->
val itemPlayback = val itemPlayback =
ItemPlayback( ItemPlayback(

View file

@ -9,9 +9,9 @@ import com.github.damontecres.wholphin.data.model.JellyfinUser
import com.github.damontecres.wholphin.preferences.AppPreferences import com.github.damontecres.wholphin.preferences.AppPreferences
import com.github.damontecres.wholphin.services.hilt.IoDispatcher import com.github.damontecres.wholphin.services.hilt.IoDispatcher
import com.github.damontecres.wholphin.ui.toServerString import com.github.damontecres.wholphin.ui.toServerString
import com.github.damontecres.wholphin.util.WholphinDispatchers
import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
@ -109,7 +109,7 @@ class ServerRepository
}.build() }.build()
} }
val currentUser = CurrentUser(updatedServer, updatedUser) val currentUser = CurrentUser(updatedServer, updatedUser)
withContext(Dispatchers.Main) { withContext(WholphinDispatchers.Main) {
_current.value = currentUser _current.value = currentUser
_currentUserDto.value = userDto _currentUserDto.value = userDto
} }
@ -222,7 +222,7 @@ class ServerRepository
suspend fun removeUser(user: JellyfinUser) { suspend fun removeUser(user: JellyfinUser) {
if (current.value?.user?.id == user.id) { if (current.value?.user?.id == user.id) {
withContext(Dispatchers.Main) { withContext(WholphinDispatchers.Main) {
_current.value = null _current.value = null
} }
userPreferencesDataStore.updateData { userPreferencesDataStore.updateData {
@ -241,7 +241,7 @@ class ServerRepository
suspend fun removeServer(server: JellyfinServer) { suspend fun removeServer(server: JellyfinServer) {
if (current.value?.server?.id == server.id) { if (current.value?.server?.id == server.id) {
withContext(Dispatchers.Main) { withContext(WholphinDispatchers.Main) {
_current.value = null _current.value = null
} }
userPreferencesDataStore.updateData { userPreferencesDataStore.updateData {

View file

@ -18,8 +18,8 @@ import com.github.damontecres.wholphin.data.model.BaseItem
import com.github.damontecres.wholphin.data.model.DiscoverItem import com.github.damontecres.wholphin.data.model.DiscoverItem
import com.github.damontecres.wholphin.preferences.AppPreferences import com.github.damontecres.wholphin.preferences.AppPreferences
import com.github.damontecres.wholphin.preferences.BackdropStyle import com.github.damontecres.wholphin.preferences.BackdropStyle
import com.github.damontecres.wholphin.util.WholphinDispatchers
import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.FlowPreview import kotlinx.coroutines.FlowPreview
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
@ -55,7 +55,7 @@ class BackdropService
* Update the backdrop to use the specified item * Update the backdrop to use the specified item
*/ */
suspend fun submit(item: BaseItem) = suspend fun submit(item: BaseItem) =
withContext(Dispatchers.IO) { withContext(WholphinDispatchers.IO) {
val imageUrl = val imageUrl =
if (item.type == BaseItemKind.GENRE) { if (item.type == BaseItemKind.GENRE) {
item.imageUrlOverride item.imageUrlOverride
@ -76,7 +76,7 @@ class BackdropService
suspend fun submit( suspend fun submit(
itemId: String, itemId: String,
imageUrl: String?, imageUrl: String?,
) = withContext(Dispatchers.IO) { ) = withContext(WholphinDispatchers.IO) {
if (backdropFlow.firstOrNull()?.imageUrl != imageUrl) { if (backdropFlow.firstOrNull()?.imageUrl != imageUrl) {
_backdropFlow.update { _backdropFlow.update {
it.copy( it.copy(
@ -132,7 +132,7 @@ class BackdropService
} }
suspend fun extractColorsFromBackdrop(imageUrl: String?): ExtractedColors = suspend fun extractColorsFromBackdrop(imageUrl: String?): ExtractedColors =
withContext(Dispatchers.IO) { withContext(WholphinDispatchers.IO) {
if (imageUrl.isNullOrBlank()) { if (imageUrl.isNullOrBlank()) {
return@withContext ExtractedColors.DEFAULT return@withContext ExtractedColors.DEFAULT
} }

View file

@ -9,12 +9,12 @@ import com.github.damontecres.wholphin.preferences.AppPreference
import com.github.damontecres.wholphin.services.hilt.IoCoroutineScope import com.github.damontecres.wholphin.services.hilt.IoCoroutineScope
import com.github.damontecres.wholphin.ui.collectLatestIn import com.github.damontecres.wholphin.ui.collectLatestIn
import com.github.damontecres.wholphin.util.GetEpisodesRequestHandler import com.github.damontecres.wholphin.util.GetEpisodesRequestHandler
import com.github.damontecres.wholphin.util.WholphinDispatchers
import com.google.common.cache.CacheBuilder import com.google.common.cache.CacheBuilder
import dagger.hilt.android.qualifiers.ActivityContext import dagger.hilt.android.qualifiers.ActivityContext
import dagger.hilt.android.scopes.ActivityScoped import dagger.hilt.android.scopes.ActivityScoped
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Deferred import kotlinx.coroutines.Deferred
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async import kotlinx.coroutines.async
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import org.jellyfin.sdk.api.client.ApiClient import org.jellyfin.sdk.api.client.ApiClient
@ -56,7 +56,7 @@ class DatePlayedService
adjacentTo = item.id, adjacentTo = item.id,
limit = 1, limit = 1,
) )
return scope.async(Dispatchers.IO) { return scope.async(WholphinDispatchers.IO) {
val premiereDate = item.data.premiereDate val premiereDate = item.data.premiereDate
try { try {
val result = val result =
@ -92,7 +92,7 @@ class DatePlayedService
* the previous episode's last played timestamp, and the episode's premiere date * the previous episode's last played timestamp, and the episode's premiere date
*/ */
suspend fun getLastPlayed(item: BaseItem): LocalDateTime? = suspend fun getLastPlayed(item: BaseItem): LocalDateTime? =
withContext(Dispatchers.IO) { withContext(WholphinDispatchers.IO) {
val seriesId = item.data.seriesId val seriesId = item.data.seriesId
return@withContext if (seriesId != null) { return@withContext if (seriesId != null) {
datePlayedCache datePlayedCache

View file

@ -3,10 +3,10 @@ package com.github.damontecres.wholphin.services
import android.content.Context import android.content.Context
import com.github.damontecres.wholphin.preferences.AssPlaybackMode import com.github.damontecres.wholphin.preferences.AssPlaybackMode
import com.github.damontecres.wholphin.preferences.PlaybackPreferences import com.github.damontecres.wholphin.preferences.PlaybackPreferences
import com.github.damontecres.wholphin.util.WholphinDispatchers
import com.github.damontecres.wholphin.util.profile.MediaCodecCapabilitiesTest import com.github.damontecres.wholphin.util.profile.MediaCodecCapabilitiesTest
import com.github.damontecres.wholphin.util.profile.createDeviceProfile import com.github.damontecres.wholphin.util.profile.createDeviceProfile
import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock import kotlinx.coroutines.sync.withLock
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
@ -37,7 +37,7 @@ class DeviceProfileService
prefs: PlaybackPreferences, prefs: PlaybackPreferences,
serverVersion: ServerVersion?, serverVersion: ServerVersion?,
): DeviceProfile = ): DeviceProfile =
withContext(Dispatchers.Default) { withContext(WholphinDispatchers.Default) {
mutex.withLock { mutex.withLock {
val newConfig = val newConfig =
DeviceProfileConfiguration( DeviceProfileConfiguration(

View file

@ -8,8 +8,8 @@ package com.github.damontecres.wholphin.services
import com.github.damontecres.wholphin.data.model.BaseItem import com.github.damontecres.wholphin.data.model.BaseItem
import com.github.damontecres.wholphin.ui.SlimItemFields import com.github.damontecres.wholphin.ui.SlimItemFields
import com.github.damontecres.wholphin.util.LocalDateTimeSerializer import com.github.damontecres.wholphin.util.LocalDateTimeSerializer
import com.github.damontecres.wholphin.util.WholphinDispatchers
import com.github.damontecres.wholphin.util.supportItemKinds import com.github.damontecres.wholphin.util.supportItemKinds
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.sync.Semaphore import kotlinx.coroutines.sync.Semaphore
@ -143,14 +143,14 @@ class LatestNextUpService
resume: List<BaseItem>, resume: List<BaseItem>,
nextUp: List<BaseItem>, nextUp: List<BaseItem>,
): List<BaseItem> = ): List<BaseItem> =
withContext(Dispatchers.IO) { withContext(WholphinDispatchers.IO) {
val start = System.currentTimeMillis() val start = System.currentTimeMillis()
val semaphore = Semaphore(3) val semaphore = Semaphore(3)
val deferred = val deferred =
nextUp nextUp
.filter { it.data.seriesId != null } .filter { it.data.seriesId != null }
.map { item -> .map { item ->
async(Dispatchers.IO) { async(WholphinDispatchers.IO) {
try { try {
semaphore.withPermit { semaphore.withPermit {
datePlayedService.getLastPlayed(item) datePlayedService.getLastPlayed(item)

View file

@ -16,12 +16,12 @@ import androidx.work.workDataOf
import com.github.damontecres.wholphin.data.ServerRepository import com.github.damontecres.wholphin.data.ServerRepository
import com.github.damontecres.wholphin.ui.collectLatestIn import com.github.damontecres.wholphin.ui.collectLatestIn
import com.github.damontecres.wholphin.util.ExceptionHandler import com.github.damontecres.wholphin.util.ExceptionHandler
import com.github.damontecres.wholphin.util.WholphinDispatchers
import dagger.assisted.Assisted import dagger.assisted.Assisted
import dagger.assisted.AssistedInject import dagger.assisted.AssistedInject
import dagger.hilt.android.qualifiers.ActivityContext import dagger.hilt.android.qualifiers.ActivityContext
import dagger.hilt.android.scopes.ActivityScoped import dagger.hilt.android.scopes.ActivityScoped
import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import org.jellyfin.sdk.api.client.ApiClient import org.jellyfin.sdk.api.client.ApiClient
import org.jellyfin.sdk.model.serializer.toUUIDOrNull import org.jellyfin.sdk.model.serializer.toUUIDOrNull
@ -92,7 +92,7 @@ class LatestNextUpSchedulerService
) )
// Exposed for testing // Exposed for testing
internal var dispatcher: CoroutineDispatcher = Dispatchers.IO internal var dispatcher: CoroutineDispatcher = WholphinDispatchers.IO
init { init {
serverRepository.current.collectLatestIn(activity.lifecycleScope) { user -> serverRepository.current.collectLatestIn(activity.lifecycleScope) { user ->

View file

@ -24,10 +24,10 @@ import com.github.damontecres.wholphin.util.BlockingList
import com.github.damontecres.wholphin.util.LoadingState import com.github.damontecres.wholphin.util.LoadingState
import com.github.damontecres.wholphin.util.PlaybackItemState import com.github.damontecres.wholphin.util.PlaybackItemState
import com.github.damontecres.wholphin.util.TrackActivityPlaybackListener import com.github.damontecres.wholphin.util.TrackActivityPlaybackListener
import com.github.damontecres.wholphin.util.WholphinDispatchers
import com.github.damontecres.wholphin.util.profile.supportedAudioCodecs import com.github.damontecres.wholphin.util.profile.supportedAudioCodecs
import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
@ -177,9 +177,9 @@ class MusicService
items: BlockingList<BaseItem?>, items: BlockingList<BaseItem?>,
startIndex: Int, startIndex: Int,
shuffled: Boolean, shuffled: Boolean,
) = withContext(Dispatchers.IO) { ) = withContext(WholphinDispatchers.IO) {
Timber.d("setQueue: %s items, startIndex=%s, shuffled=%s", items.size, startIndex, shuffled) Timber.d("setQueue: %s items, startIndex=%s, shuffled=%s", items.size, startIndex, shuffled)
withContext(Dispatchers.Main) { withContext(WholphinDispatchers.Main) {
player.setMediaItems(emptyList()) player.setMediaItems(emptyList())
player.shuffleModeEnabled = shuffled player.shuffleModeEnabled = shuffled
} }
@ -199,7 +199,7 @@ class MusicService
items items
.filter { it.type == BaseItemKind.AUDIO } .filter { it.type == BaseItemKind.AUDIO }
.map(::convert) .map(::convert)
withContext(Dispatchers.Main) { withContext(WholphinDispatchers.Main) {
player.setMediaItems(mediaItems) player.setMediaItems(mediaItems)
player.shuffleModeEnabled = shuffled player.shuffleModeEnabled = shuffled
updateQueueSize() updateQueueSize()
@ -216,7 +216,7 @@ class MusicService
) { ) {
if (item.type == BaseItemKind.AUDIO) { if (item.type == BaseItemKind.AUDIO) {
val mediaItem = convert(item) val mediaItem = convert(item)
withContext(Dispatchers.Main) { withContext(WholphinDispatchers.Main) {
if (index != null) { if (index != null) {
player.addMediaItem(index, mediaItem) player.addMediaItem(index, mediaItem)
} else { } else {
@ -293,7 +293,7 @@ class MusicService
*/ */
private suspend fun updateQueueSize() { private suspend fun updateQueueSize() {
// val ids = // val ids =
// withContext(Dispatchers.Default) { // withContext(WholphinDispatchers.Default) {
// (0..<player.mediaItemCount).map { player.getMediaItemAt(it).mediaId.toUUID() } // (0..<player.mediaItemCount).map { player.getMediaItemAt(it).mediaId.toUUID() }
// } // }
val timeline = onMain { player.currentTimeline } val timeline = onMain { player.currentTimeline }
@ -304,7 +304,7 @@ class MusicService
timeline.getWindow(it, window) timeline.getWindow(it, window)
window.mediaItem.mediaId.toUUID() window.mediaItem.mediaId.toUUID()
}.toSet() }.toSet()
withContext(Dispatchers.Main) { withContext(WholphinDispatchers.Main) {
_state.update { _state.update {
it.copy( it.copy(
queueVersion = it.queueVersion + 1, queueVersion = it.queueVersion + 1,
@ -321,7 +321,7 @@ class MusicService
suspend fun moveQueue( suspend fun moveQueue(
index: Int, index: Int,
direction: MoveDirection, direction: MoveDirection,
) = withContext(Dispatchers.Main) { ) = withContext(WholphinDispatchers.Main) {
player.moveMediaItem(index, if (direction == MoveDirection.UP) index - 1 else index + 1) player.moveMediaItem(index, if (direction == MoveDirection.UP) index - 1 else index + 1)
updateQueueSize() updateQueueSize()
} }
@ -332,7 +332,7 @@ class MusicService
suspend fun moveQueue( suspend fun moveQueue(
index: Int, index: Int,
newIndex: Int, newIndex: Int,
) = withContext(Dispatchers.Main) { ) = withContext(WholphinDispatchers.Main) {
player.moveMediaItem(index, newIndex) player.moveMediaItem(index, newIndex)
updateQueueSize() updateQueueSize()
} }
@ -385,7 +385,7 @@ class MusicService
.subscribe<PlaystateMessage>() .subscribe<PlaystateMessage>()
.onEach { message -> .onEach { message ->
message.data?.let { message.data?.let {
withContext(Dispatchers.Main) { withContext(WholphinDispatchers.Main) {
when (it.command) { when (it.command) {
PlaystateCommand.STOP -> { PlaystateCommand.STOP -> {
stop() stop()

View file

@ -13,11 +13,11 @@ import com.github.damontecres.wholphin.ui.nav.Destination
import com.github.damontecres.wholphin.ui.nav.NavDrawerItem import com.github.damontecres.wholphin.ui.nav.NavDrawerItem
import com.github.damontecres.wholphin.ui.nav.ServerNavDrawerItem import com.github.damontecres.wholphin.ui.nav.ServerNavDrawerItem
import com.github.damontecres.wholphin.ui.showToast import com.github.damontecres.wholphin.ui.showToast
import com.github.damontecres.wholphin.util.WholphinDispatchers
import com.github.damontecres.wholphin.util.supportedCollectionTypes import com.github.damontecres.wholphin.util.supportedCollectionTypes
import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.CancellationException import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
@ -209,7 +209,7 @@ class NavDrawerService
val allItems = builtins + libraries val allItems = builtins + libraries
val navDrawerPins = val navDrawerPins =
withContext(Dispatchers.IO) { withContext(WholphinDispatchers.IO) {
serverPreferencesDao.getNavDrawerPinnedItems(user).associateBy { it.itemId } serverPreferencesDao.getNavDrawerPinnedItems(user).associateBy { it.itemId }
} }

View file

@ -29,6 +29,7 @@ import com.github.damontecres.wholphin.preferences.MediaExtensionStatus
import com.github.damontecres.wholphin.preferences.PlaybackPreferences import com.github.damontecres.wholphin.preferences.PlaybackPreferences
import com.github.damontecres.wholphin.preferences.PlayerBackend import com.github.damontecres.wholphin.preferences.PlayerBackend
import com.github.damontecres.wholphin.services.hilt.AuthOkHttpClient import com.github.damontecres.wholphin.services.hilt.AuthOkHttpClient
import com.github.damontecres.wholphin.util.WholphinDispatchers
import com.github.damontecres.wholphin.util.mpv.MpvPlayer import com.github.damontecres.wholphin.util.mpv.MpvPlayer
import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.android.qualifiers.ApplicationContext
import io.github.peerless2012.ass.media.AssHandler import io.github.peerless2012.ass.media.AssHandler
@ -36,7 +37,6 @@ import io.github.peerless2012.ass.media.factory.AssRenderersFactory
import io.github.peerless2012.ass.media.kt.withAssMkvSupport import io.github.peerless2012.ass.media.kt.withAssMkvSupport
import io.github.peerless2012.ass.media.parser.AssSubtitleParserFactory import io.github.peerless2012.ass.media.parser.AssSubtitleParserFactory
import io.github.peerless2012.ass.media.type.AssRenderType import io.github.peerless2012.ass.media.type.AssRenderType
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
import timber.log.Timber import timber.log.Timber
@ -62,7 +62,7 @@ class PlayerFactory
backend: PlayerBackend, backend: PlayerBackend,
prefs: PlaybackPreferences, prefs: PlaybackPreferences,
): PlayerCreation { ): PlayerCreation {
withContext(Dispatchers.Main) { withContext(WholphinDispatchers.Main) {
if (currentPlayer?.isReleased == false) { if (currentPlayer?.isReleased == false) {
Timber.w("Player was not released before trying to create a new one!") Timber.w("Player was not released before trying to create a new one!")
currentPlayer?.release() currentPlayer?.release()
@ -138,7 +138,7 @@ class PlayerFactory
.build() .build()
.apply { .apply {
assHandler?.init(this) assHandler?.init(this)
withContext(Dispatchers.Main) { withContext(WholphinDispatchers.Main) {
setAudioAttributes( setAudioAttributes(
AudioAttributes AudioAttributes
.Builder() .Builder()

View file

@ -8,9 +8,9 @@ import android.os.Looper
import android.view.Display import android.view.Display
import com.github.damontecres.wholphin.MainActivity import com.github.damontecres.wholphin.MainActivity
import com.github.damontecres.wholphin.ui.showToast import com.github.damontecres.wholphin.ui.showToast
import com.github.damontecres.wholphin.util.WholphinDispatchers
import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.CompletableDeferred import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import kotlinx.coroutines.withTimeoutOrNull import kotlinx.coroutines.withTimeoutOrNull
@ -38,7 +38,7 @@ class RefreshRateService
stream: MediaStream, stream: MediaStream,
switchRefreshRate: Boolean, switchRefreshRate: Boolean,
switchResolution: Boolean, switchResolution: Boolean,
) = withContext(Dispatchers.IO) { ) = withContext(WholphinDispatchers.IO) {
if (!switchRefreshRate && !switchResolution) { if (!switchRefreshRate && !switchResolution) {
Timber.v("Not switching either refresh rate nor resolution") Timber.v("Not switching either refresh rate nor resolution")
return@withContext return@withContext

View file

@ -3,7 +3,7 @@ package com.github.damontecres.wholphin.services
import com.github.damontecres.wholphin.data.RememberedTabDao import com.github.damontecres.wholphin.data.RememberedTabDao
import com.github.damontecres.wholphin.data.ServerRepository import com.github.damontecres.wholphin.data.ServerRepository
import com.github.damontecres.wholphin.data.model.RememberedTab import com.github.damontecres.wholphin.data.model.RememberedTab
import kotlinx.coroutines.Dispatchers import com.github.damontecres.wholphin.util.WholphinDispatchers
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import javax.inject.Inject import javax.inject.Inject
import javax.inject.Singleton import javax.inject.Singleton
@ -16,7 +16,7 @@ class RememberedTabService
private val rememberedTabDao: RememberedTabDao, private val rememberedTabDao: RememberedTabDao,
) { ) {
suspend fun getRememberedTab(itemId: String): Int? = suspend fun getRememberedTab(itemId: String): Int? =
withContext(Dispatchers.IO) { withContext(WholphinDispatchers.IO) {
serverRepository.currentUser?.rowId?.let { userId -> serverRepository.currentUser?.rowId?.let { userId ->
rememberedTabDao.getRememberedTab(userId, itemId)?.index rememberedTabDao.getRememberedTab(userId, itemId)?.index
} }
@ -26,7 +26,7 @@ class RememberedTabService
itemId: String, itemId: String,
tabIndex: Int, tabIndex: Int,
): Unit = ): Unit =
withContext(Dispatchers.IO) { withContext(WholphinDispatchers.IO) {
serverRepository.currentUser?.rowId?.let { userId -> serverRepository.currentUser?.rowId?.let { userId ->
rememberedTabDao.save(RememberedTab(userId, itemId, tabIndex)) rememberedTabDao.save(RememberedTab(userId, itemId, tabIndex))
} }

View file

@ -10,10 +10,10 @@ import com.github.damontecres.wholphin.ui.launchDefault
import com.github.damontecres.wholphin.util.ApiRequestPager import com.github.damontecres.wholphin.util.ApiRequestPager
import com.github.damontecres.wholphin.util.ExceptionHandler import com.github.damontecres.wholphin.util.ExceptionHandler
import com.github.damontecres.wholphin.util.GetItemsRequestHandler import com.github.damontecres.wholphin.util.GetItemsRequestHandler
import com.github.damontecres.wholphin.util.WholphinDispatchers
import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.CancellationException import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
@ -224,7 +224,7 @@ class ScreensaverService
if (index > pager.lastIndex) index = 0 if (index > pager.lastIndex) index = 0
} }
} }
}.flowOn(Dispatchers.Default).cancellable() }.flowOn(WholphinDispatchers.Default).cancellable()
private suspend fun createPager(): ApiRequestPager<GetItemsRequest> { private suspend fun createPager(): ApiRequestPager<GetItemsRequest> {
val prefs = val prefs =

View file

@ -4,11 +4,11 @@ package com.github.damontecres.wholphin.services
import android.content.Context import android.content.Context
import com.github.damontecres.wholphin.ui.toServerString import com.github.damontecres.wholphin.ui.toServerString
import com.github.damontecres.wholphin.util.WholphinDispatchers
import com.mayakapps.kache.InMemoryKache import com.mayakapps.kache.InMemoryKache
import com.mayakapps.kache.ObjectKache import com.mayakapps.kache.ObjectKache
import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock import kotlinx.coroutines.sync.withLock
import kotlinx.serialization.ExperimentalSerializationApi import kotlinx.serialization.ExperimentalSerializationApi
@ -43,7 +43,7 @@ class SuggestionsCache
private val memoryCache = private val memoryCache =
InMemoryKache<String, CachedSuggestions>(maxSize = 8) { InMemoryKache<String, CachedSuggestions>(maxSize = 8) {
creationScope = CoroutineScope(Dispatchers.IO) creationScope = CoroutineScope(WholphinDispatchers.IO)
} }
private fun cacheKey( private fun cacheKey(

View file

@ -9,9 +9,9 @@ import com.github.damontecres.wholphin.data.ServerRepository
import com.github.damontecres.wholphin.preferences.AppPreference import com.github.damontecres.wholphin.preferences.AppPreference
import com.github.damontecres.wholphin.preferences.AppPreferences import com.github.damontecres.wholphin.preferences.AppPreferences
import com.github.damontecres.wholphin.util.GetItemsRequestHandler import com.github.damontecres.wholphin.util.GetItemsRequestHandler
import com.github.damontecres.wholphin.util.WholphinDispatchers
import dagger.assisted.Assisted import dagger.assisted.Assisted
import dagger.assisted.AssistedInject import dagger.assisted.AssistedInject
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.coroutineScope
@ -81,7 +81,7 @@ class SuggestionsWorker
.takeIf { it > 0 } .takeIf { it > 0 }
?: AppPreference.HomePageItems.defaultValue.toInt() ?: AppPreference.HomePageItems.defaultValue.toInt()
val context = Dispatchers.IO.limitedParallelism(2, "fetchSuggestions") val context = WholphinDispatchers.IO.limitedParallelism(2, "fetchSuggestions")
if (requestedParentId != null && requestedItemKind != null) { if (requestedParentId != null && requestedItemKind != null) {
Timber.d( Timber.d(
"Fetching on-demand suggestions for parent=%s kind=%s", "Fetching on-demand suggestions for parent=%s kind=%s",
@ -128,7 +128,7 @@ class SuggestionsWorker
supervisorScope { supervisorScope {
supportedViews supportedViews
.map { view -> .map { view ->
async(Dispatchers.IO) { async(WholphinDispatchers.IO) {
runCatching { runCatching {
Timber.v("Fetching suggestions for parent=%s kind=%s", view.id, view.itemKind) Timber.v("Fetching suggestions for parent=%s kind=%s", view.id, view.itemKind)
fetchAndCacheSuggestions( fetchAndCacheSuggestions(
@ -293,7 +293,7 @@ class SuggestionsWorker
limit = freshLimit, limit = freshLimit,
) )
} }
withContext(Dispatchers.Default) { withContext(WholphinDispatchers.Default) {
val contextual = contextualDeferred.await() val contextual = contextualDeferred.await()
val random = randomDeferred.await() val random = randomDeferred.await()
val fresh = freshDeferred.await() val fresh = freshDeferred.await()

View file

@ -14,10 +14,10 @@ import com.github.damontecres.wholphin.preferences.ThemeSongVolume
import com.github.damontecres.wholphin.services.hilt.AuthOkHttpClient import com.github.damontecres.wholphin.services.hilt.AuthOkHttpClient
import com.github.damontecres.wholphin.services.hilt.IoCoroutineScope import com.github.damontecres.wholphin.services.hilt.IoCoroutineScope
import com.github.damontecres.wholphin.ui.launchIO import com.github.damontecres.wholphin.ui.launchIO
import com.github.damontecres.wholphin.util.WholphinDispatchers
import com.github.damontecres.wholphin.util.profile.Codec import com.github.damontecres.wholphin.util.profile.Codec
import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
import kotlinx.coroutines.cancelAndJoin import kotlinx.coroutines.cancelAndJoin
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
@ -102,7 +102,7 @@ class ThemeSongPlayer
Codec.Audio.FLAC, Codec.Audio.FLAC,
), ),
) )
withContext(Dispatchers.Main) { withContext(WholphinDispatchers.Main) {
player.apply { player.apply {
stop() stop()
volume = volumeLevel volume = volumeLevel
@ -127,7 +127,7 @@ class ThemeSongPlayer
scope.launch { scope.launch {
mutex.withLock { mutex.withLock {
state.value.job?.cancelAndJoin() state.value.job?.cancelAndJoin()
withContext(Dispatchers.Main) { withContext(WholphinDispatchers.Main) {
Timber.v("Stopping theme song") Timber.v("Stopping theme song")
player.stop() player.stop()
} }

View file

@ -21,8 +21,8 @@ import com.github.damontecres.wholphin.services.hilt.StandardOkHttpClient
import com.github.damontecres.wholphin.ui.isNotNullOrBlank import com.github.damontecres.wholphin.ui.isNotNullOrBlank
import com.github.damontecres.wholphin.ui.showToast import com.github.damontecres.wholphin.ui.showToast
import com.github.damontecres.wholphin.util.Version import com.github.damontecres.wholphin.util.Version
import com.github.damontecres.wholphin.util.WholphinDispatchers
import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
@ -131,7 +131,7 @@ class UpdateChecker
suspend fun getRelease(version: Version): Release? { suspend fun getRelease(version: Version): Release? {
val url = val url =
"https://api.github.com/repos/damontecres/Wholphin/releases/tags/v${version.major}.${version.minor}.${version.patch}" "https://api.github.com/repos/damontecres/Wholphin/releases/tags/v${version.major}.${version.minor}.${version.patch}"
return withContext(Dispatchers.IO) { return withContext(WholphinDispatchers.IO) {
val request = val request =
Request Request
.Builder() .Builder()
@ -146,7 +146,7 @@ class UpdateChecker
* Get the latest released version * Get the latest released version
*/ */
suspend fun getLatestRelease(updateUrl: String): Release? = suspend fun getLatestRelease(updateUrl: String): Release? =
withContext(Dispatchers.IO) { withContext(WholphinDispatchers.IO) {
val request = val request =
Request Request
.Builder() .Builder()
@ -199,7 +199,7 @@ class UpdateChecker
release: Release, release: Release,
callback: DownloadCallback, callback: DownloadCallback,
) { ) {
withContext(Dispatchers.IO) { withContext(WholphinDispatchers.IO) {
cleanup() cleanup()
val request = val request =
Request Request
@ -210,7 +210,7 @@ class UpdateChecker
okHttpClient.newCall(request).execute().use { okHttpClient.newCall(request).execute().use {
if (it.isSuccessful && it.body != null) { if (it.isSuccessful && it.body != null) {
Timber.v("Request successful for ${release.downloadUrl}") Timber.v("Request successful for ${release.downloadUrl}")
withContext(Dispatchers.Main) { withContext(WholphinDispatchers.Main) {
callback.contentLength(it.body.contentLength()) callback.contentLength(it.body.contentLength())
} }
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
@ -396,7 +396,7 @@ suspend fun copyTo(
while (bytes >= 0) { while (bytes >= 0) {
out.write(buffer, 0, bytes) out.write(buffer, 0, bytes)
bytesCopied += bytes bytesCopied += bytes
withContext(Dispatchers.Main) { withContext(WholphinDispatchers.Main) {
callback.bytesDownloaded(bytesCopied) callback.bytesDownloaded(bytesCopied)
} }
bytes = input.read(buffer) bytes = input.read(buffer)

View file

@ -12,9 +12,9 @@ import com.github.damontecres.wholphin.data.model.JellyfinUser
import com.github.damontecres.wholphin.data.model.SeerrAuthMethod import com.github.damontecres.wholphin.data.model.SeerrAuthMethod
import com.github.damontecres.wholphin.ui.launchDefault import com.github.damontecres.wholphin.ui.launchDefault
import com.github.damontecres.wholphin.ui.launchIO import com.github.damontecres.wholphin.ui.launchIO
import com.github.damontecres.wholphin.util.WholphinDispatchers
import dagger.hilt.android.qualifiers.ActivityContext import dagger.hilt.android.qualifiers.ActivityContext
import dagger.hilt.android.scopes.ActivityScoped import dagger.hilt.android.scopes.ActivityScoped
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.update import kotlinx.coroutines.flow.update
import kotlinx.coroutines.supervisorScope import kotlinx.coroutines.supervisorScope
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
@ -56,7 +56,7 @@ class UserSwitchListener
user.uiLanguage?.let { LocaleListCompat.forLanguageTags(it) } user.uiLanguage?.let { LocaleListCompat.forLanguageTags(it) }
?: LocaleListCompat.getEmptyLocaleList() ?: LocaleListCompat.getEmptyLocaleList()
Timber.i("Switching locale to %s", localeList) Timber.i("Switching locale to %s", localeList)
withContext(Dispatchers.Main) { withContext(WholphinDispatchers.Main) {
AppCompatDelegate.setApplicationLocales(localeList) AppCompatDelegate.setApplicationLocales(localeList)
} }

View file

@ -7,6 +7,7 @@ import com.github.damontecres.wholphin.R
import com.github.damontecres.wholphin.data.ServerRepository import com.github.damontecres.wholphin.data.ServerRepository
import com.github.damontecres.wholphin.services.SeerrApi import com.github.damontecres.wholphin.services.SeerrApi
import com.github.damontecres.wholphin.util.CoroutineContextApiClientFactory import com.github.damontecres.wholphin.util.CoroutineContextApiClientFactory
import com.github.damontecres.wholphin.util.WholphinDispatchers
import dagger.Module import dagger.Module
import dagger.Provides import dagger.Provides
import dagger.hilt.InstallIn import dagger.hilt.InstallIn
@ -14,7 +15,6 @@ import dagger.hilt.android.qualifiers.ApplicationContext
import dagger.hilt.components.SingletonComponent import dagger.hilt.components.SingletonComponent
import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.SupervisorJob
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
import org.jellyfin.sdk.Jellyfin import org.jellyfin.sdk.Jellyfin
@ -42,21 +42,21 @@ annotation class AuthOkHttpClient
annotation class StandardOkHttpClient annotation class StandardOkHttpClient
/** /**
* A [CoroutineScope] with [Dispatchers.IO] * A [CoroutineScope] with [WholphinDispatchers.IO]
*/ */
@Qualifier @Qualifier
@Retention(AnnotationRetention.BINARY) @Retention(AnnotationRetention.BINARY)
annotation class IoCoroutineScope annotation class IoCoroutineScope
/** /**
* A [CoroutineScope] with [Dispatchers.Default] * A [CoroutineScope] with [WholphinDispatchers.Default]
*/ */
@Qualifier @Qualifier
@Retention(AnnotationRetention.BINARY) @Retention(AnnotationRetention.BINARY)
annotation class DefaultCoroutineScope annotation class DefaultCoroutineScope
/** /**
* [Dispatchers.IO] * [WholphinDispatchers.IO]
* *
* @see IoCoroutineScope * @see IoCoroutineScope
*/ */
@ -65,7 +65,7 @@ annotation class DefaultCoroutineScope
annotation class IoDispatcher annotation class IoDispatcher
/** /**
* [Dispatchers.Default] * [WholphinDispatchers.Default]
* *
* @see DefaultCoroutineScope * @see DefaultCoroutineScope
*/ */
@ -156,7 +156,7 @@ object AppModule {
@Provides @Provides
@Singleton @Singleton
@IoDispatcher @IoDispatcher
fun ioDispatcher(): CoroutineDispatcher = Dispatchers.IO fun ioDispatcher(): CoroutineDispatcher = WholphinDispatchers.IO
@Provides @Provides
@Singleton @Singleton
@ -168,7 +168,7 @@ object AppModule {
@Provides @Provides
@Singleton @Singleton
@DefaultDispatcher @DefaultDispatcher
fun defaultDispatcher(): CoroutineDispatcher = Dispatchers.Default fun defaultDispatcher(): CoroutineDispatcher = WholphinDispatchers.Default
@Provides @Provides
@Singleton @Singleton

View file

@ -44,7 +44,7 @@ object DatabaseModule {
).addMigrations(Migrations.Migrate2to3) ).addMigrations(Migrations.Migrate2to3)
// .setQueryCallback({ sqlQuery, args -> // .setQueryCallback({ sqlQuery, args ->
// Timber.v("sqlQuery=$sqlQuery, args=$args") // Timber.v("sqlQuery=$sqlQuery, args=$args")
// }, Dispatchers.IO.asExecutor()) // }, WholphinDispatchers.IO.asExecutor())
.build() .build()
@Provides @Provides

View file

@ -33,9 +33,9 @@ import com.github.damontecres.wholphin.data.model.BaseItem
import com.github.damontecres.wholphin.ui.data.RowColumn import com.github.damontecres.wholphin.ui.data.RowColumn
import com.github.damontecres.wholphin.ui.data.RowColumnSaver import com.github.damontecres.wholphin.ui.data.RowColumnSaver
import com.github.damontecres.wholphin.util.ExceptionHandler import com.github.damontecres.wholphin.util.ExceptionHandler
import com.github.damontecres.wholphin.util.WholphinDispatchers
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.CoroutineStart import kotlinx.coroutines.CoroutineStart
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.collectLatest
@ -359,34 +359,34 @@ suspend fun showToast(
context: Context, context: Context,
text: CharSequence, text: CharSequence,
duration: Int, duration: Int,
) = withContext(Dispatchers.Main) { ) = withContext(WholphinDispatchers.Main) {
Toast.makeText(context, text, duration).show() Toast.makeText(context, text, duration).show()
} }
suspend fun showToast( suspend fun showToast(
context: Context, context: Context,
text: CharSequence, text: CharSequence,
) = withContext(Dispatchers.Main) { ) = withContext(WholphinDispatchers.Main) {
Toast.makeText(context, text, Toast.LENGTH_LONG).show() Toast.makeText(context, text, Toast.LENGTH_LONG).show()
} }
/** /**
* Launches a coroutine with [Dispatchers.IO] plus the provided [CoroutineContext] defaulting to using [ExceptionHandler] * Launches a coroutine with [WholphinDispatchers.IO] plus the provided [CoroutineContext] defaulting to using [ExceptionHandler]
*/ */
fun CoroutineScope.launchIO( fun CoroutineScope.launchIO(
context: CoroutineContext = ExceptionHandler(), context: CoroutineContext = ExceptionHandler(),
start: CoroutineStart = CoroutineStart.DEFAULT, start: CoroutineStart = CoroutineStart.DEFAULT,
block: suspend CoroutineScope.() -> Unit, block: suspend CoroutineScope.() -> Unit,
): Job = launch(context = Dispatchers.IO + context, start = start, block = block) ): Job = launch(context = WholphinDispatchers.IO + context, start = start, block = block)
/** /**
* Launches a coroutine with [Dispatchers.Default] plus the provided [CoroutineContext] defaulting to using [ExceptionHandler] * Launches a coroutine with [WholphinDispatchers.Default] plus the provided [CoroutineContext] defaulting to using [ExceptionHandler]
*/ */
fun CoroutineScope.launchDefault( fun CoroutineScope.launchDefault(
context: CoroutineContext = ExceptionHandler(), context: CoroutineContext = ExceptionHandler(),
start: CoroutineStart = CoroutineStart.DEFAULT, start: CoroutineStart = CoroutineStart.DEFAULT,
block: suspend CoroutineScope.() -> Unit, block: suspend CoroutineScope.() -> Unit,
): Job = launch(context = Dispatchers.Default + context, start = start, block = block) ): Job = launch(context = WholphinDispatchers.Default + context, start = start, block = block)
/** /**
* Converts a UUID to the format used server-side (ie without hyphens). * Converts a UUID to the format used server-side (ie without hyphens).
@ -410,7 +410,7 @@ fun logTab(
ACRA.errorReporter.putCustomData("tabInfo", info) ACRA.errorReporter.putCustomData("tabInfo", info)
} }
suspend fun <T> onMain(block: suspend CoroutineScope.() -> T) = withContext(Dispatchers.Main, block) suspend fun <T> onMain(block: suspend CoroutineScope.() -> T) = withContext(WholphinDispatchers.Main, block)
fun Modifier.dimAndBlur(enabled: Boolean) = fun Modifier.dimAndBlur(enabled: Boolean) =
this.ifElse( this.ifElse(

View file

@ -30,7 +30,6 @@ import coil3.request.crossfade
import com.github.damontecres.wholphin.ui.AspectRatios import com.github.damontecres.wholphin.ui.AspectRatios
import com.github.damontecres.wholphin.ui.PreviewTvSpec import com.github.damontecres.wholphin.ui.PreviewTvSpec
import com.github.damontecres.wholphin.ui.components.Genre import com.github.damontecres.wholphin.ui.components.Genre
import com.github.damontecres.wholphin.ui.isNotNullOrBlank
import com.github.damontecres.wholphin.ui.setup.rememberIdColor import com.github.damontecres.wholphin.ui.setup.rememberIdColor
import com.github.damontecres.wholphin.ui.theme.WholphinTheme import com.github.damontecres.wholphin.ui.theme.WholphinTheme
import java.util.UUID import java.util.UUID

View file

@ -32,9 +32,7 @@ import coil3.request.ImageRequest
import coil3.request.crossfade import coil3.request.crossfade
import com.github.damontecres.wholphin.ui.AspectRatios import com.github.damontecres.wholphin.ui.AspectRatios
import com.github.damontecres.wholphin.ui.PreviewTvSpec import com.github.damontecres.wholphin.ui.PreviewTvSpec
import com.github.damontecres.wholphin.ui.components.Genre
import com.github.damontecres.wholphin.ui.components.Studio import com.github.damontecres.wholphin.ui.components.Studio
import com.github.damontecres.wholphin.ui.isNotNullOrBlank
import com.github.damontecres.wholphin.ui.setup.rememberIdColor import com.github.damontecres.wholphin.ui.setup.rememberIdColor
import com.github.damontecres.wholphin.ui.theme.WholphinTheme import com.github.damontecres.wholphin.ui.theme.WholphinTheme
import java.util.UUID import java.util.UUID

View file

@ -70,13 +70,13 @@ import com.github.damontecres.wholphin.util.ExceptionHandler
import com.github.damontecres.wholphin.util.GetItemsRequestHandler import com.github.damontecres.wholphin.util.GetItemsRequestHandler
import com.github.damontecres.wholphin.util.GetPersonsHandler import com.github.damontecres.wholphin.util.GetPersonsHandler
import com.github.damontecres.wholphin.util.LoadingState import com.github.damontecres.wholphin.util.LoadingState
import com.github.damontecres.wholphin.util.WholphinDispatchers
import com.github.damontecres.wholphin.util.successValue import com.github.damontecres.wholphin.util.successValue
import dagger.assisted.Assisted import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject import dagger.assisted.AssistedInject
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.catch
@ -251,7 +251,7 @@ class CollectionFolderViewModel
fun saveViewOptions(viewOptions: ViewOptions) { fun saveViewOptions(viewOptions: ViewOptions) {
_state.update { it.copy(viewOptions = viewOptions) } _state.update { it.copy(viewOptions = viewOptions) }
viewModelScope.launch(ExceptionHandler() + Dispatchers.IO) { viewModelScope.launch(ExceptionHandler() + WholphinDispatchers.IO) {
saveLibraryDisplayInfo(viewOptions = viewOptions) saveLibraryDisplayInfo(viewOptions = viewOptions)
if (!viewOptions.showBackdrop) { if (!viewOptions.showBackdrop) {
backdropService.clearBackdrop() backdropService.clearBackdrop()
@ -295,7 +295,7 @@ class CollectionFolderViewModel
recursive: Boolean, recursive: Boolean,
filter: GetItemsFilter, filter: GetItemsFilter,
useSeriesForPrimary: Boolean, useSeriesForPrimary: Boolean,
) = viewModelScope.launch(Dispatchers.IO) { ) = viewModelScope.launch(WholphinDispatchers.IO) {
_state.update { _state.update {
it.copy( it.copy(
items = DataLoadingState.Loading, items = DataLoadingState.Loading,
@ -437,7 +437,7 @@ class CollectionFolderViewModel
) )
suspend fun positionOfLetter(letter: Char): Int? = suspend fun positionOfLetter(letter: Char): Int? =
withContext(Dispatchers.IO) { withContext(WholphinDispatchers.IO) {
val sort = state.value.sortAndDirection val sort = state.value.sortAndDirection
val filter = state.value.filter val filter = state.value.filter
val request = val request =
@ -461,7 +461,7 @@ class CollectionFolderViewModel
itemId: UUID, itemId: UUID,
played: Boolean, played: Boolean,
) { ) {
viewModelScope.launch(ExceptionHandler() + Dispatchers.IO) { viewModelScope.launch(ExceptionHandler() + WholphinDispatchers.IO) {
favoriteWatchManager.setWatched(itemId, played) favoriteWatchManager.setWatched(itemId, played)
(state.value.items as? DataLoadingState.Success)?.let { (state.value.items as? DataLoadingState.Success)?.let {
(it.data as? ApiRequestPager<*>)?.refreshItem(position, itemId) (it.data as? ApiRequestPager<*>)?.refreshItem(position, itemId)
@ -474,7 +474,7 @@ class CollectionFolderViewModel
itemId: UUID, itemId: UUID,
favorite: Boolean, favorite: Boolean,
) { ) {
viewModelScope.launch(ExceptionHandler() + Dispatchers.IO) { viewModelScope.launch(ExceptionHandler() + WholphinDispatchers.IO) {
favoriteWatchManager.setFavorite(itemId, favorite) favoriteWatchManager.setFavorite(itemId, favorite)
(state.value.items as? DataLoadingState.Success)?.let { (state.value.items as? DataLoadingState.Success)?.let {
(it.data as? ApiRequestPager<*>)?.refreshItem(position, itemId) (it.data as? ApiRequestPager<*>)?.refreshItem(position, itemId)

View file

@ -33,13 +33,13 @@ import com.github.damontecres.wholphin.ui.tryRequestFocus
import com.github.damontecres.wholphin.util.DataLoadingState import com.github.damontecres.wholphin.util.DataLoadingState
import com.github.damontecres.wholphin.util.GetGenresRequestHandler import com.github.damontecres.wholphin.util.GetGenresRequestHandler
import com.github.damontecres.wholphin.util.GetItemsRequestHandler import com.github.damontecres.wholphin.util.GetItemsRequestHandler
import com.github.damontecres.wholphin.util.WholphinDispatchers
import com.mayakapps.kache.InMemoryKache import com.mayakapps.kache.InMemoryKache
import dagger.assisted.Assisted import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject import dagger.assisted.AssistedInject
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
@ -142,7 +142,7 @@ class GenreViewModel
} }
suspend fun positionOfLetter(letter: Char): Int = suspend fun positionOfLetter(letter: Char): Int =
withContext(Dispatchers.IO) { withContext(WholphinDispatchers.IO) {
val request = val request =
GetGenresRequest( GetGenresRequest(
parentId = itemId, parentId = itemId,
@ -197,7 +197,7 @@ suspend fun getGenreImageMap(
val semaphore = Semaphore(4) val semaphore = Semaphore(4)
genres genres
.map { genreId -> .map { genreId ->
scope.async(Dispatchers.IO) { scope.async(WholphinDispatchers.IO) {
semaphore.withPermit { semaphore.withPermit {
val item = val item =
GetItemsRequestHandler GetItemsRequestHandler

View file

@ -38,13 +38,13 @@ import com.github.damontecres.wholphin.util.ApiRequestPager
import com.github.damontecres.wholphin.util.DataLoadingState import com.github.damontecres.wholphin.util.DataLoadingState
import com.github.damontecres.wholphin.util.ExceptionHandler import com.github.damontecres.wholphin.util.ExceptionHandler
import com.github.damontecres.wholphin.util.RequestHandler import com.github.damontecres.wholphin.util.RequestHandler
import com.github.damontecres.wholphin.util.WholphinDispatchers
import dagger.assisted.Assisted import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject import dagger.assisted.AssistedInject
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.CancellationException import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.update import kotlinx.coroutines.flow.update
@ -150,7 +150,7 @@ class ItemGridViewModel
itemId: UUID, itemId: UUID,
played: Boolean, played: Boolean,
) { ) {
viewModelScope.launch(ExceptionHandler() + Dispatchers.IO) { viewModelScope.launch(ExceptionHandler() + WholphinDispatchers.IO) {
favoriteWatchManager.setWatched(itemId, played) favoriteWatchManager.setWatched(itemId, played)
(state.value.items as? DataLoadingState.Success)?.let { (state.value.items as? DataLoadingState.Success)?.let {
(it.data as? ApiRequestPager<*>)?.refreshItem(position, itemId) (it.data as? ApiRequestPager<*>)?.refreshItem(position, itemId)
@ -163,7 +163,7 @@ class ItemGridViewModel
itemId: UUID, itemId: UUID,
favorite: Boolean, favorite: Boolean,
) { ) {
viewModelScope.launch(ExceptionHandler() + Dispatchers.IO) { viewModelScope.launch(ExceptionHandler() + WholphinDispatchers.IO) {
favoriteWatchManager.setFavorite(itemId, favorite) favoriteWatchManager.setFavorite(itemId, favorite)
(state.value.items as? DataLoadingState.Success)?.let { (state.value.items as? DataLoadingState.Success)?.let {
(it.data as? ApiRequestPager<*>)?.refreshItem(position, itemId) (it.data as? ApiRequestPager<*>)?.refreshItem(position, itemId)

View file

@ -48,13 +48,13 @@ import com.github.damontecres.wholphin.util.GetItemsRequestHandler
import com.github.damontecres.wholphin.util.HomeRowLoadingState import com.github.damontecres.wholphin.util.HomeRowLoadingState
import com.github.damontecres.wholphin.util.LoadingState import com.github.damontecres.wholphin.util.LoadingState
import com.github.damontecres.wholphin.util.RequestHandler import com.github.damontecres.wholphin.util.RequestHandler
import com.github.damontecres.wholphin.util.WholphinDispatchers
import dagger.assisted.Assisted import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject import dagger.assisted.AssistedInject
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.CancellationException import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.first
@ -173,7 +173,7 @@ class RecommendedViewModel
} }
private fun fetchSuggestions() { private fun fetchSuggestions() {
viewModelScope.launch(Dispatchers.IO) { viewModelScope.launch(WholphinDispatchers.IO) {
val limit = val limit =
userPreferencesService userPreferencesService
.getCurrent() .getCurrent()

View file

@ -32,11 +32,11 @@ import com.github.damontecres.wholphin.ui.launchIO
import com.github.damontecres.wholphin.ui.tryRequestFocus import com.github.damontecres.wholphin.ui.tryRequestFocus
import com.github.damontecres.wholphin.util.DataLoadingState import com.github.damontecres.wholphin.util.DataLoadingState
import com.github.damontecres.wholphin.util.GetStudiosRequestHandler import com.github.damontecres.wholphin.util.GetStudiosRequestHandler
import com.github.damontecres.wholphin.util.WholphinDispatchers
import dagger.assisted.Assisted import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject import dagger.assisted.AssistedInject
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.update import kotlinx.coroutines.flow.update
@ -113,7 +113,7 @@ class StudioViewModel
} }
suspend fun positionOfLetter(letter: Char): Int = suspend fun positionOfLetter(letter: Char): Int =
withContext(Dispatchers.IO) { withContext(WholphinDispatchers.IO) {
val request = val request =
GetStudiosRequest( GetStudiosRequest(
userId = serverRepository.currentUser?.id, userId = serverRepository.currentUser?.id,

View file

@ -17,10 +17,10 @@ import android.speech.RecognizerIntent
import android.speech.SpeechRecognizer import android.speech.SpeechRecognizer
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import com.github.damontecres.wholphin.R import com.github.damontecres.wholphin.R
import com.github.damontecres.wholphin.util.WholphinDispatchers
import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.android.asCoroutineDispatcher import kotlinx.coroutines.android.asCoroutineDispatcher
@ -175,7 +175,7 @@ class VoiceInputManager
private fun provideMainDispatcher(): CoroutineDispatcher = private fun provideMainDispatcher(): CoroutineDispatcher =
try { try {
Dispatchers.Main.immediate WholphinDispatchers.Main.immediate
} catch (_: IllegalStateException) { } catch (_: IllegalStateException) {
// Fallback for unit tests where Main dispatcher is not installed // Fallback for unit tests where Main dispatcher is not installed
handler.asCoroutineDispatcher() handler.asCoroutineDispatcher()

View file

@ -73,7 +73,7 @@ import com.github.damontecres.wholphin.ui.playback.isForwardButton
import com.github.damontecres.wholphin.ui.playback.isPlayKeyUp import com.github.damontecres.wholphin.ui.playback.isPlayKeyUp
import com.github.damontecres.wholphin.ui.tryRequestFocus import com.github.damontecres.wholphin.ui.tryRequestFocus
import com.github.damontecres.wholphin.util.ExceptionHandler import com.github.damontecres.wholphin.util.ExceptionHandler
import kotlinx.coroutines.Dispatchers import com.github.damontecres.wholphin.util.WholphinDispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import timber.log.Timber import timber.log.Timber
@ -217,7 +217,7 @@ fun <T : CardGridItem> CardGrid(
{ letter: Char -> { letter: Char ->
scope.launch(ExceptionHandler()) { scope.launch(ExceptionHandler()) {
val jumpPosition = val jumpPosition =
withContext(Dispatchers.IO) { withContext(WholphinDispatchers.IO) {
letterPosition.invoke(letter) letterPosition.invoke(letter)
} }
Timber.d("Alphabet jump to $jumpPosition") Timber.d("Alphabet jump to $jumpPosition")

View file

@ -53,12 +53,12 @@ import com.github.damontecres.wholphin.ui.util.StringStringProvider
import com.github.damontecres.wholphin.util.ApiRequestPager import com.github.damontecres.wholphin.util.ApiRequestPager
import com.github.damontecres.wholphin.util.ExceptionHandler import com.github.damontecres.wholphin.util.ExceptionHandler
import com.github.damontecres.wholphin.util.HomeRowLoadingState import com.github.damontecres.wholphin.util.HomeRowLoadingState
import com.github.damontecres.wholphin.util.WholphinDispatchers
import dagger.assisted.Assisted import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject import dagger.assisted.AssistedInject
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.update import kotlinx.coroutines.flow.update
@ -166,7 +166,7 @@ class HomeRowGridViewModel
itemId: UUID, itemId: UUID,
played: Boolean, played: Boolean,
) { ) {
viewModelScope.launch(ExceptionHandler() + Dispatchers.IO) { viewModelScope.launch(ExceptionHandler() + WholphinDispatchers.IO) {
favoriteWatchManager.setWatched(itemId, played) favoriteWatchManager.setWatched(itemId, played)
(state.value.loading as? HomeRowLoadingState.Success)?.let { (state.value.loading as? HomeRowLoadingState.Success)?.let {
(it.items as? ApiRequestPager<*>)?.refreshItem(position, itemId) (it.items as? ApiRequestPager<*>)?.refreshItem(position, itemId)
@ -179,7 +179,7 @@ class HomeRowGridViewModel
itemId: UUID, itemId: UUID,
favorite: Boolean, favorite: Boolean,
) { ) {
viewModelScope.launch(ExceptionHandler() + Dispatchers.IO) { viewModelScope.launch(ExceptionHandler() + WholphinDispatchers.IO) {
favoriteWatchManager.setFavorite(itemId, favorite) favoriteWatchManager.setFavorite(itemId, favorite)
(state.value.loading as? HomeRowLoadingState.Success)?.let { (state.value.loading as? HomeRowLoadingState.Success)?.let {
(it.items as? ApiRequestPager<*>)?.refreshItem(position, itemId) (it.items as? ApiRequestPager<*>)?.refreshItem(position, itemId)

View file

@ -107,12 +107,12 @@ import com.github.damontecres.wholphin.util.ApiRequestPager
import com.github.damontecres.wholphin.util.ExceptionHandler import com.github.damontecres.wholphin.util.ExceptionHandler
import com.github.damontecres.wholphin.util.GetItemsRequestHandler import com.github.damontecres.wholphin.util.GetItemsRequestHandler
import com.github.damontecres.wholphin.util.LoadingState import com.github.damontecres.wholphin.util.LoadingState
import com.github.damontecres.wholphin.util.WholphinDispatchers
import dagger.assisted.Assisted import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject import dagger.assisted.AssistedInject
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.update import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@ -316,14 +316,14 @@ class PlaylistViewModel
fun setWatched( fun setWatched(
itemId: UUID, itemId: UUID,
played: Boolean, played: Boolean,
) = viewModelScope.launch(ExceptionHandler() + Dispatchers.IO) { ) = viewModelScope.launch(ExceptionHandler() + WholphinDispatchers.IO) {
favoriteWatchManager.setWatched(itemId, played) favoriteWatchManager.setWatched(itemId, played)
} }
fun setFavorite( fun setFavorite(
itemId: UUID, itemId: UUID,
favorite: Boolean, favorite: Boolean,
) = viewModelScope.launch(ExceptionHandler() + Dispatchers.IO) { ) = viewModelScope.launch(ExceptionHandler() + WholphinDispatchers.IO) {
favoriteWatchManager.setFavorite(itemId, favorite) favoriteWatchManager.setFavorite(itemId, favorite)
} }

View file

@ -40,12 +40,12 @@ import com.github.damontecres.wholphin.util.ExceptionHandler
import com.github.damontecres.wholphin.util.GetItemsRequestHandler import com.github.damontecres.wholphin.util.GetItemsRequestHandler
import com.github.damontecres.wholphin.util.HomeRowLoadingState import com.github.damontecres.wholphin.util.HomeRowLoadingState
import com.github.damontecres.wholphin.util.LoadingState import com.github.damontecres.wholphin.util.LoadingState
import com.github.damontecres.wholphin.util.WholphinDispatchers
import dagger.assisted.Assisted import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject import dagger.assisted.AssistedInject
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.async import kotlinx.coroutines.async
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
@ -210,7 +210,7 @@ class CollectionViewModel
supervisorScope { supervisorScope {
val jobs = val jobs =
typesInCollection.map { type -> typesInCollection.map { type ->
async(Dispatchers.IO) { async(WholphinDispatchers.IO) {
val title = ResStringProvider(formatTypeName(type)) val title = ResStringProvider(formatTypeName(type))
val result = val result =
try { try {
@ -359,7 +359,7 @@ class CollectionViewModel
) )
suspend fun letterPosition(letter: Char): Int = suspend fun letterPosition(letter: Char): Int =
withContext(Dispatchers.IO) { withContext(WholphinDispatchers.IO) {
val sort = state.value.sortAndDirection val sort = state.value.sortAndDirection
val filter = state.value.itemFilter val filter = state.value.itemFilter
val request = val request =
@ -387,7 +387,7 @@ class CollectionViewModel
itemId: UUID, itemId: UUID,
played: Boolean, played: Boolean,
position: RowColumn?, position: RowColumn?,
) = viewModelScope.launch(Dispatchers.IO + ExceptionHandler()) { ) = viewModelScope.launch(WholphinDispatchers.IO + ExceptionHandler()) {
favoriteWatchManager.setWatched(itemId, played) favoriteWatchManager.setWatched(itemId, played)
if (itemId == state.value.collection?.id) { if (itemId == state.value.collection?.id) {
refreshCollection() refreshCollection()
@ -400,7 +400,7 @@ class CollectionViewModel
itemId: UUID, itemId: UUID,
favorite: Boolean, favorite: Boolean,
position: RowColumn?, position: RowColumn?,
) = viewModelScope.launch(ExceptionHandler() + Dispatchers.IO) { ) = viewModelScope.launch(ExceptionHandler() + WholphinDispatchers.IO) {
favoriteWatchManager.setFavorite(itemId, favorite) favoriteWatchManager.setFavorite(itemId, favorite)
if (itemId == state.value.collection?.id) { if (itemId == state.value.collection?.id) {
refreshCollection() refreshCollection()

View file

@ -24,6 +24,7 @@ import com.github.damontecres.wholphin.ui.launchIO
import com.github.damontecres.wholphin.ui.nav.Destination import com.github.damontecres.wholphin.ui.nav.Destination
import com.github.damontecres.wholphin.ui.showToast import com.github.damontecres.wholphin.ui.showToast
import com.github.damontecres.wholphin.util.DataLoadingState import com.github.damontecres.wholphin.util.DataLoadingState
import com.github.damontecres.wholphin.util.WholphinDispatchers
import com.github.damontecres.wholphin.util.successValue import com.github.damontecres.wholphin.util.successValue
import dagger.assisted.Assisted import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory import dagger.assisted.AssistedFactory
@ -31,7 +32,6 @@ import dagger.assisted.AssistedInject
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.Deferred import kotlinx.coroutines.Deferred
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
import kotlinx.coroutines.async import kotlinx.coroutines.async
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
@ -73,7 +73,7 @@ class DiscoverMovieViewModel
} }
private fun fetchAndSetItem(): Deferred<MovieDetails?> = private fun fetchAndSetItem(): Deferred<MovieDetails?> =
viewModelScope.async(Dispatchers.IO) { viewModelScope.async(WholphinDispatchers.IO) {
try { try {
val movie = seerrService.api.moviesApi.movieMovieIdGet(movieId = item.id) val movie = seerrService.api.moviesApi.movieMovieIdGet(movieId = item.id)
_state.update { it.copy(movie = DataLoadingState.Success(movie)) } _state.update { it.copy(movie = DataLoadingState.Success(movie)) }

View file

@ -22,6 +22,7 @@ import com.github.damontecres.wholphin.ui.launchIO
import com.github.damontecres.wholphin.ui.nav.Destination import com.github.damontecres.wholphin.ui.nav.Destination
import com.github.damontecres.wholphin.ui.showToast import com.github.damontecres.wholphin.ui.showToast
import com.github.damontecres.wholphin.util.DataLoadingState import com.github.damontecres.wholphin.util.DataLoadingState
import com.github.damontecres.wholphin.util.WholphinDispatchers
import com.github.damontecres.wholphin.util.successValue import com.github.damontecres.wholphin.util.successValue
import dagger.assisted.Assisted import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory import dagger.assisted.AssistedFactory
@ -30,7 +31,6 @@ import dagger.hilt.android.lifecycle.HiltViewModel
import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.CancellationException import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.Deferred import kotlinx.coroutines.Deferred
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
import kotlinx.coroutines.async import kotlinx.coroutines.async
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
@ -71,7 +71,7 @@ class DiscoverSeriesViewModel
} }
private fun fetchAndSetItem(): Deferred<TvDetails?> = private fun fetchAndSetItem(): Deferred<TvDetails?> =
viewModelScope.async(Dispatchers.IO) { viewModelScope.async(WholphinDispatchers.IO) {
try { try {
val tv = seerrService.api.tvApi.tvTvIdGet(tvId = item.id) val tv = seerrService.api.tvApi.tvTvIdGet(tvId = item.id)
_state.update { it.copy(tvSeries = DataLoadingState.Success(tv)) } _state.update { it.copy(tvSeries = DataLoadingState.Success(tv)) }

View file

@ -23,13 +23,13 @@ import com.github.damontecres.wholphin.ui.nav.Destination
import com.github.damontecres.wholphin.ui.showToast import com.github.damontecres.wholphin.ui.showToast
import com.github.damontecres.wholphin.util.DataLoadingState import com.github.damontecres.wholphin.util.DataLoadingState
import com.github.damontecres.wholphin.util.ExceptionHandler import com.github.damontecres.wholphin.util.ExceptionHandler
import com.github.damontecres.wholphin.util.WholphinDispatchers
import dagger.assisted.Assisted import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject import dagger.assisted.AssistedInject
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.CancellationException import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
@ -126,7 +126,7 @@ class EpisodeViewModel
fun setWatched( fun setWatched(
itemId: UUID, itemId: UUID,
played: Boolean, played: Boolean,
) = viewModelScope.launch(ExceptionHandler() + Dispatchers.IO) { ) = viewModelScope.launch(ExceptionHandler() + WholphinDispatchers.IO) {
favoriteWatchManager.setWatched(itemId, played) favoriteWatchManager.setWatched(itemId, played)
fetchAndSetItem() fetchAndSetItem()
} }
@ -134,7 +134,7 @@ class EpisodeViewModel
fun setFavorite( fun setFavorite(
itemId: UUID, itemId: UUID,
favorite: Boolean, favorite: Boolean,
) = viewModelScope.launch(ExceptionHandler() + Dispatchers.IO) { ) = viewModelScope.launch(ExceptionHandler() + WholphinDispatchers.IO) {
favoriteWatchManager.setFavorite(itemId, favorite) favoriteWatchManager.setFavorite(itemId, favorite)
fetchAndSetItem() fetchAndSetItem()
} }

View file

@ -59,11 +59,11 @@ import com.github.damontecres.wholphin.ui.rememberInt
import com.github.damontecres.wholphin.ui.rememberPosition import com.github.damontecres.wholphin.ui.rememberPosition
import com.github.damontecres.wholphin.ui.tryRequestFocus import com.github.damontecres.wholphin.ui.tryRequestFocus
import com.github.damontecres.wholphin.util.LoadingState import com.github.damontecres.wholphin.util.LoadingState
import com.github.damontecres.wholphin.util.WholphinDispatchers
import eu.wewox.programguide.ProgramGuide import eu.wewox.programguide.ProgramGuide
import eu.wewox.programguide.ProgramGuideDimensions import eu.wewox.programguide.ProgramGuideDimensions
import eu.wewox.programguide.ProgramGuideItem import eu.wewox.programguide.ProgramGuideItem
import eu.wewox.programguide.rememberSaveableProgramGuideState import eu.wewox.programguide.rememberSaveableProgramGuideState
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import timber.log.Timber import timber.log.Timber
@ -319,7 +319,7 @@ fun TvGuideGridContent(
var focusedProgramIndex by rememberInt(0) var focusedProgramIndex by rememberInt(0)
LaunchedEffect(onFocus, focusedProgramIndex) { LaunchedEffect(onFocus, focusedProgramIndex) {
withContext(Dispatchers.Default) { withContext(WholphinDispatchers.Default) {
val program = programs.programs.getOrNull(focusedProgramIndex) val program = programs.programs.getOrNull(focusedProgramIndex)
if (program != null) { if (program != null) {
val channelIndex = channels.indexOfFirst { it.id == program.channelId } val channelIndex = channels.indexOfFirst { it.id == program.channelId }

View file

@ -86,12 +86,12 @@ import com.github.damontecres.wholphin.util.ApiRequestPager
import com.github.damontecres.wholphin.util.ExceptionHandler import com.github.damontecres.wholphin.util.ExceptionHandler
import com.github.damontecres.wholphin.util.GetItemsRequestHandler import com.github.damontecres.wholphin.util.GetItemsRequestHandler
import com.github.damontecres.wholphin.util.LoadingState import com.github.damontecres.wholphin.util.LoadingState
import com.github.damontecres.wholphin.util.WholphinDispatchers
import dagger.assisted.Assisted import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject import dagger.assisted.AssistedInject
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async import kotlinx.coroutines.async
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
@ -242,7 +242,7 @@ class AlbumViewModel
fun setFavorite( fun setFavorite(
itemId: UUID, itemId: UUID,
favorite: Boolean, favorite: Boolean,
) = viewModelScope.launch(ExceptionHandler() + Dispatchers.IO) { ) = viewModelScope.launch(ExceptionHandler() + WholphinDispatchers.IO) {
favoriteWatchManager.setFavorite(itemId, favorite) favoriteWatchManager.setFavorite(itemId, favorite)
val album = val album =
api.userLibraryApi api.userLibraryApi

View file

@ -81,12 +81,12 @@ import com.github.damontecres.wholphin.util.ApiRequestPager
import com.github.damontecres.wholphin.util.ExceptionHandler import com.github.damontecres.wholphin.util.ExceptionHandler
import com.github.damontecres.wholphin.util.GetItemsRequestHandler import com.github.damontecres.wholphin.util.GetItemsRequestHandler
import com.github.damontecres.wholphin.util.LoadingState import com.github.damontecres.wholphin.util.LoadingState
import com.github.damontecres.wholphin.util.WholphinDispatchers
import dagger.assisted.Assisted import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject import dagger.assisted.AssistedInject
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async import kotlinx.coroutines.async
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
@ -254,7 +254,7 @@ class ArtistViewModel
fun setFavorite( fun setFavorite(
itemId: UUID, itemId: UUID,
favorite: Boolean, favorite: Boolean,
) = viewModelScope.launch(ExceptionHandler() + Dispatchers.IO) { ) = viewModelScope.launch(ExceptionHandler() + WholphinDispatchers.IO) {
favoriteWatchManager.setFavorite(itemId, favorite) favoriteWatchManager.setFavorite(itemId, favorite)
val artist = val artist =
api.userLibraryApi api.userLibraryApi

View file

@ -44,7 +44,6 @@ import com.github.damontecres.wholphin.ui.playback.ControllerViewState
import com.github.damontecres.wholphin.ui.playback.overlay.PlaybackAction import com.github.damontecres.wholphin.ui.playback.overlay.PlaybackAction
import com.github.damontecres.wholphin.ui.playback.overlay.PlaybackButton import com.github.damontecres.wholphin.ui.playback.overlay.PlaybackButton
import com.github.damontecres.wholphin.ui.playback.overlay.PlaybackButtons import com.github.damontecres.wholphin.ui.playback.overlay.PlaybackButtons
import com.github.damontecres.wholphin.ui.playback.overlay.PlaybackFaButton
import com.github.damontecres.wholphin.ui.playback.overlay.buttonSpacing import com.github.damontecres.wholphin.ui.playback.overlay.buttonSpacing
import com.github.damontecres.wholphin.ui.theme.PreviewInteractionSource import com.github.damontecres.wholphin.ui.theme.PreviewInteractionSource
import com.github.damontecres.wholphin.ui.theme.WholphinTheme import com.github.damontecres.wholphin.ui.theme.WholphinTheme

View file

@ -27,13 +27,13 @@ import com.github.damontecres.wholphin.ui.launchDefault
import com.github.damontecres.wholphin.ui.main.settings.MoveDirection import com.github.damontecres.wholphin.ui.main.settings.MoveDirection
import com.github.damontecres.wholphin.ui.onMain import com.github.damontecres.wholphin.ui.onMain
import com.github.damontecres.wholphin.ui.playback.ControllerViewState import com.github.damontecres.wholphin.ui.playback.ControllerViewState
import com.github.damontecres.wholphin.util.WholphinDispatchers
import com.mayakapps.kache.InMemoryKache import com.mayakapps.kache.InMemoryKache
import dagger.assisted.AssistedFactory import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject import dagger.assisted.AssistedInject
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
@ -95,7 +95,7 @@ class NowPlayingViewModel
private val lyricCache = private val lyricCache =
InMemoryKache<UUID, LyricDto>(20) { InMemoryKache<UUID, LyricDto>(20) {
creationScope = CoroutineScope(Dispatchers.IO) creationScope = CoroutineScope(WholphinDispatchers.IO)
} }
init { init {

View file

@ -24,7 +24,6 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.tv.material3.DenseListItem import androidx.tv.material3.DenseListItem
import androidx.tv.material3.Icon import androidx.tv.material3.Icon
import androidx.tv.material3.ListItem
import androidx.tv.material3.ListItemDefaults import androidx.tv.material3.ListItemDefaults
import androidx.tv.material3.LocalContentColor import androidx.tv.material3.LocalContentColor
import androidx.tv.material3.Text import androidx.tv.material3.Text

View file

@ -41,6 +41,7 @@ import com.github.damontecres.wholphin.util.DataLoadingState
import com.github.damontecres.wholphin.util.ExceptionHandler import com.github.damontecres.wholphin.util.ExceptionHandler
import com.github.damontecres.wholphin.util.GetEpisodesRequestHandler import com.github.damontecres.wholphin.util.GetEpisodesRequestHandler
import com.github.damontecres.wholphin.util.GetItemsRequestHandler import com.github.damontecres.wholphin.util.GetItemsRequestHandler
import com.github.damontecres.wholphin.util.WholphinDispatchers
import com.github.damontecres.wholphin.util.successValue import com.github.damontecres.wholphin.util.successValue
import com.google.common.cache.CacheBuilder import com.google.common.cache.CacheBuilder
import dagger.assisted.Assisted import dagger.assisted.Assisted
@ -50,7 +51,6 @@ import dagger.hilt.android.lifecycle.HiltViewModel
import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.CompletableDeferred import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.Deferred import kotlinx.coroutines.Deferred
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
import kotlinx.coroutines.async import kotlinx.coroutines.async
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
@ -137,7 +137,7 @@ class SeriesViewModel
val episodeListDeferred = val episodeListDeferred =
if (seriesPageType == SeriesPageType.OVERVIEW) { if (seriesPageType == SeriesPageType.OVERVIEW) {
viewModelScope.async(Dispatchers.IO) { viewModelScope.async(WholphinDispatchers.IO) {
if (seasonEpisodeIds != null) { if (seasonEpisodeIds != null) {
loadEpisodesInternal( loadEpisodesInternal(
seasonEpisodeIds.seasonId, seasonEpisodeIds.seasonId,
@ -292,7 +292,7 @@ class SeriesViewModel
series: BaseItem, series: BaseItem,
seasonNum: Int?, seasonNum: Int?,
): Deferred<List<BaseItem?>> = ): Deferred<List<BaseItem?>> =
viewModelScope.async(Dispatchers.IO) { viewModelScope.async(WholphinDispatchers.IO) {
Timber.v("getSeasons for %s", series.id) Timber.v("getSeasons for %s", series.id)
val request = val request =
GetItemsRequest( GetItemsRequest(
@ -405,7 +405,7 @@ class SeriesViewModel
itemId: UUID, itemId: UUID,
played: Boolean, played: Boolean,
listIndex: Int?, listIndex: Int?,
) = viewModelScope.launch(Dispatchers.IO + ExceptionHandler()) { ) = viewModelScope.launch(WholphinDispatchers.IO + ExceptionHandler()) {
favoriteWatchManager.setWatched(itemId, played) favoriteWatchManager.setWatched(itemId, played)
listIndex?.let { listIndex?.let {
refreshEpisode(itemId, listIndex) refreshEpisode(itemId, listIndex)
@ -440,7 +440,7 @@ class SeriesViewModel
itemId: UUID, itemId: UUID,
favorite: Boolean, favorite: Boolean,
listIndex: Int?, listIndex: Int?,
) = viewModelScope.launch(ExceptionHandler() + Dispatchers.IO) { ) = viewModelScope.launch(ExceptionHandler() + WholphinDispatchers.IO) {
favoriteWatchManager.setFavorite(itemId, favorite) favoriteWatchManager.setFavorite(itemId, favorite)
if (listIndex != null) { if (listIndex != null) {
refreshEpisode(itemId, listIndex) refreshEpisode(itemId, listIndex)
@ -452,13 +452,13 @@ class SeriesViewModel
fun setSeasonWatched( fun setSeasonWatched(
seasonId: UUID, seasonId: UUID,
played: Boolean, played: Boolean,
) = viewModelScope.launch(Dispatchers.IO + ExceptionHandler()) { ) = viewModelScope.launch(WholphinDispatchers.IO + ExceptionHandler()) {
setWatched(seasonId, played, null) setWatched(seasonId, played, null)
updateSeries() updateSeries()
} }
fun setWatchedSeries(played: Boolean) = fun setWatchedSeries(played: Boolean) =
viewModelScope.launch(ExceptionHandler() + Dispatchers.IO) { viewModelScope.launch(ExceptionHandler() + WholphinDispatchers.IO) {
favoriteWatchManager.setWatched(seriesId, played) favoriteWatchManager.setWatched(seriesId, played)
updateSeries() updateSeries()
} }
@ -466,7 +466,7 @@ class SeriesViewModel
fun refreshEpisode( fun refreshEpisode(
itemId: UUID, itemId: UUID,
listIndex: Int, listIndex: Int,
) = viewModelScope.launch(ExceptionHandler() + Dispatchers.IO) { ) = viewModelScope.launch(ExceptionHandler() + WholphinDispatchers.IO) {
val eps = state.value.episodes val eps = state.value.episodes
if (eps is EpisodeList.Success) { if (eps is EpisodeList.Success) {
eps.episodes.refreshItem(listIndex, itemId) eps.episodes.refreshItem(listIndex, itemId)
@ -481,7 +481,7 @@ class SeriesViewModel
* Play whichever episode is next up for series or else the first episode * Play whichever episode is next up for series or else the first episode
*/ */
fun playNextUp() { fun playNextUp() {
viewModelScope.launch(ExceptionHandler() + Dispatchers.IO) { viewModelScope.launch(ExceptionHandler() + WholphinDispatchers.IO) {
val result by api.tvShowsApi.getNextUp(seriesId = seriesId) val result by api.tvShowsApi.getNextUp(seriesId = seriesId)
val nextUp = val nextUp =
result.items.firstOrNull() ?: api.tvShowsApi result.items.firstOrNull() ?: api.tvShowsApi
@ -491,7 +491,7 @@ class SeriesViewModel
).content.items ).content.items
.firstOrNull() .firstOrNull()
if (nextUp != null) { if (nextUp != null) {
withContext(Dispatchers.Main) { withContext(WholphinDispatchers.Main) {
navigateTo(Destination.Playback(BaseItem(nextUp))) navigateTo(Destination.Playback(BaseItem(nextUp)))
} }
} else { } else {
@ -582,7 +582,7 @@ class SeriesViewModel
val result = val result =
peopleInEpisodeCache peopleInEpisodeCache
.get(item.id) { .get(item.id) {
viewModelScope.async(Dispatchers.IO) { viewModelScope.async(WholphinDispatchers.IO) {
val list = val list =
api.userLibraryApi api.userLibraryApi
.getItem(item.id) .getItem(item.id)

View file

@ -35,8 +35,8 @@ import com.github.damontecres.wholphin.ui.launchIO
import com.github.damontecres.wholphin.ui.nav.Destination import com.github.damontecres.wholphin.ui.nav.Destination
import com.github.damontecres.wholphin.ui.tryRequestFocus import com.github.damontecres.wholphin.ui.tryRequestFocus
import com.github.damontecres.wholphin.util.DataLoadingState import com.github.damontecres.wholphin.util.DataLoadingState
import com.github.damontecres.wholphin.util.WholphinDispatchers
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
@ -77,7 +77,7 @@ class SeerrRequestsViewModel
val requests = val requests =
mediaRequests.mapNotNull { request -> mediaRequests.mapNotNull { request ->
if (request.media?.tmdbId != null) { if (request.media?.tmdbId != null) {
viewModelScope.async(Dispatchers.IO) { viewModelScope.async(WholphinDispatchers.IO) {
semaphore.withPermit { semaphore.withPermit {
val type = SeerrItemType.fromString(request.type) val type = SeerrItemType.fromString(request.type)
when (type) { when (type) {

View file

@ -28,9 +28,9 @@ import com.github.damontecres.wholphin.ui.util.EmptyStringProvider
import com.github.damontecres.wholphin.util.ExceptionHandler import com.github.damontecres.wholphin.util.ExceptionHandler
import com.github.damontecres.wholphin.util.HomeRowLoadingState import com.github.damontecres.wholphin.util.HomeRowLoadingState
import com.github.damontecres.wholphin.util.LoadingState import com.github.damontecres.wholphin.util.LoadingState
import com.github.damontecres.wholphin.util.WholphinDispatchers
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
@ -117,7 +117,7 @@ class HomeViewModel
val deferred = val deferred =
settings.rows settings.rows
.map { row -> .map { row ->
viewModelScope.async(Dispatchers.IO) { viewModelScope.async(WholphinDispatchers.IO) {
semaphore.withPermit { semaphore.withPermit {
Timber.v("Fetching row: %s", row) Timber.v("Fetching row: %s", row)
try { try {
@ -220,9 +220,9 @@ class HomeViewModel
fun setWatched( fun setWatched(
itemId: UUID, itemId: UUID,
played: Boolean, played: Boolean,
) = viewModelScope.launch(ExceptionHandler() + Dispatchers.IO) { ) = viewModelScope.launch(ExceptionHandler() + WholphinDispatchers.IO) {
favoriteWatchManager.setWatched(itemId, played) favoriteWatchManager.setWatched(itemId, played)
withContext(Dispatchers.Main) { withContext(WholphinDispatchers.Main) {
init() init()
} }
} }
@ -230,9 +230,9 @@ class HomeViewModel
fun setFavorite( fun setFavorite(
itemId: UUID, itemId: UUID,
favorite: Boolean, favorite: Boolean,
) = viewModelScope.launch(ExceptionHandler() + Dispatchers.IO) { ) = viewModelScope.launch(ExceptionHandler() + WholphinDispatchers.IO) {
favoriteWatchManager.setFavorite(itemId, favorite) favoriteWatchManager.setFavorite(itemId, favorite)
withContext(Dispatchers.Main) { withContext(WholphinDispatchers.Main) {
init() init()
} }
} }

View file

@ -103,9 +103,9 @@ import com.github.damontecres.wholphin.ui.rememberPosition
import com.github.damontecres.wholphin.ui.tryRequestFocus import com.github.damontecres.wholphin.ui.tryRequestFocus
import com.github.damontecres.wholphin.util.ExceptionHandler import com.github.damontecres.wholphin.util.ExceptionHandler
import com.github.damontecres.wholphin.util.SearchRelevance import com.github.damontecres.wholphin.util.SearchRelevance
import com.github.damontecres.wholphin.util.WholphinDispatchers
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.CancellationException import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
@ -253,7 +253,7 @@ class SearchViewModel
} }
private fun searchCombined(query: String) { private fun searchCombined(query: String) {
viewModelScope.launch(ExceptionHandler() + Dispatchers.IO) { viewModelScope.launch(ExceptionHandler() + WholphinDispatchers.IO) {
try { try {
val request = val request =
GetItemsRequest( GetItemsRequest(
@ -477,7 +477,7 @@ fun SearchPage(
) { ) {
if (!searchClicked) return@LaunchedEffect if (!searchClicked) return@LaunchedEffect
withContext(Dispatchers.IO) { withContext(WholphinDispatchers.IO) {
// Want to focus on the first successful row after all of the ones before it are finished searching // Want to focus on the first successful row after all of the ones before it are finished searching
val results = val results =
if (isLibraryTab) { if (isLibraryTab) {

View file

@ -43,10 +43,10 @@ import com.github.damontecres.wholphin.ui.util.ResStringProvider
import com.github.damontecres.wholphin.ui.util.StringStringProvider import com.github.damontecres.wholphin.ui.util.StringStringProvider
import com.github.damontecres.wholphin.util.HomeRowLoadingState import com.github.damontecres.wholphin.util.HomeRowLoadingState
import com.github.damontecres.wholphin.util.LoadingState import com.github.damontecres.wholphin.util.LoadingState
import com.github.damontecres.wholphin.util.WholphinDispatchers
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
import kotlinx.coroutines.async import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll import kotlinx.coroutines.awaitAll
@ -129,7 +129,7 @@ class HomeSettingsViewModel
.let { state -> .let { state ->
state.rows state.rows
.map { row -> .map { row ->
viewModelScope.async(Dispatchers.IO) { viewModelScope.async(WholphinDispatchers.IO) {
semaphore.withPermit { semaphore.withPermit {
try { try {
homeSettingsService.fetchDataForRow( homeSettingsService.fetchDataForRow(

View file

@ -67,6 +67,7 @@ import com.github.damontecres.wholphin.util.ExceptionHandler
import com.github.damontecres.wholphin.util.LoadingState import com.github.damontecres.wholphin.util.LoadingState
import com.github.damontecres.wholphin.util.PlaybackItemState import com.github.damontecres.wholphin.util.PlaybackItemState
import com.github.damontecres.wholphin.util.TrackActivityPlaybackListener import com.github.damontecres.wholphin.util.TrackActivityPlaybackListener
import com.github.damontecres.wholphin.util.WholphinDispatchers
import com.github.damontecres.wholphin.util.checkForSupport import com.github.damontecres.wholphin.util.checkForSupport
import com.github.damontecres.wholphin.util.mpv.MpvPlayer import com.github.damontecres.wholphin.util.mpv.MpvPlayer
import com.github.damontecres.wholphin.util.mpv.mpvDeviceProfile import com.github.damontecres.wholphin.util.mpv.mpvDeviceProfile
@ -79,7 +80,6 @@ import dagger.assisted.AssistedInject
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.CancellationException import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
@ -242,7 +242,7 @@ class PlaybackViewModel
Timber.d("Selected backend: %s", playerBackend) Timber.d("Selected backend: %s", playerBackend)
if (currentPlayer.value?.backend != playerBackend) { if (currentPlayer.value?.backend != playerBackend) {
Timber.i("Switching player backend to %s", playerBackend) Timber.i("Switching player backend to %s", playerBackend)
withContext(Dispatchers.Main) { withContext(WholphinDispatchers.Main) {
disconnectPlayer() disconnectPlayer()
} }
@ -421,7 +421,7 @@ class PlaybackViewModel
positionMs: Long, positionMs: Long,
forceTranscoding: Boolean = this.forceTranscoding, forceTranscoding: Boolean = this.forceTranscoding,
): Boolean = ): Boolean =
withContext(Dispatchers.IO) { withContext(WholphinDispatchers.IO) {
val item = val item =
when (playlistItem) { when (playlistItem) {
is PlaylistItem.Intro -> playlistItem.item is PlaylistItem.Intro -> playlistItem.item
@ -581,7 +581,7 @@ class PlaybackViewModel
trickPlayInfo = trickPlayInfo, trickPlayInfo = trickPlayInfo,
) )
} }
withContext(Dispatchers.Main) { withContext(WholphinDispatchers.Main) {
changeStreams( changeStreams(
item, item,
itemPlaybackToUse, itemPlaybackToUse,
@ -612,7 +612,7 @@ class PlaybackViewModel
userInitiated: Boolean, userInitiated: Boolean,
enableDirectPlay: Boolean = !this.forceTranscoding, enableDirectPlay: Boolean = !this.forceTranscoding,
enableDirectStream: Boolean = !this.forceTranscoding, enableDirectStream: Boolean = !this.forceTranscoding,
) = withContext(Dispatchers.IO) { ) = withContext(WholphinDispatchers.IO) {
val itemId = item.id val itemId = item.id
val currentPlayback = state.value.currentPlayback val currentPlayback = state.value.currentPlayback
@ -779,7 +779,7 @@ class PlaybackViewModel
) )
} }
} }
withContext(Dispatchers.Main) { withContext(WholphinDispatchers.Main) {
// TODO, don't need to release & recreate when switching streams // TODO, don't need to release & recreate when switching streams
this@PlaybackViewModel.activityListener?.let { this@PlaybackViewModel.activityListener?.let {
it.release() it.release()
@ -850,7 +850,7 @@ class PlaybackViewModel
subtitleIndex: Int?, subtitleIndex: Int?,
userInitiated: Boolean, userInitiated: Boolean,
): Boolean = ): Boolean =
withContext(Dispatchers.IO) { withContext(WholphinDispatchers.IO) {
// TODO there's probably no reason why we can't add external subtitles? // TODO there's probably no reason why we can't add external subtitles?
Timber.v("changeStreams direct play") Timber.v("changeStreams direct play")
@ -859,7 +859,7 @@ class PlaybackViewModel
if (externalSubtitle == null) { if (externalSubtitle == null) {
val result = val result =
withContext(Dispatchers.Main) { withContext(WholphinDispatchers.Main) {
TrackSelectionUtils.createTrackSelections( TrackSelectionUtils.createTrackSelections(
onMain { player.trackSelectionParameters }, onMain { player.trackSelectionParameters },
onMain { player.currentTracks }, onMain { player.currentTracks },
@ -1124,7 +1124,7 @@ class PlaybackViewModel
MediaSegmentType.INTRO -> prefs.skipIntros MediaSegmentType.INTRO -> prefs.skipIntros
MediaSegmentType.UNKNOWN -> SkipSegmentBehavior.IGNORE MediaSegmentType.UNKNOWN -> SkipSegmentBehavior.IGNORE
} }
withContext(Dispatchers.Main) { withContext(WholphinDispatchers.Main) {
val newSegment = val newSegment =
when (behavior) { when (behavior) {
SkipSegmentBehavior.AUTO_SKIP -> { SkipSegmentBehavior.AUTO_SKIP -> {
@ -1318,7 +1318,7 @@ class PlaybackViewModel
override fun onPlayerError(error: PlaybackException) { override fun onPlayerError(error: PlaybackException) {
Timber.e(error, "Playback error") Timber.e(error, "Playback error")
viewModelScope.launch(Dispatchers.Main + ExceptionHandler()) { viewModelScope.launch(WholphinDispatchers.Main + ExceptionHandler()) {
state.value.currentPlayback?.let { state.value.currentPlayback?.let {
when (it.playMethod) { when (it.playMethod) {
PlayMethod.TRANSCODE -> { PlayMethod.TRANSCODE -> {
@ -1346,7 +1346,7 @@ class PlaybackViewModel
enableDirectPlay = false, enableDirectPlay = false,
enableDirectStream = false, enableDirectStream = false,
) )
withContext(Dispatchers.Main) { withContext(WholphinDispatchers.Main) {
player.prepare() player.prepare()
player.play() player.play()
} }
@ -1367,7 +1367,7 @@ class PlaybackViewModel
.subscribe<PlaystateMessage>() .subscribe<PlaystateMessage>()
.onEach { message -> .onEach { message ->
message.data?.let { message.data?.let {
withContext(Dispatchers.Main) { withContext(WholphinDispatchers.Main) {
when (it.command) { when (it.command) {
PlaystateCommand.STOP -> { PlaystateCommand.STOP -> {
release() release()
@ -1422,7 +1422,7 @@ class PlaybackViewModel
* Atomically update [currentMediaInfo] * Atomically update [currentMediaInfo]
*/ */
internal suspend fun updateCurrentMedia(block: (CurrentMediaInfo) -> CurrentMediaInfo) = internal suspend fun updateCurrentMedia(block: (CurrentMediaInfo) -> CurrentMediaInfo) =
withContext(Dispatchers.IO) { withContext(WholphinDispatchers.IO) {
_state.update { _state.update {
it.copy(currentMediaInfo = block.invoke(it.currentMediaInfo)) it.copy(currentMediaInfo = block.invoke(it.currentMediaInfo))
} }

View file

@ -10,7 +10,7 @@ import com.github.damontecres.wholphin.data.model.TrackIndex
import com.github.damontecres.wholphin.ui.launchIO import com.github.damontecres.wholphin.ui.launchIO
import com.github.damontecres.wholphin.ui.onMain import com.github.damontecres.wholphin.ui.onMain
import com.github.damontecres.wholphin.ui.showToast import com.github.damontecres.wholphin.ui.showToast
import kotlinx.coroutines.Dispatchers import com.github.damontecres.wholphin.util.WholphinDispatchers
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.update import kotlinx.coroutines.flow.update
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
@ -183,7 +183,7 @@ fun PlaybackViewModel.downloadAndSwitchSubtitles(
} }
} }
subtitleSearchState.update { it.copy(status = SubtitleSearchStatus.Inactive) } subtitleSearchState.update { it.copy(status = SubtitleSearchStatus.Inactive) }
withContext(Dispatchers.Main) { withContext(WholphinDispatchers.Main) {
if (wasPlaying) { if (wasPlaying) {
player.play() player.play()
} }

View file

@ -36,7 +36,7 @@ import com.github.damontecres.wholphin.ui.theme.WholphinTheme
import com.github.damontecres.wholphin.util.TrackSupport import com.github.damontecres.wholphin.util.TrackSupport
import com.github.damontecres.wholphin.util.TrackSupportReason import com.github.damontecres.wholphin.util.TrackSupportReason
import com.github.damontecres.wholphin.util.TrackType import com.github.damontecres.wholphin.util.TrackType
import kotlinx.coroutines.Dispatchers import com.github.damontecres.wholphin.util.WholphinDispatchers
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.isActive import kotlinx.coroutines.isActive
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
@ -90,7 +90,7 @@ fun PlaybackDebugOverlay(
) )
val memoryUsed by produceState("") { val memoryUsed by produceState("") {
withContext(Dispatchers.Default) { withContext(WholphinDispatchers.Default) {
while (isActive) { while (isActive) {
val runtime = Runtime.getRuntime() val runtime = Runtime.getRuntime()
val total = runtime.totalMemory() val total = runtime.totalMemory()

View file

@ -16,7 +16,6 @@ import androidx.compose.ui.window.Dialog
import androidx.compose.ui.window.DialogProperties import androidx.compose.ui.window.DialogProperties
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.asFlow
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import androidx.tv.material3.Text import androidx.tv.material3.Text
import com.github.damontecres.wholphin.R import com.github.damontecres.wholphin.R

View file

@ -57,9 +57,9 @@ import com.github.damontecres.wholphin.ui.main.settings.MoveDirection
import com.github.damontecres.wholphin.ui.nav.NavDrawerItem import com.github.damontecres.wholphin.ui.nav.NavDrawerItem
import com.github.damontecres.wholphin.ui.theme.WholphinTheme import com.github.damontecres.wholphin.ui.theme.WholphinTheme
import com.github.damontecres.wholphin.util.ExceptionHandler import com.github.damontecres.wholphin.util.ExceptionHandler
import com.github.damontecres.wholphin.util.WholphinDispatchers
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.firstOrNull import kotlinx.coroutines.flow.firstOrNull
import kotlinx.coroutines.flow.update import kotlinx.coroutines.flow.update
@ -317,7 +317,7 @@ class NavDrawerPreferencesViewModel
return@launchDefault return@launchDefault
} }
val navDrawerPins = val navDrawerPins =
withContext(Dispatchers.IO) { withContext(WholphinDispatchers.IO) {
serverPreferencesDao serverPreferencesDao
.getNavDrawerPinnedItems(user) .getNavDrawerPinnedItems(user)
.associateBy { it.itemId } .associateBy { it.itemId }

View file

@ -34,7 +34,6 @@ import com.github.damontecres.wholphin.R
import com.github.damontecres.wholphin.ui.FontAwesome import com.github.damontecres.wholphin.ui.FontAwesome
import com.github.damontecres.wholphin.ui.PreviewTvSpec import com.github.damontecres.wholphin.ui.PreviewTvSpec
import com.github.damontecres.wholphin.ui.components.BasicDialog import com.github.damontecres.wholphin.ui.components.BasicDialog
import com.github.damontecres.wholphin.ui.components.Button
import com.github.damontecres.wholphin.ui.components.TextButton import com.github.damontecres.wholphin.ui.components.TextButton
import com.github.damontecres.wholphin.ui.playback.isEnterKey import com.github.damontecres.wholphin.ui.playback.isEnterKey
import com.github.damontecres.wholphin.ui.theme.WholphinTheme import com.github.damontecres.wholphin.ui.theme.WholphinTheme

View file

@ -13,13 +13,13 @@ import com.github.damontecres.wholphin.services.SetupNavigationManager
import com.github.damontecres.wholphin.ui.launchDefault import com.github.damontecres.wholphin.ui.launchDefault
import com.github.damontecres.wholphin.ui.launchIO import com.github.damontecres.wholphin.ui.launchIO
import com.github.damontecres.wholphin.util.LoadingState import com.github.damontecres.wholphin.util.LoadingState
import com.github.damontecres.wholphin.util.WholphinDispatchers
import dagger.assisted.Assisted import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject import dagger.assisted.AssistedInject
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.CancellationException import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.Deferred import kotlinx.coroutines.Deferred
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
import kotlinx.coroutines.async import kotlinx.coroutines.async
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
@ -119,7 +119,7 @@ class SwitchUserViewModel
} }
fun trySwitchUser(user: JellyfinUser): Deferred<String?> = fun trySwitchUser(user: JellyfinUser): Deferred<String?> =
viewModelScope.async(Dispatchers.IO) { viewModelScope.async(WholphinDispatchers.IO) {
try { try {
val current = serverRepository.changeUser(server, user) val current = serverRepository.changeUser(server, user)
setupNavigationManager.navigateTo(SetupDestination.AppContent(current)) setupNavigationManager.navigateTo(SetupDestination.AppContent(current))
@ -234,7 +234,7 @@ class SwitchUserViewModel
} }
private suspend fun getUsers(): List<JellyfinUserAndImage> = private suspend fun getUsers(): List<JellyfinUserAndImage> =
withContext(Dispatchers.IO) { withContext(WholphinDispatchers.IO) {
val api = jellyfin.createApi(server.url) val api = jellyfin.createApi(server.url)
val knownUsers = val knownUsers =
serverDao serverDao

View file

@ -27,13 +27,13 @@ import com.github.damontecres.wholphin.util.ApiRequestPager
import com.github.damontecres.wholphin.util.ExceptionHandler import com.github.damontecres.wholphin.util.ExceptionHandler
import com.github.damontecres.wholphin.util.GetItemsRequestHandler import com.github.damontecres.wholphin.util.GetItemsRequestHandler
import com.github.damontecres.wholphin.util.LoadingState import com.github.damontecres.wholphin.util.LoadingState
import com.github.damontecres.wholphin.util.WholphinDispatchers
import dagger.assisted.Assisted import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject import dagger.assisted.AssistedInject
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.CancellationException import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
@ -377,7 +377,7 @@ class SlideshowViewModel
), ),
) )
Timber.d("Saved VideoFilter for image %s", it.image.id) Timber.d("Saved VideoFilter for image %s", it.image.id)
withContext(Dispatchers.Main) { withContext(WholphinDispatchers.Main) {
showToast( showToast(
context, context,
"Saved", "Saved",
@ -406,7 +406,7 @@ class SlideshowViewModel
), ),
) )
Timber.d("Saved VideoFilter for album %s", slideshowSettings.parentId) Timber.d("Saved VideoFilter for album %s", slideshowSettings.parentId)
withContext(Dispatchers.Main) { withContext(WholphinDispatchers.Main) {
showToast( showToast(
context, context,
"Saved", "Saved",

View file

@ -8,7 +8,7 @@ import androidx.compose.runtime.compositionLocalOf
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import com.github.damontecres.wholphin.ui.getTimeFormatter import com.github.damontecres.wholphin.ui.getTimeFormatter
import kotlinx.coroutines.Dispatchers import com.github.damontecres.wholphin.util.WholphinDispatchers
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.isActive import kotlinx.coroutines.isActive
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
@ -34,7 +34,7 @@ data class Clock(
fun ProvideLocalClock(content: @Composable () -> Unit) { fun ProvideLocalClock(content: @Composable () -> Unit) {
val clock = remember { Clock() } val clock = remember { Clock() }
LaunchedEffect(Unit) { LaunchedEffect(Unit) {
withContext(Dispatchers.Default) { withContext(WholphinDispatchers.Default) {
while (isActive) { while (isActive) {
val now = LocalDateTime.now() val now = LocalDateTime.now()
val time = getTimeFormatter().format(now) val time = getTimeFormatter().format(now)

View file

@ -1,7 +1,6 @@
package com.github.damontecres.wholphin.util package com.github.damontecres.wholphin.util
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import org.jellyfin.sdk.api.client.ApiClient import org.jellyfin.sdk.api.client.ApiClient
import org.jellyfin.sdk.api.client.ApiClientFactory import org.jellyfin.sdk.api.client.ApiClientFactory
@ -21,7 +20,7 @@ import kotlin.coroutines.CoroutineContext
*/ */
class CoroutineContextApiClient( class CoroutineContextApiClient(
private val client: ApiClient, private val client: ApiClient,
private val coroutineContext: CoroutineContext = Dispatchers.IO, private val coroutineContext: CoroutineContext = WholphinDispatchers.IO,
) : ApiClient() { ) : ApiClient() {
override val baseUrl: String? override val baseUrl: String?
get() = client.baseUrl get() = client.baseUrl
@ -65,7 +64,7 @@ class CoroutineContextApiClient(
class CoroutineContextApiClientFactory( class CoroutineContextApiClientFactory(
private val factory: OkHttpFactory, private val factory: OkHttpFactory,
private val coroutineContext: CoroutineContext = Dispatchers.IO, private val coroutineContext: CoroutineContext = WholphinDispatchers.IO,
) : ApiClientFactory, ) : ApiClientFactory,
SocketConnectionFactory { SocketConnectionFactory {
override fun create( override fun create(

View file

@ -7,7 +7,6 @@ import androidx.compose.runtime.setValue
import com.github.damontecres.wholphin.ui.DEFAULT_PAGE_SIZE import com.github.damontecres.wholphin.ui.DEFAULT_PAGE_SIZE
import com.google.common.cache.CacheBuilder import com.google.common.cache.CacheBuilder
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.Mutex
@ -85,7 +84,7 @@ abstract class RequestPager<T>(
get() = totalCount get() = totalCount
private fun fetchPage(position: Int): Job = private fun fetchPage(position: Int): Job =
scope.launch(ExceptionHandler() + Dispatchers.IO) { scope.launch(ExceptionHandler() + WholphinDispatchers.IO) {
fetchPageBlocking(position, false) fetchPageBlocking(position, false)
} }

View file

@ -7,7 +7,6 @@ import com.github.damontecres.wholphin.data.model.ItemPlayback
import com.github.damontecres.wholphin.ui.launchIO import com.github.damontecres.wholphin.ui.launchIO
import com.github.damontecres.wholphin.ui.playback.CurrentPlayback import com.github.damontecres.wholphin.ui.playback.CurrentPlayback
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import org.jellyfin.sdk.api.client.ApiClient import org.jellyfin.sdk.api.client.ApiClient
import org.jellyfin.sdk.api.client.extensions.playStateApi import org.jellyfin.sdk.api.client.extensions.playStateApi
@ -34,7 +33,7 @@ class TrackActivityPlaybackListener(
private val player: Player, private val player: Player,
private val getState: () -> PlaybackItemState?, private val getState: () -> PlaybackItemState?,
) : Player.Listener { ) : Player.Listener {
private val coroutineScope = CoroutineScope(Dispatchers.Main) private val coroutineScope = CoroutineScope(WholphinDispatchers.Main)
private val task: TimerTask = private val task: TimerTask =
object : TimerTask() { object : TimerTask() {
override fun run() { override fun run() {
@ -57,7 +56,7 @@ class TrackActivityPlaybackListener(
PlaybackStartInfo( PlaybackStartInfo(
canSeek = true, canSeek = true,
itemId = state.itemId, itemId = state.itemId,
isPaused = withContext(Dispatchers.Main) { !player.isPlaying }, isPaused = withContext(WholphinDispatchers.Main) { !player.isPlaying },
playMethod = state.playMethod, playMethod = state.playMethod,
repeatMode = RepeatMode.REPEAT_NONE, repeatMode = RepeatMode.REPEAT_NONE,
playbackOrder = PlaybackOrder.DEFAULT, playbackOrder = PlaybackOrder.DEFAULT,
@ -117,11 +116,11 @@ class TrackActivityPlaybackListener(
launch("saveActivity") { launch("saveActivity") {
getState.invoke()?.let { state -> getState.invoke()?.let { state ->
val calcPosition = val calcPosition =
withContext(Dispatchers.Main) { withContext(WholphinDispatchers.Main) {
(if (position >= 0) position else player.currentPosition) (if (position >= 0) position else player.currentPosition)
} }
if (calcPosition > 0) { if (calcPosition > 0) {
val isPaused = withContext(Dispatchers.Main) { !player.isPlaying } val isPaused = withContext(WholphinDispatchers.Main) { !player.isPlaying }
Timber.v("saveActivity: itemId=${state.itemId}, pos=$calcPosition") Timber.v("saveActivity: itemId=${state.itemId}, pos=$calcPosition")
api.playStateApi.reportPlaybackProgress( api.playStateApi.reportPlaybackProgress(
PlaybackProgressInfo( PlaybackProgressInfo(

View file

@ -0,0 +1,20 @@
package com.github.damontecres.wholphin.util
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.MainCoroutineDispatcher
/**
* Analogous to [kotlinx.coroutines.Dispatchers], but easier to swap for testing
*/
object WholphinDispatchers {
@Suppress("ktlint:standard:property-naming")
val Main: MainCoroutineDispatcher get() = kotlinx.coroutines.Dispatchers.Main
@Suppress("ktlint:standard:property-naming")
var IO: CoroutineDispatcher = kotlinx.coroutines.Dispatchers.IO
internal set
@Suppress("ktlint:standard:property-naming")
var Default: CoroutineDispatcher = kotlinx.coroutines.Dispatchers.Default
internal set
}

View file

@ -8,17 +8,17 @@ import androidx.lifecycle.LifecycleRegistry
import androidx.work.WorkManager import androidx.work.WorkManager
import com.github.damontecres.wholphin.data.CurrentUser import com.github.damontecres.wholphin.data.CurrentUser
import com.github.damontecres.wholphin.data.ServerRepository import com.github.damontecres.wholphin.data.ServerRepository
import com.github.damontecres.wholphin.util.WholphinDispatchers
import com.github.damontecres.wholphin.util.configure
import com.github.damontecres.wholphin.util.reset
import io.mockk.every import io.mockk.every
import io.mockk.mockk import io.mockk.mockk
import io.mockk.verify import io.mockk.verify
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.test.StandardTestDispatcher import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.advanceUntilIdle import kotlinx.coroutines.test.advanceUntilIdle
import kotlinx.coroutines.test.resetMain
import kotlinx.coroutines.test.runTest import kotlinx.coroutines.test.runTest
import kotlinx.coroutines.test.setMain
import org.junit.After import org.junit.After
import org.junit.Before import org.junit.Before
import org.junit.Rule import org.junit.Rule
@ -36,14 +36,14 @@ class LatestNextUpSchedulerServiceTest {
@Before @Before
fun setUp() { fun setUp() {
Dispatchers.setMain(testDispatcher) WholphinDispatchers.configure(testDispatcher)
every { mockActivity.lifecycle } returns lifecycleRegistry every { mockActivity.lifecycle } returns lifecycleRegistry
every { mockServerRepository.current } returns currentUser every { mockServerRepository.current } returns currentUser
lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_RESUME) lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_RESUME)
} }
@After @After
fun tearDown() = Dispatchers.resetMain() fun tearDown() = WholphinDispatchers.reset()
private fun createService() = private fun createService() =
LatestNextUpSchedulerService( LatestNextUpSchedulerService(
@ -56,7 +56,7 @@ class LatestNextUpSchedulerServiceTest {
@Test @Test
fun cancels_latestNextUp_work_when_user_null() = fun cancels_latestNextUp_work_when_user_null() =
runTest { runTest(testDispatcher) {
createService() createService()
currentUser.value = null currentUser.value = null

View file

@ -5,15 +5,15 @@
package com.github.damontecres.wholphin.services package com.github.damontecres.wholphin.services
import com.github.damontecres.wholphin.util.WholphinDispatchers
import com.github.damontecres.wholphin.util.configure
import com.github.damontecres.wholphin.util.reset
import io.mockk.coEvery import io.mockk.coEvery
import io.mockk.every import io.mockk.every
import io.mockk.mockk import io.mockk.mockk
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.StandardTestDispatcher import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.resetMain
import kotlinx.coroutines.test.runTest import kotlinx.coroutines.test.runTest
import kotlinx.coroutines.test.setMain
import kotlinx.serialization.UseSerializers import kotlinx.serialization.UseSerializers
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import org.jellyfin.sdk.api.client.ApiClient import org.jellyfin.sdk.api.client.ApiClient
@ -56,14 +56,14 @@ class LatestNextUpServiceTests {
@OptIn(ExperimentalCoroutinesApi::class) @OptIn(ExperimentalCoroutinesApi::class)
@Before @Before
fun setUp() { fun setUp() {
Dispatchers.setMain(testDispatcher) WholphinDispatchers.configure(testDispatcher)
every { mockApi.tvShowsApi } returns mockTvShowsApi every { mockApi.tvShowsApi } returns mockTvShowsApi
} }
@OptIn(ExperimentalCoroutinesApi::class) @OptIn(ExperimentalCoroutinesApi::class)
@After @After
fun tearDown() { fun tearDown() {
Dispatchers.resetMain() WholphinDispatchers.reset()
} }
@Test @Test

View file

@ -8,19 +8,19 @@ import androidx.work.WorkManager
import com.github.damontecres.wholphin.data.ServerRepository import com.github.damontecres.wholphin.data.ServerRepository
import com.github.damontecres.wholphin.data.model.JellyfinUser import com.github.damontecres.wholphin.data.model.JellyfinUser
import com.github.damontecres.wholphin.util.GetItemsRequestHandler import com.github.damontecres.wholphin.util.GetItemsRequestHandler
import com.github.damontecres.wholphin.util.WholphinDispatchers
import com.github.damontecres.wholphin.util.configure
import com.github.damontecres.wholphin.util.reset
import io.mockk.coEvery import io.mockk.coEvery
import io.mockk.every import io.mockk.every
import io.mockk.mockk import io.mockk.mockk
import io.mockk.verify import io.mockk.verify
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.test.StandardTestDispatcher import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.resetMain
import kotlinx.coroutines.test.runTest import kotlinx.coroutines.test.runTest
import kotlinx.coroutines.test.setMain
import org.jellyfin.sdk.api.client.ApiClient import org.jellyfin.sdk.api.client.ApiClient
import org.jellyfin.sdk.api.client.Response import org.jellyfin.sdk.api.client.Response
import org.jellyfin.sdk.model.api.BaseItemDto import org.jellyfin.sdk.model.api.BaseItemDto
@ -48,12 +48,12 @@ class SuggestionServiceTest {
@Before @Before
fun setUp() { fun setUp() {
Dispatchers.setMain(testDispatcher) WholphinDispatchers.configure(testDispatcher)
} }
@After @After
fun tearDown() { fun tearDown() {
Dispatchers.resetMain() WholphinDispatchers.reset()
io.mockk.unmockkObject(GetItemsRequestHandler) io.mockk.unmockkObject(GetItemsRequestHandler)
} }

View file

@ -11,19 +11,19 @@ import com.github.damontecres.wholphin.data.CurrentUser
import com.github.damontecres.wholphin.data.ServerRepository import com.github.damontecres.wholphin.data.ServerRepository
import com.github.damontecres.wholphin.data.model.JellyfinServer import com.github.damontecres.wholphin.data.model.JellyfinServer
import com.github.damontecres.wholphin.data.model.JellyfinUser import com.github.damontecres.wholphin.data.model.JellyfinUser
import com.github.damontecres.wholphin.util.WholphinDispatchers
import com.github.damontecres.wholphin.util.configure
import com.github.damontecres.wholphin.util.reset
import com.google.common.util.concurrent.ListenableFuture import com.google.common.util.concurrent.ListenableFuture
import io.mockk.every import io.mockk.every
import io.mockk.mockk import io.mockk.mockk
import io.mockk.slot import io.mockk.slot
import io.mockk.verify import io.mockk.verify
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.test.StandardTestDispatcher import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.advanceUntilIdle import kotlinx.coroutines.test.advanceUntilIdle
import kotlinx.coroutines.test.resetMain
import kotlinx.coroutines.test.runTest import kotlinx.coroutines.test.runTest
import kotlinx.coroutines.test.setMain
import org.junit.After import org.junit.After
import org.junit.Assert.assertEquals import org.junit.Assert.assertEquals
import org.junit.Before import org.junit.Before
@ -43,14 +43,14 @@ class SuggestionsSchedulerServiceTest {
@Before @Before
fun setUp() { fun setUp() {
Dispatchers.setMain(testDispatcher) WholphinDispatchers.configure(testDispatcher)
every { mockActivity.lifecycle } returns lifecycleRegistry every { mockActivity.lifecycle } returns lifecycleRegistry
every { mockServerRepository.current } returns currentUser every { mockServerRepository.current } returns currentUser
lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_RESUME) lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_RESUME)
} }
@After @After
fun tearDown() = Dispatchers.resetMain() fun tearDown() = WholphinDispatchers.reset()
private fun createService() = private fun createService() =
SuggestionsSchedulerService( SuggestionsSchedulerService(

View file

@ -27,18 +27,17 @@ import com.github.damontecres.wholphin.ui.setup.SwitchServerContent
import com.github.damontecres.wholphin.ui.setup.SwitchServerViewModel import com.github.damontecres.wholphin.ui.setup.SwitchServerViewModel
import com.github.damontecres.wholphin.ui.theme.WholphinTheme import com.github.damontecres.wholphin.ui.theme.WholphinTheme
import com.github.damontecres.wholphin.util.LoadingState import com.github.damontecres.wholphin.util.LoadingState
import com.github.damontecres.wholphin.util.WholphinDispatchers
import com.github.damontecres.wholphin.util.configure
import com.github.damontecres.wholphin.util.reset
import dagger.hilt.android.testing.HiltAndroidRule import dagger.hilt.android.testing.HiltAndroidRule
import dagger.hilt.android.testing.HiltAndroidTest import dagger.hilt.android.testing.HiltAndroidTest
import dagger.hilt.android.testing.HiltTestApplication import dagger.hilt.android.testing.HiltTestApplication
import io.mockk.coEvery import io.mockk.coEvery
import io.mockk.every import io.mockk.every
import io.mockk.mockk import io.mockk.mockk
import io.mockk.mockkStatic
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.test.resetMain
import kotlinx.coroutines.test.setMain
import org.jellyfin.sdk.Jellyfin import org.jellyfin.sdk.Jellyfin
import org.jellyfin.sdk.api.client.ApiClient import org.jellyfin.sdk.api.client.ApiClient
import org.jellyfin.sdk.api.client.extensions.quickConnectApi import org.jellyfin.sdk.api.client.extensions.quickConnectApi
@ -105,11 +104,7 @@ class BasicUiTests {
@OptIn(ExperimentalCoroutinesApi::class) @OptIn(ExperimentalCoroutinesApi::class)
@Before @Before
fun setup() { fun setup() {
Dispatchers.setMain(TestModule.testDispatcher) WholphinDispatchers.configure(TestModule.testDispatcher)
mockkStatic(Dispatchers::class)
every { Dispatchers.IO } returns TestModule.testDispatcher
every { Dispatchers.Default } returns TestModule.testDispatcher
hiltRule.inject() hiltRule.inject()
screensaverService = mockk(relaxed = true) screensaverService = mockk(relaxed = true)
@ -130,7 +125,7 @@ class BasicUiTests {
@OptIn(ExperimentalCoroutinesApi::class) @OptIn(ExperimentalCoroutinesApi::class)
@After @After
fun tearDown() { fun tearDown() {
Dispatchers.resetMain() WholphinDispatchers.reset()
} }
/** /**

View file

@ -36,6 +36,7 @@ import com.github.damontecres.wholphin.services.hilt.IoCoroutineScope
import com.github.damontecres.wholphin.services.hilt.IoDispatcher import com.github.damontecres.wholphin.services.hilt.IoDispatcher
import com.github.damontecres.wholphin.services.hilt.StandardOkHttpClient import com.github.damontecres.wholphin.services.hilt.StandardOkHttpClient
import com.github.damontecres.wholphin.util.CoroutineContextApiClientFactory import com.github.damontecres.wholphin.util.CoroutineContextApiClientFactory
import com.github.damontecres.wholphin.util.WholphinDispatchers
import dagger.Module import dagger.Module
import dagger.Provides import dagger.Provides
import dagger.hilt.InstallIn import dagger.hilt.InstallIn
@ -46,7 +47,6 @@ import io.mockk.every
import io.mockk.mockk import io.mockk.mockk
import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.asExecutor import kotlinx.coroutines.asExecutor
import kotlinx.coroutines.test.StandardTestDispatcher import kotlinx.coroutines.test.StandardTestDispatcher
@ -225,7 +225,7 @@ object TestDatabaseModule {
.allowMainThreadQueries() .allowMainThreadQueries()
.setQueryCallback({ sqlQuery, args -> .setQueryCallback({ sqlQuery, args ->
Timber.v("sqlQuery=$sqlQuery, args=$args") Timber.v("sqlQuery=$sqlQuery, args=$args")
}, Dispatchers.IO.asExecutor()) }, WholphinDispatchers.IO.asExecutor())
.build() .build()
@Provides @Provides

View file

@ -0,0 +1,21 @@
package com.github.damontecres.wholphin.util
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.TestDispatcher
import kotlinx.coroutines.test.resetMain
import kotlinx.coroutines.test.setMain
@OptIn(ExperimentalCoroutinesApi::class)
fun WholphinDispatchers.configure(testDispatcher: TestDispatcher) {
Dispatchers.setMain(testDispatcher)
IO = testDispatcher
Default = testDispatcher
}
@OptIn(ExperimentalCoroutinesApi::class)
fun WholphinDispatchers.reset() {
Dispatchers.resetMain()
IO = Dispatchers.IO
Default = Dispatchers.Default
}