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.util.DebugLogTree
import com.github.damontecres.wholphin.util.ExceptionHandler
import com.github.damontecres.wholphin.util.WholphinDispatchers
import dagger.hilt.android.AndroidEntryPoint
import dagger.hilt.android.lifecycle.HiltViewModel
import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.first
@ -170,7 +170,7 @@ class MainActivity : AppCompatActivity() {
viewModel.serverRepository.currentUserFlow
.onEach { user ->
withContext(Dispatchers.Main) {
withContext(WholphinDispatchers.Main) {
if (user?.hasPin == true) {
window.addFlags(WindowManager.LayoutParams.FLAG_SECURE)
} else {
@ -184,7 +184,7 @@ class MainActivity : AppCompatActivity() {
screensaverService.keepScreenOn
.onEach { keepScreenOn ->
Timber.v("keepScreenOn: %s", keepScreenOn)
withContext(Dispatchers.Main) {
withContext(WholphinDispatchers.Main) {
if (keepScreenOn) {
window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
} else {
@ -383,7 +383,7 @@ class MainActivity : AppCompatActivity() {
}
fun changeDisplayMode(modeId: Int) {
lifecycleScope.launch(Dispatchers.Main + ExceptionHandler(autoToast = true)) {
lifecycleScope.launch(WholphinDispatchers.Main + ExceptionHandler(autoToast = true)) {
val attrs = window.attributes
if (attrs.preferredDisplayModeId != 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.preferences.UserPreferences
import com.github.damontecres.wholphin.services.StreamChoiceService
import kotlinx.coroutines.Dispatchers
import com.github.damontecres.wholphin.util.WholphinDispatchers
import kotlinx.coroutines.withContext
import org.jellyfin.sdk.model.api.MediaSourceInfo
import org.jellyfin.sdk.model.api.MediaStream
@ -108,7 +108,7 @@ class ItemPlaybackRepository
itemId: UUID,
sourceId: UUID,
): ItemPlayback? =
withContext(Dispatchers.IO) {
withContext(WholphinDispatchers.IO) {
serverRepository.currentUser?.let { user ->
val 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.services.hilt.IoDispatcher
import com.github.damontecres.wholphin.ui.toServerString
import com.github.damontecres.wholphin.util.WholphinDispatchers
import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
@ -109,7 +109,7 @@ class ServerRepository
}.build()
}
val currentUser = CurrentUser(updatedServer, updatedUser)
withContext(Dispatchers.Main) {
withContext(WholphinDispatchers.Main) {
_current.value = currentUser
_currentUserDto.value = userDto
}
@ -222,7 +222,7 @@ class ServerRepository
suspend fun removeUser(user: JellyfinUser) {
if (current.value?.user?.id == user.id) {
withContext(Dispatchers.Main) {
withContext(WholphinDispatchers.Main) {
_current.value = null
}
userPreferencesDataStore.updateData {
@ -241,7 +241,7 @@ class ServerRepository
suspend fun removeServer(server: JellyfinServer) {
if (current.value?.server?.id == server.id) {
withContext(Dispatchers.Main) {
withContext(WholphinDispatchers.Main) {
_current.value = null
}
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.preferences.AppPreferences
import com.github.damontecres.wholphin.preferences.BackdropStyle
import com.github.damontecres.wholphin.util.WholphinDispatchers
import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.FlowPreview
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableStateFlow
@ -55,7 +55,7 @@ class BackdropService
* Update the backdrop to use the specified item
*/
suspend fun submit(item: BaseItem) =
withContext(Dispatchers.IO) {
withContext(WholphinDispatchers.IO) {
val imageUrl =
if (item.type == BaseItemKind.GENRE) {
item.imageUrlOverride
@ -76,7 +76,7 @@ class BackdropService
suspend fun submit(
itemId: String,
imageUrl: String?,
) = withContext(Dispatchers.IO) {
) = withContext(WholphinDispatchers.IO) {
if (backdropFlow.firstOrNull()?.imageUrl != imageUrl) {
_backdropFlow.update {
it.copy(
@ -132,7 +132,7 @@ class BackdropService
}
suspend fun extractColorsFromBackdrop(imageUrl: String?): ExtractedColors =
withContext(Dispatchers.IO) {
withContext(WholphinDispatchers.IO) {
if (imageUrl.isNullOrBlank()) {
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.ui.collectLatestIn
import com.github.damontecres.wholphin.util.GetEpisodesRequestHandler
import com.github.damontecres.wholphin.util.WholphinDispatchers
import com.google.common.cache.CacheBuilder
import dagger.hilt.android.qualifiers.ActivityContext
import dagger.hilt.android.scopes.ActivityScoped
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Deferred
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async
import kotlinx.coroutines.withContext
import org.jellyfin.sdk.api.client.ApiClient
@ -56,7 +56,7 @@ class DatePlayedService
adjacentTo = item.id,
limit = 1,
)
return scope.async(Dispatchers.IO) {
return scope.async(WholphinDispatchers.IO) {
val premiereDate = item.data.premiereDate
try {
val result =
@ -92,7 +92,7 @@ class DatePlayedService
* the previous episode's last played timestamp, and the episode's premiere date
*/
suspend fun getLastPlayed(item: BaseItem): LocalDateTime? =
withContext(Dispatchers.IO) {
withContext(WholphinDispatchers.IO) {
val seriesId = item.data.seriesId
return@withContext if (seriesId != null) {
datePlayedCache

View file

@ -3,10 +3,10 @@ package com.github.damontecres.wholphin.services
import android.content.Context
import com.github.damontecres.wholphin.preferences.AssPlaybackMode
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.createDeviceProfile
import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import kotlinx.coroutines.withContext
@ -37,7 +37,7 @@ class DeviceProfileService
prefs: PlaybackPreferences,
serverVersion: ServerVersion?,
): DeviceProfile =
withContext(Dispatchers.Default) {
withContext(WholphinDispatchers.Default) {
mutex.withLock {
val newConfig =
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.ui.SlimItemFields
import com.github.damontecres.wholphin.util.LocalDateTimeSerializer
import com.github.damontecres.wholphin.util.WholphinDispatchers
import com.github.damontecres.wholphin.util.supportItemKinds
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.sync.Semaphore
@ -143,14 +143,14 @@ class LatestNextUpService
resume: List<BaseItem>,
nextUp: List<BaseItem>,
): List<BaseItem> =
withContext(Dispatchers.IO) {
withContext(WholphinDispatchers.IO) {
val start = System.currentTimeMillis()
val semaphore = Semaphore(3)
val deferred =
nextUp
.filter { it.data.seriesId != null }
.map { item ->
async(Dispatchers.IO) {
async(WholphinDispatchers.IO) {
try {
semaphore.withPermit {
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.ui.collectLatestIn
import com.github.damontecres.wholphin.util.ExceptionHandler
import com.github.damontecres.wholphin.util.WholphinDispatchers
import dagger.assisted.Assisted
import dagger.assisted.AssistedInject
import dagger.hilt.android.qualifiers.ActivityContext
import dagger.hilt.android.scopes.ActivityScoped
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import org.jellyfin.sdk.api.client.ApiClient
import org.jellyfin.sdk.model.serializer.toUUIDOrNull
@ -92,7 +92,7 @@ class LatestNextUpSchedulerService
)
// Exposed for testing
internal var dispatcher: CoroutineDispatcher = Dispatchers.IO
internal var dispatcher: CoroutineDispatcher = WholphinDispatchers.IO
init {
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.PlaybackItemState
import com.github.damontecres.wholphin.util.TrackActivityPlaybackListener
import com.github.damontecres.wholphin.util.WholphinDispatchers
import com.github.damontecres.wholphin.util.profile.supportedAudioCodecs
import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
@ -177,9 +177,9 @@ class MusicService
items: BlockingList<BaseItem?>,
startIndex: Int,
shuffled: Boolean,
) = withContext(Dispatchers.IO) {
) = withContext(WholphinDispatchers.IO) {
Timber.d("setQueue: %s items, startIndex=%s, shuffled=%s", items.size, startIndex, shuffled)
withContext(Dispatchers.Main) {
withContext(WholphinDispatchers.Main) {
player.setMediaItems(emptyList())
player.shuffleModeEnabled = shuffled
}
@ -199,7 +199,7 @@ class MusicService
items
.filter { it.type == BaseItemKind.AUDIO }
.map(::convert)
withContext(Dispatchers.Main) {
withContext(WholphinDispatchers.Main) {
player.setMediaItems(mediaItems)
player.shuffleModeEnabled = shuffled
updateQueueSize()
@ -216,7 +216,7 @@ class MusicService
) {
if (item.type == BaseItemKind.AUDIO) {
val mediaItem = convert(item)
withContext(Dispatchers.Main) {
withContext(WholphinDispatchers.Main) {
if (index != null) {
player.addMediaItem(index, mediaItem)
} else {
@ -293,7 +293,7 @@ class MusicService
*/
private suspend fun updateQueueSize() {
// val ids =
// withContext(Dispatchers.Default) {
// withContext(WholphinDispatchers.Default) {
// (0..<player.mediaItemCount).map { player.getMediaItemAt(it).mediaId.toUUID() }
// }
val timeline = onMain { player.currentTimeline }
@ -304,7 +304,7 @@ class MusicService
timeline.getWindow(it, window)
window.mediaItem.mediaId.toUUID()
}.toSet()
withContext(Dispatchers.Main) {
withContext(WholphinDispatchers.Main) {
_state.update {
it.copy(
queueVersion = it.queueVersion + 1,
@ -321,7 +321,7 @@ class MusicService
suspend fun moveQueue(
index: Int,
direction: MoveDirection,
) = withContext(Dispatchers.Main) {
) = withContext(WholphinDispatchers.Main) {
player.moveMediaItem(index, if (direction == MoveDirection.UP) index - 1 else index + 1)
updateQueueSize()
}
@ -332,7 +332,7 @@ class MusicService
suspend fun moveQueue(
index: Int,
newIndex: Int,
) = withContext(Dispatchers.Main) {
) = withContext(WholphinDispatchers.Main) {
player.moveMediaItem(index, newIndex)
updateQueueSize()
}
@ -385,7 +385,7 @@ class MusicService
.subscribe<PlaystateMessage>()
.onEach { message ->
message.data?.let {
withContext(Dispatchers.Main) {
withContext(WholphinDispatchers.Main) {
when (it.command) {
PlaystateCommand.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.ServerNavDrawerItem
import com.github.damontecres.wholphin.ui.showToast
import com.github.damontecres.wholphin.util.WholphinDispatchers
import com.github.damontecres.wholphin.util.supportedCollectionTypes
import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
@ -209,7 +209,7 @@ class NavDrawerService
val allItems = builtins + libraries
val navDrawerPins =
withContext(Dispatchers.IO) {
withContext(WholphinDispatchers.IO) {
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.PlayerBackend
import com.github.damontecres.wholphin.services.hilt.AuthOkHttpClient
import com.github.damontecres.wholphin.util.WholphinDispatchers
import com.github.damontecres.wholphin.util.mpv.MpvPlayer
import dagger.hilt.android.qualifiers.ApplicationContext
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.parser.AssSubtitleParserFactory
import io.github.peerless2012.ass.media.type.AssRenderType
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import okhttp3.OkHttpClient
import timber.log.Timber
@ -62,7 +62,7 @@ class PlayerFactory
backend: PlayerBackend,
prefs: PlaybackPreferences,
): PlayerCreation {
withContext(Dispatchers.Main) {
withContext(WholphinDispatchers.Main) {
if (currentPlayer?.isReleased == false) {
Timber.w("Player was not released before trying to create a new one!")
currentPlayer?.release()
@ -138,7 +138,7 @@ class PlayerFactory
.build()
.apply {
assHandler?.init(this)
withContext(Dispatchers.Main) {
withContext(WholphinDispatchers.Main) {
setAudioAttributes(
AudioAttributes
.Builder()

View file

@ -8,9 +8,9 @@ import android.os.Looper
import android.view.Display
import com.github.damontecres.wholphin.MainActivity
import com.github.damontecres.wholphin.ui.showToast
import com.github.damontecres.wholphin.util.WholphinDispatchers
import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.withContext
import kotlinx.coroutines.withTimeoutOrNull
@ -38,7 +38,7 @@ class RefreshRateService
stream: MediaStream,
switchRefreshRate: Boolean,
switchResolution: Boolean,
) = withContext(Dispatchers.IO) {
) = withContext(WholphinDispatchers.IO) {
if (!switchRefreshRate && !switchResolution) {
Timber.v("Not switching either refresh rate nor resolution")
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.ServerRepository
import com.github.damontecres.wholphin.data.model.RememberedTab
import kotlinx.coroutines.Dispatchers
import com.github.damontecres.wholphin.util.WholphinDispatchers
import kotlinx.coroutines.withContext
import javax.inject.Inject
import javax.inject.Singleton
@ -16,7 +16,7 @@ class RememberedTabService
private val rememberedTabDao: RememberedTabDao,
) {
suspend fun getRememberedTab(itemId: String): Int? =
withContext(Dispatchers.IO) {
withContext(WholphinDispatchers.IO) {
serverRepository.currentUser?.rowId?.let { userId ->
rememberedTabDao.getRememberedTab(userId, itemId)?.index
}
@ -26,7 +26,7 @@ class RememberedTabService
itemId: String,
tabIndex: Int,
): Unit =
withContext(Dispatchers.IO) {
withContext(WholphinDispatchers.IO) {
serverRepository.currentUser?.rowId?.let { userId ->
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.ExceptionHandler
import com.github.damontecres.wholphin.util.GetItemsRequestHandler
import com.github.damontecres.wholphin.util.WholphinDispatchers
import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
@ -224,7 +224,7 @@ class ScreensaverService
if (index > pager.lastIndex) index = 0
}
}
}.flowOn(Dispatchers.Default).cancellable()
}.flowOn(WholphinDispatchers.Default).cancellable()
private suspend fun createPager(): ApiRequestPager<GetItemsRequest> {
val prefs =

View file

@ -4,11 +4,11 @@ package com.github.damontecres.wholphin.services
import android.content.Context
import com.github.damontecres.wholphin.ui.toServerString
import com.github.damontecres.wholphin.util.WholphinDispatchers
import com.mayakapps.kache.InMemoryKache
import com.mayakapps.kache.ObjectKache
import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import kotlinx.serialization.ExperimentalSerializationApi
@ -43,7 +43,7 @@ class SuggestionsCache
private val memoryCache =
InMemoryKache<String, CachedSuggestions>(maxSize = 8) {
creationScope = CoroutineScope(Dispatchers.IO)
creationScope = CoroutineScope(WholphinDispatchers.IO)
}
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.AppPreferences
import com.github.damontecres.wholphin.util.GetItemsRequestHandler
import com.github.damontecres.wholphin.util.WholphinDispatchers
import dagger.assisted.Assisted
import dagger.assisted.AssistedInject
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.coroutineScope
@ -81,7 +81,7 @@ class SuggestionsWorker
.takeIf { it > 0 }
?: AppPreference.HomePageItems.defaultValue.toInt()
val context = Dispatchers.IO.limitedParallelism(2, "fetchSuggestions")
val context = WholphinDispatchers.IO.limitedParallelism(2, "fetchSuggestions")
if (requestedParentId != null && requestedItemKind != null) {
Timber.d(
"Fetching on-demand suggestions for parent=%s kind=%s",
@ -128,7 +128,7 @@ class SuggestionsWorker
supervisorScope {
supportedViews
.map { view ->
async(Dispatchers.IO) {
async(WholphinDispatchers.IO) {
runCatching {
Timber.v("Fetching suggestions for parent=%s kind=%s", view.id, view.itemKind)
fetchAndCacheSuggestions(
@ -293,7 +293,7 @@ class SuggestionsWorker
limit = freshLimit,
)
}
withContext(Dispatchers.Default) {
withContext(WholphinDispatchers.Default) {
val contextual = contextualDeferred.await()
val random = randomDeferred.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.IoCoroutineScope
import com.github.damontecres.wholphin.ui.launchIO
import com.github.damontecres.wholphin.util.WholphinDispatchers
import com.github.damontecres.wholphin.util.profile.Codec
import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.cancelAndJoin
import kotlinx.coroutines.flow.MutableStateFlow
@ -102,7 +102,7 @@ class ThemeSongPlayer
Codec.Audio.FLAC,
),
)
withContext(Dispatchers.Main) {
withContext(WholphinDispatchers.Main) {
player.apply {
stop()
volume = volumeLevel
@ -127,7 +127,7 @@ class ThemeSongPlayer
scope.launch {
mutex.withLock {
state.value.job?.cancelAndJoin()
withContext(Dispatchers.Main) {
withContext(WholphinDispatchers.Main) {
Timber.v("Stopping theme song")
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.showToast
import com.github.damontecres.wholphin.util.Version
import com.github.damontecres.wholphin.util.WholphinDispatchers
import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.Json
@ -131,7 +131,7 @@ class UpdateChecker
suspend fun getRelease(version: Version): Release? {
val url =
"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 =
Request
.Builder()
@ -146,7 +146,7 @@ class UpdateChecker
* Get the latest released version
*/
suspend fun getLatestRelease(updateUrl: String): Release? =
withContext(Dispatchers.IO) {
withContext(WholphinDispatchers.IO) {
val request =
Request
.Builder()
@ -199,7 +199,7 @@ class UpdateChecker
release: Release,
callback: DownloadCallback,
) {
withContext(Dispatchers.IO) {
withContext(WholphinDispatchers.IO) {
cleanup()
val request =
Request
@ -210,7 +210,7 @@ class UpdateChecker
okHttpClient.newCall(request).execute().use {
if (it.isSuccessful && it.body != null) {
Timber.v("Request successful for ${release.downloadUrl}")
withContext(Dispatchers.Main) {
withContext(WholphinDispatchers.Main) {
callback.contentLength(it.body.contentLength())
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
@ -396,7 +396,7 @@ suspend fun copyTo(
while (bytes >= 0) {
out.write(buffer, 0, bytes)
bytesCopied += bytes
withContext(Dispatchers.Main) {
withContext(WholphinDispatchers.Main) {
callback.bytesDownloaded(bytesCopied)
}
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.ui.launchDefault
import com.github.damontecres.wholphin.ui.launchIO
import com.github.damontecres.wholphin.util.WholphinDispatchers
import dagger.hilt.android.qualifiers.ActivityContext
import dagger.hilt.android.scopes.ActivityScoped
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.supervisorScope
import kotlinx.coroutines.withContext
@ -56,7 +56,7 @@ class UserSwitchListener
user.uiLanguage?.let { LocaleListCompat.forLanguageTags(it) }
?: LocaleListCompat.getEmptyLocaleList()
Timber.i("Switching locale to %s", localeList)
withContext(Dispatchers.Main) {
withContext(WholphinDispatchers.Main) {
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.services.SeerrApi
import com.github.damontecres.wholphin.util.CoroutineContextApiClientFactory
import com.github.damontecres.wholphin.util.WholphinDispatchers
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
@ -14,7 +15,6 @@ import dagger.hilt.android.qualifiers.ApplicationContext
import dagger.hilt.components.SingletonComponent
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import okhttp3.OkHttpClient
import org.jellyfin.sdk.Jellyfin
@ -42,21 +42,21 @@ annotation class AuthOkHttpClient
annotation class StandardOkHttpClient
/**
* A [CoroutineScope] with [Dispatchers.IO]
* A [CoroutineScope] with [WholphinDispatchers.IO]
*/
@Qualifier
@Retention(AnnotationRetention.BINARY)
annotation class IoCoroutineScope
/**
* A [CoroutineScope] with [Dispatchers.Default]
* A [CoroutineScope] with [WholphinDispatchers.Default]
*/
@Qualifier
@Retention(AnnotationRetention.BINARY)
annotation class DefaultCoroutineScope
/**
* [Dispatchers.IO]
* [WholphinDispatchers.IO]
*
* @see IoCoroutineScope
*/
@ -65,7 +65,7 @@ annotation class DefaultCoroutineScope
annotation class IoDispatcher
/**
* [Dispatchers.Default]
* [WholphinDispatchers.Default]
*
* @see DefaultCoroutineScope
*/
@ -156,7 +156,7 @@ object AppModule {
@Provides
@Singleton
@IoDispatcher
fun ioDispatcher(): CoroutineDispatcher = Dispatchers.IO
fun ioDispatcher(): CoroutineDispatcher = WholphinDispatchers.IO
@Provides
@Singleton
@ -168,7 +168,7 @@ object AppModule {
@Provides
@Singleton
@DefaultDispatcher
fun defaultDispatcher(): CoroutineDispatcher = Dispatchers.Default
fun defaultDispatcher(): CoroutineDispatcher = WholphinDispatchers.Default
@Provides
@Singleton

View file

@ -44,7 +44,7 @@ object DatabaseModule {
).addMigrations(Migrations.Migrate2to3)
// .setQueryCallback({ sqlQuery, args ->
// Timber.v("sqlQuery=$sqlQuery, args=$args")
// }, Dispatchers.IO.asExecutor())
// }, WholphinDispatchers.IO.asExecutor())
.build()
@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.RowColumnSaver
import com.github.damontecres.wholphin.util.ExceptionHandler
import com.github.damontecres.wholphin.util.WholphinDispatchers
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.CoroutineStart
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.collectLatest
@ -359,34 +359,34 @@ suspend fun showToast(
context: Context,
text: CharSequence,
duration: Int,
) = withContext(Dispatchers.Main) {
) = withContext(WholphinDispatchers.Main) {
Toast.makeText(context, text, duration).show()
}
suspend fun showToast(
context: Context,
text: CharSequence,
) = withContext(Dispatchers.Main) {
) = withContext(WholphinDispatchers.Main) {
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(
context: CoroutineContext = ExceptionHandler(),
start: CoroutineStart = CoroutineStart.DEFAULT,
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(
context: CoroutineContext = ExceptionHandler(),
start: CoroutineStart = CoroutineStart.DEFAULT,
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).
@ -410,7 +410,7 @@ fun logTab(
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) =
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.PreviewTvSpec
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.theme.WholphinTheme
import java.util.UUID

View file

@ -32,9 +32,7 @@ import coil3.request.ImageRequest
import coil3.request.crossfade
import com.github.damontecres.wholphin.ui.AspectRatios
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.isNotNullOrBlank
import com.github.damontecres.wholphin.ui.setup.rememberIdColor
import com.github.damontecres.wholphin.ui.theme.WholphinTheme
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.GetPersonsHandler
import com.github.damontecres.wholphin.util.LoadingState
import com.github.damontecres.wholphin.util.WholphinDispatchers
import com.github.damontecres.wholphin.util.successValue
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import dagger.hilt.android.lifecycle.HiltViewModel
import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.catch
@ -251,7 +251,7 @@ class CollectionFolderViewModel
fun saveViewOptions(viewOptions: ViewOptions) {
_state.update { it.copy(viewOptions = viewOptions) }
viewModelScope.launch(ExceptionHandler() + Dispatchers.IO) {
viewModelScope.launch(ExceptionHandler() + WholphinDispatchers.IO) {
saveLibraryDisplayInfo(viewOptions = viewOptions)
if (!viewOptions.showBackdrop) {
backdropService.clearBackdrop()
@ -295,7 +295,7 @@ class CollectionFolderViewModel
recursive: Boolean,
filter: GetItemsFilter,
useSeriesForPrimary: Boolean,
) = viewModelScope.launch(Dispatchers.IO) {
) = viewModelScope.launch(WholphinDispatchers.IO) {
_state.update {
it.copy(
items = DataLoadingState.Loading,
@ -437,7 +437,7 @@ class CollectionFolderViewModel
)
suspend fun positionOfLetter(letter: Char): Int? =
withContext(Dispatchers.IO) {
withContext(WholphinDispatchers.IO) {
val sort = state.value.sortAndDirection
val filter = state.value.filter
val request =
@ -461,7 +461,7 @@ class CollectionFolderViewModel
itemId: UUID,
played: Boolean,
) {
viewModelScope.launch(ExceptionHandler() + Dispatchers.IO) {
viewModelScope.launch(ExceptionHandler() + WholphinDispatchers.IO) {
favoriteWatchManager.setWatched(itemId, played)
(state.value.items as? DataLoadingState.Success)?.let {
(it.data as? ApiRequestPager<*>)?.refreshItem(position, itemId)
@ -474,7 +474,7 @@ class CollectionFolderViewModel
itemId: UUID,
favorite: Boolean,
) {
viewModelScope.launch(ExceptionHandler() + Dispatchers.IO) {
viewModelScope.launch(ExceptionHandler() + WholphinDispatchers.IO) {
favoriteWatchManager.setFavorite(itemId, favorite)
(state.value.items as? DataLoadingState.Success)?.let {
(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.GetGenresRequestHandler
import com.github.damontecres.wholphin.util.GetItemsRequestHandler
import com.github.damontecres.wholphin.util.WholphinDispatchers
import com.mayakapps.kache.InMemoryKache
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.flow.MutableStateFlow
@ -142,7 +142,7 @@ class GenreViewModel
}
suspend fun positionOfLetter(letter: Char): Int =
withContext(Dispatchers.IO) {
withContext(WholphinDispatchers.IO) {
val request =
GetGenresRequest(
parentId = itemId,
@ -197,7 +197,7 @@ suspend fun getGenreImageMap(
val semaphore = Semaphore(4)
genres
.map { genreId ->
scope.async(Dispatchers.IO) {
scope.async(WholphinDispatchers.IO) {
semaphore.withPermit {
val item =
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.ExceptionHandler
import com.github.damontecres.wholphin.util.RequestHandler
import com.github.damontecres.wholphin.util.WholphinDispatchers
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import dagger.hilt.android.lifecycle.HiltViewModel
import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.update
@ -150,7 +150,7 @@ class ItemGridViewModel
itemId: UUID,
played: Boolean,
) {
viewModelScope.launch(ExceptionHandler() + Dispatchers.IO) {
viewModelScope.launch(ExceptionHandler() + WholphinDispatchers.IO) {
favoriteWatchManager.setWatched(itemId, played)
(state.value.items as? DataLoadingState.Success)?.let {
(it.data as? ApiRequestPager<*>)?.refreshItem(position, itemId)
@ -163,7 +163,7 @@ class ItemGridViewModel
itemId: UUID,
favorite: Boolean,
) {
viewModelScope.launch(ExceptionHandler() + Dispatchers.IO) {
viewModelScope.launch(ExceptionHandler() + WholphinDispatchers.IO) {
favoriteWatchManager.setFavorite(itemId, favorite)
(state.value.items as? DataLoadingState.Success)?.let {
(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.LoadingState
import com.github.damontecres.wholphin.util.RequestHandler
import com.github.damontecres.wholphin.util.WholphinDispatchers
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import dagger.hilt.android.lifecycle.HiltViewModel
import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.first
@ -173,7 +173,7 @@ class RecommendedViewModel
}
private fun fetchSuggestions() {
viewModelScope.launch(Dispatchers.IO) {
viewModelScope.launch(WholphinDispatchers.IO) {
val limit =
userPreferencesService
.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.util.DataLoadingState
import com.github.damontecres.wholphin.util.GetStudiosRequestHandler
import com.github.damontecres.wholphin.util.WholphinDispatchers
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.update
@ -113,7 +113,7 @@ class StudioViewModel
}
suspend fun positionOfLetter(letter: Char): Int =
withContext(Dispatchers.IO) {
withContext(WholphinDispatchers.IO) {
val request =
GetStudiosRequest(
userId = serverRepository.currentUser?.id,

View file

@ -17,10 +17,10 @@ import android.speech.RecognizerIntent
import android.speech.SpeechRecognizer
import androidx.core.content.ContextCompat
import com.github.damontecres.wholphin.R
import com.github.damontecres.wholphin.util.WholphinDispatchers
import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.android.asCoroutineDispatcher
@ -175,7 +175,7 @@ class VoiceInputManager
private fun provideMainDispatcher(): CoroutineDispatcher =
try {
Dispatchers.Main.immediate
WholphinDispatchers.Main.immediate
} catch (_: IllegalStateException) {
// Fallback for unit tests where Main dispatcher is not installed
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.tryRequestFocus
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.withContext
import timber.log.Timber
@ -217,7 +217,7 @@ fun <T : CardGridItem> CardGrid(
{ letter: Char ->
scope.launch(ExceptionHandler()) {
val jumpPosition =
withContext(Dispatchers.IO) {
withContext(WholphinDispatchers.IO) {
letterPosition.invoke(letter)
}
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.ExceptionHandler
import com.github.damontecres.wholphin.util.HomeRowLoadingState
import com.github.damontecres.wholphin.util.WholphinDispatchers
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import dagger.hilt.android.lifecycle.HiltViewModel
import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.update
@ -166,7 +166,7 @@ class HomeRowGridViewModel
itemId: UUID,
played: Boolean,
) {
viewModelScope.launch(ExceptionHandler() + Dispatchers.IO) {
viewModelScope.launch(ExceptionHandler() + WholphinDispatchers.IO) {
favoriteWatchManager.setWatched(itemId, played)
(state.value.loading as? HomeRowLoadingState.Success)?.let {
(it.items as? ApiRequestPager<*>)?.refreshItem(position, itemId)
@ -179,7 +179,7 @@ class HomeRowGridViewModel
itemId: UUID,
favorite: Boolean,
) {
viewModelScope.launch(ExceptionHandler() + Dispatchers.IO) {
viewModelScope.launch(ExceptionHandler() + WholphinDispatchers.IO) {
favoriteWatchManager.setFavorite(itemId, favorite)
(state.value.loading as? HomeRowLoadingState.Success)?.let {
(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.GetItemsRequestHandler
import com.github.damontecres.wholphin.util.LoadingState
import com.github.damontecres.wholphin.util.WholphinDispatchers
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import dagger.hilt.android.lifecycle.HiltViewModel
import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
@ -316,14 +316,14 @@ class PlaylistViewModel
fun setWatched(
itemId: UUID,
played: Boolean,
) = viewModelScope.launch(ExceptionHandler() + Dispatchers.IO) {
) = viewModelScope.launch(ExceptionHandler() + WholphinDispatchers.IO) {
favoriteWatchManager.setWatched(itemId, played)
}
fun setFavorite(
itemId: UUID,
favorite: Boolean,
) = viewModelScope.launch(ExceptionHandler() + Dispatchers.IO) {
) = viewModelScope.launch(ExceptionHandler() + WholphinDispatchers.IO) {
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.HomeRowLoadingState
import com.github.damontecres.wholphin.util.LoadingState
import com.github.damontecres.wholphin.util.WholphinDispatchers
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import dagger.hilt.android.lifecycle.HiltViewModel
import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.async
import kotlinx.coroutines.flow.MutableStateFlow
@ -210,7 +210,7 @@ class CollectionViewModel
supervisorScope {
val jobs =
typesInCollection.map { type ->
async(Dispatchers.IO) {
async(WholphinDispatchers.IO) {
val title = ResStringProvider(formatTypeName(type))
val result =
try {
@ -359,7 +359,7 @@ class CollectionViewModel
)
suspend fun letterPosition(letter: Char): Int =
withContext(Dispatchers.IO) {
withContext(WholphinDispatchers.IO) {
val sort = state.value.sortAndDirection
val filter = state.value.itemFilter
val request =
@ -387,7 +387,7 @@ class CollectionViewModel
itemId: UUID,
played: Boolean,
position: RowColumn?,
) = viewModelScope.launch(Dispatchers.IO + ExceptionHandler()) {
) = viewModelScope.launch(WholphinDispatchers.IO + ExceptionHandler()) {
favoriteWatchManager.setWatched(itemId, played)
if (itemId == state.value.collection?.id) {
refreshCollection()
@ -400,7 +400,7 @@ class CollectionViewModel
itemId: UUID,
favorite: Boolean,
position: RowColumn?,
) = viewModelScope.launch(ExceptionHandler() + Dispatchers.IO) {
) = viewModelScope.launch(ExceptionHandler() + WholphinDispatchers.IO) {
favoriteWatchManager.setFavorite(itemId, favorite)
if (itemId == state.value.collection?.id) {
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.showToast
import com.github.damontecres.wholphin.util.DataLoadingState
import com.github.damontecres.wholphin.util.WholphinDispatchers
import com.github.damontecres.wholphin.util.successValue
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
@ -31,7 +32,6 @@ import dagger.assisted.AssistedInject
import dagger.hilt.android.lifecycle.HiltViewModel
import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.Deferred
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.async
import kotlinx.coroutines.flow.MutableStateFlow
@ -73,7 +73,7 @@ class DiscoverMovieViewModel
}
private fun fetchAndSetItem(): Deferred<MovieDetails?> =
viewModelScope.async(Dispatchers.IO) {
viewModelScope.async(WholphinDispatchers.IO) {
try {
val movie = seerrService.api.moviesApi.movieMovieIdGet(movieId = item.id)
_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.showToast
import com.github.damontecres.wholphin.util.DataLoadingState
import com.github.damontecres.wholphin.util.WholphinDispatchers
import com.github.damontecres.wholphin.util.successValue
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
@ -30,7 +31,6 @@ import dagger.hilt.android.lifecycle.HiltViewModel
import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.Deferred
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.async
import kotlinx.coroutines.flow.MutableStateFlow
@ -71,7 +71,7 @@ class DiscoverSeriesViewModel
}
private fun fetchAndSetItem(): Deferred<TvDetails?> =
viewModelScope.async(Dispatchers.IO) {
viewModelScope.async(WholphinDispatchers.IO) {
try {
val tv = seerrService.api.tvApi.tvTvIdGet(tvId = item.id)
_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.util.DataLoadingState
import com.github.damontecres.wholphin.util.ExceptionHandler
import com.github.damontecres.wholphin.util.WholphinDispatchers
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import dagger.hilt.android.lifecycle.HiltViewModel
import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
@ -126,7 +126,7 @@ class EpisodeViewModel
fun setWatched(
itemId: UUID,
played: Boolean,
) = viewModelScope.launch(ExceptionHandler() + Dispatchers.IO) {
) = viewModelScope.launch(ExceptionHandler() + WholphinDispatchers.IO) {
favoriteWatchManager.setWatched(itemId, played)
fetchAndSetItem()
}
@ -134,7 +134,7 @@ class EpisodeViewModel
fun setFavorite(
itemId: UUID,
favorite: Boolean,
) = viewModelScope.launch(ExceptionHandler() + Dispatchers.IO) {
) = viewModelScope.launch(ExceptionHandler() + WholphinDispatchers.IO) {
favoriteWatchManager.setFavorite(itemId, favorite)
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.tryRequestFocus
import com.github.damontecres.wholphin.util.LoadingState
import com.github.damontecres.wholphin.util.WholphinDispatchers
import eu.wewox.programguide.ProgramGuide
import eu.wewox.programguide.ProgramGuideDimensions
import eu.wewox.programguide.ProgramGuideItem
import eu.wewox.programguide.rememberSaveableProgramGuideState
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import timber.log.Timber
@ -319,7 +319,7 @@ fun TvGuideGridContent(
var focusedProgramIndex by rememberInt(0)
LaunchedEffect(onFocus, focusedProgramIndex) {
withContext(Dispatchers.Default) {
withContext(WholphinDispatchers.Default) {
val program = programs.programs.getOrNull(focusedProgramIndex)
if (program != null) {
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.GetItemsRequestHandler
import com.github.damontecres.wholphin.util.LoadingState
import com.github.damontecres.wholphin.util.WholphinDispatchers
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import dagger.hilt.android.lifecycle.HiltViewModel
import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
@ -242,7 +242,7 @@ class AlbumViewModel
fun setFavorite(
itemId: UUID,
favorite: Boolean,
) = viewModelScope.launch(ExceptionHandler() + Dispatchers.IO) {
) = viewModelScope.launch(ExceptionHandler() + WholphinDispatchers.IO) {
favoriteWatchManager.setFavorite(itemId, favorite)
val album =
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.GetItemsRequestHandler
import com.github.damontecres.wholphin.util.LoadingState
import com.github.damontecres.wholphin.util.WholphinDispatchers
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import dagger.hilt.android.lifecycle.HiltViewModel
import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
@ -254,7 +254,7 @@ class ArtistViewModel
fun setFavorite(
itemId: UUID,
favorite: Boolean,
) = viewModelScope.launch(ExceptionHandler() + Dispatchers.IO) {
) = viewModelScope.launch(ExceptionHandler() + WholphinDispatchers.IO) {
favoriteWatchManager.setFavorite(itemId, favorite)
val artist =
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.PlaybackButton
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.theme.PreviewInteractionSource
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.onMain
import com.github.damontecres.wholphin.ui.playback.ControllerViewState
import com.github.damontecres.wholphin.util.WholphinDispatchers
import com.mayakapps.kache.InMemoryKache
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import dagger.hilt.android.lifecycle.HiltViewModel
import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableStateFlow
@ -95,7 +95,7 @@ class NowPlayingViewModel
private val lyricCache =
InMemoryKache<UUID, LyricDto>(20) {
creationScope = CoroutineScope(Dispatchers.IO)
creationScope = CoroutineScope(WholphinDispatchers.IO)
}
init {

View file

@ -24,7 +24,6 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.tv.material3.DenseListItem
import androidx.tv.material3.Icon
import androidx.tv.material3.ListItem
import androidx.tv.material3.ListItemDefaults
import androidx.tv.material3.LocalContentColor
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.GetEpisodesRequestHandler
import com.github.damontecres.wholphin.util.GetItemsRequestHandler
import com.github.damontecres.wholphin.util.WholphinDispatchers
import com.github.damontecres.wholphin.util.successValue
import com.google.common.cache.CacheBuilder
import dagger.assisted.Assisted
@ -50,7 +51,6 @@ import dagger.hilt.android.lifecycle.HiltViewModel
import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.Deferred
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.async
import kotlinx.coroutines.delay
@ -137,7 +137,7 @@ class SeriesViewModel
val episodeListDeferred =
if (seriesPageType == SeriesPageType.OVERVIEW) {
viewModelScope.async(Dispatchers.IO) {
viewModelScope.async(WholphinDispatchers.IO) {
if (seasonEpisodeIds != null) {
loadEpisodesInternal(
seasonEpisodeIds.seasonId,
@ -292,7 +292,7 @@ class SeriesViewModel
series: BaseItem,
seasonNum: Int?,
): Deferred<List<BaseItem?>> =
viewModelScope.async(Dispatchers.IO) {
viewModelScope.async(WholphinDispatchers.IO) {
Timber.v("getSeasons for %s", series.id)
val request =
GetItemsRequest(
@ -405,7 +405,7 @@ class SeriesViewModel
itemId: UUID,
played: Boolean,
listIndex: Int?,
) = viewModelScope.launch(Dispatchers.IO + ExceptionHandler()) {
) = viewModelScope.launch(WholphinDispatchers.IO + ExceptionHandler()) {
favoriteWatchManager.setWatched(itemId, played)
listIndex?.let {
refreshEpisode(itemId, listIndex)
@ -440,7 +440,7 @@ class SeriesViewModel
itemId: UUID,
favorite: Boolean,
listIndex: Int?,
) = viewModelScope.launch(ExceptionHandler() + Dispatchers.IO) {
) = viewModelScope.launch(ExceptionHandler() + WholphinDispatchers.IO) {
favoriteWatchManager.setFavorite(itemId, favorite)
if (listIndex != null) {
refreshEpisode(itemId, listIndex)
@ -452,13 +452,13 @@ class SeriesViewModel
fun setSeasonWatched(
seasonId: UUID,
played: Boolean,
) = viewModelScope.launch(Dispatchers.IO + ExceptionHandler()) {
) = viewModelScope.launch(WholphinDispatchers.IO + ExceptionHandler()) {
setWatched(seasonId, played, null)
updateSeries()
}
fun setWatchedSeries(played: Boolean) =
viewModelScope.launch(ExceptionHandler() + Dispatchers.IO) {
viewModelScope.launch(ExceptionHandler() + WholphinDispatchers.IO) {
favoriteWatchManager.setWatched(seriesId, played)
updateSeries()
}
@ -466,7 +466,7 @@ class SeriesViewModel
fun refreshEpisode(
itemId: UUID,
listIndex: Int,
) = viewModelScope.launch(ExceptionHandler() + Dispatchers.IO) {
) = viewModelScope.launch(ExceptionHandler() + WholphinDispatchers.IO) {
val eps = state.value.episodes
if (eps is EpisodeList.Success) {
eps.episodes.refreshItem(listIndex, itemId)
@ -481,7 +481,7 @@ class SeriesViewModel
* Play whichever episode is next up for series or else the first episode
*/
fun playNextUp() {
viewModelScope.launch(ExceptionHandler() + Dispatchers.IO) {
viewModelScope.launch(ExceptionHandler() + WholphinDispatchers.IO) {
val result by api.tvShowsApi.getNextUp(seriesId = seriesId)
val nextUp =
result.items.firstOrNull() ?: api.tvShowsApi
@ -491,7 +491,7 @@ class SeriesViewModel
).content.items
.firstOrNull()
if (nextUp != null) {
withContext(Dispatchers.Main) {
withContext(WholphinDispatchers.Main) {
navigateTo(Destination.Playback(BaseItem(nextUp)))
}
} else {
@ -582,7 +582,7 @@ class SeriesViewModel
val result =
peopleInEpisodeCache
.get(item.id) {
viewModelScope.async(Dispatchers.IO) {
viewModelScope.async(WholphinDispatchers.IO) {
val list =
api.userLibraryApi
.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.tryRequestFocus
import com.github.damontecres.wholphin.util.DataLoadingState
import com.github.damontecres.wholphin.util.WholphinDispatchers
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.flow.MutableStateFlow
@ -77,7 +77,7 @@ class SeerrRequestsViewModel
val requests =
mediaRequests.mapNotNull { request ->
if (request.media?.tmdbId != null) {
viewModelScope.async(Dispatchers.IO) {
viewModelScope.async(WholphinDispatchers.IO) {
semaphore.withPermit {
val type = SeerrItemType.fromString(request.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.HomeRowLoadingState
import com.github.damontecres.wholphin.util.LoadingState
import com.github.damontecres.wholphin.util.WholphinDispatchers
import dagger.hilt.android.lifecycle.HiltViewModel
import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.flow.MutableStateFlow
@ -117,7 +117,7 @@ class HomeViewModel
val deferred =
settings.rows
.map { row ->
viewModelScope.async(Dispatchers.IO) {
viewModelScope.async(WholphinDispatchers.IO) {
semaphore.withPermit {
Timber.v("Fetching row: %s", row)
try {
@ -220,9 +220,9 @@ class HomeViewModel
fun setWatched(
itemId: UUID,
played: Boolean,
) = viewModelScope.launch(ExceptionHandler() + Dispatchers.IO) {
) = viewModelScope.launch(ExceptionHandler() + WholphinDispatchers.IO) {
favoriteWatchManager.setWatched(itemId, played)
withContext(Dispatchers.Main) {
withContext(WholphinDispatchers.Main) {
init()
}
}
@ -230,9 +230,9 @@ class HomeViewModel
fun setFavorite(
itemId: UUID,
favorite: Boolean,
) = viewModelScope.launch(ExceptionHandler() + Dispatchers.IO) {
) = viewModelScope.launch(ExceptionHandler() + WholphinDispatchers.IO) {
favoriteWatchManager.setFavorite(itemId, favorite)
withContext(Dispatchers.Main) {
withContext(WholphinDispatchers.Main) {
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.util.ExceptionHandler
import com.github.damontecres.wholphin.util.SearchRelevance
import com.github.damontecres.wholphin.util.WholphinDispatchers
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
@ -253,7 +253,7 @@ class SearchViewModel
}
private fun searchCombined(query: String) {
viewModelScope.launch(ExceptionHandler() + Dispatchers.IO) {
viewModelScope.launch(ExceptionHandler() + WholphinDispatchers.IO) {
try {
val request =
GetItemsRequest(
@ -477,7 +477,7 @@ fun SearchPage(
) {
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
val results =
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.util.HomeRowLoadingState
import com.github.damontecres.wholphin.util.LoadingState
import com.github.damontecres.wholphin.util.WholphinDispatchers
import dagger.hilt.android.lifecycle.HiltViewModel
import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll
@ -129,7 +129,7 @@ class HomeSettingsViewModel
.let { state ->
state.rows
.map { row ->
viewModelScope.async(Dispatchers.IO) {
viewModelScope.async(WholphinDispatchers.IO) {
semaphore.withPermit {
try {
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.PlaybackItemState
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.mpv.MpvPlayer
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.qualifiers.ApplicationContext
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableStateFlow
@ -242,7 +242,7 @@ class PlaybackViewModel
Timber.d("Selected backend: %s", playerBackend)
if (currentPlayer.value?.backend != playerBackend) {
Timber.i("Switching player backend to %s", playerBackend)
withContext(Dispatchers.Main) {
withContext(WholphinDispatchers.Main) {
disconnectPlayer()
}
@ -421,7 +421,7 @@ class PlaybackViewModel
positionMs: Long,
forceTranscoding: Boolean = this.forceTranscoding,
): Boolean =
withContext(Dispatchers.IO) {
withContext(WholphinDispatchers.IO) {
val item =
when (playlistItem) {
is PlaylistItem.Intro -> playlistItem.item
@ -581,7 +581,7 @@ class PlaybackViewModel
trickPlayInfo = trickPlayInfo,
)
}
withContext(Dispatchers.Main) {
withContext(WholphinDispatchers.Main) {
changeStreams(
item,
itemPlaybackToUse,
@ -612,7 +612,7 @@ class PlaybackViewModel
userInitiated: Boolean,
enableDirectPlay: Boolean = !this.forceTranscoding,
enableDirectStream: Boolean = !this.forceTranscoding,
) = withContext(Dispatchers.IO) {
) = withContext(WholphinDispatchers.IO) {
val itemId = item.id
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
this@PlaybackViewModel.activityListener?.let {
it.release()
@ -850,7 +850,7 @@ class PlaybackViewModel
subtitleIndex: Int?,
userInitiated: Boolean,
): Boolean =
withContext(Dispatchers.IO) {
withContext(WholphinDispatchers.IO) {
// TODO there's probably no reason why we can't add external subtitles?
Timber.v("changeStreams direct play")
@ -859,7 +859,7 @@ class PlaybackViewModel
if (externalSubtitle == null) {
val result =
withContext(Dispatchers.Main) {
withContext(WholphinDispatchers.Main) {
TrackSelectionUtils.createTrackSelections(
onMain { player.trackSelectionParameters },
onMain { player.currentTracks },
@ -1124,7 +1124,7 @@ class PlaybackViewModel
MediaSegmentType.INTRO -> prefs.skipIntros
MediaSegmentType.UNKNOWN -> SkipSegmentBehavior.IGNORE
}
withContext(Dispatchers.Main) {
withContext(WholphinDispatchers.Main) {
val newSegment =
when (behavior) {
SkipSegmentBehavior.AUTO_SKIP -> {
@ -1318,7 +1318,7 @@ class PlaybackViewModel
override fun onPlayerError(error: PlaybackException) {
Timber.e(error, "Playback error")
viewModelScope.launch(Dispatchers.Main + ExceptionHandler()) {
viewModelScope.launch(WholphinDispatchers.Main + ExceptionHandler()) {
state.value.currentPlayback?.let {
when (it.playMethod) {
PlayMethod.TRANSCODE -> {
@ -1346,7 +1346,7 @@ class PlaybackViewModel
enableDirectPlay = false,
enableDirectStream = false,
)
withContext(Dispatchers.Main) {
withContext(WholphinDispatchers.Main) {
player.prepare()
player.play()
}
@ -1367,7 +1367,7 @@ class PlaybackViewModel
.subscribe<PlaystateMessage>()
.onEach { message ->
message.data?.let {
withContext(Dispatchers.Main) {
withContext(WholphinDispatchers.Main) {
when (it.command) {
PlaystateCommand.STOP -> {
release()
@ -1422,7 +1422,7 @@ class PlaybackViewModel
* Atomically update [currentMediaInfo]
*/
internal suspend fun updateCurrentMedia(block: (CurrentMediaInfo) -> CurrentMediaInfo) =
withContext(Dispatchers.IO) {
withContext(WholphinDispatchers.IO) {
_state.update {
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.onMain
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.flow.update
import kotlinx.coroutines.withContext
@ -183,7 +183,7 @@ fun PlaybackViewModel.downloadAndSwitchSubtitles(
}
}
subtitleSearchState.update { it.copy(status = SubtitleSearchStatus.Inactive) }
withContext(Dispatchers.Main) {
withContext(WholphinDispatchers.Main) {
if (wasPlaying) {
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.TrackSupportReason
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.isActive
import kotlinx.coroutines.withContext
@ -90,7 +90,7 @@ fun PlaybackDebugOverlay(
)
val memoryUsed by produceState("") {
withContext(Dispatchers.Default) {
withContext(WholphinDispatchers.Default) {
while (isActive) {
val runtime = Runtime.getRuntime()
val total = runtime.totalMemory()

View file

@ -16,7 +16,6 @@ import androidx.compose.ui.window.Dialog
import androidx.compose.ui.window.DialogProperties
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
import androidx.lifecycle.ViewModel
import androidx.lifecycle.asFlow
import androidx.lifecycle.viewModelScope
import androidx.tv.material3.Text
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.theme.WholphinTheme
import com.github.damontecres.wholphin.util.ExceptionHandler
import com.github.damontecres.wholphin.util.WholphinDispatchers
import dagger.hilt.android.lifecycle.HiltViewModel
import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.firstOrNull
import kotlinx.coroutines.flow.update
@ -317,7 +317,7 @@ class NavDrawerPreferencesViewModel
return@launchDefault
}
val navDrawerPins =
withContext(Dispatchers.IO) {
withContext(WholphinDispatchers.IO) {
serverPreferencesDao
.getNavDrawerPinnedItems(user)
.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.PreviewTvSpec
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.playback.isEnterKey
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.launchIO
import com.github.damontecres.wholphin.util.LoadingState
import com.github.damontecres.wholphin.util.WholphinDispatchers
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.Deferred
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.async
import kotlinx.coroutines.delay
@ -119,7 +119,7 @@ class SwitchUserViewModel
}
fun trySwitchUser(user: JellyfinUser): Deferred<String?> =
viewModelScope.async(Dispatchers.IO) {
viewModelScope.async(WholphinDispatchers.IO) {
try {
val current = serverRepository.changeUser(server, user)
setupNavigationManager.navigateTo(SetupDestination.AppContent(current))
@ -234,7 +234,7 @@ class SwitchUserViewModel
}
private suspend fun getUsers(): List<JellyfinUserAndImage> =
withContext(Dispatchers.IO) {
withContext(WholphinDispatchers.IO) {
val api = jellyfin.createApi(server.url)
val knownUsers =
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.GetItemsRequestHandler
import com.github.damontecres.wholphin.util.LoadingState
import com.github.damontecres.wholphin.util.WholphinDispatchers
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import dagger.hilt.android.lifecycle.HiltViewModel
import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableStateFlow
@ -377,7 +377,7 @@ class SlideshowViewModel
),
)
Timber.d("Saved VideoFilter for image %s", it.image.id)
withContext(Dispatchers.Main) {
withContext(WholphinDispatchers.Main) {
showToast(
context,
"Saved",
@ -406,7 +406,7 @@ class SlideshowViewModel
),
)
Timber.d("Saved VideoFilter for album %s", slideshowSettings.parentId)
withContext(Dispatchers.Main) {
withContext(WholphinDispatchers.Main) {
showToast(
context,
"Saved",

View file

@ -8,7 +8,7 @@ import androidx.compose.runtime.compositionLocalOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
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.isActive
import kotlinx.coroutines.withContext
@ -34,7 +34,7 @@ data class Clock(
fun ProvideLocalClock(content: @Composable () -> Unit) {
val clock = remember { Clock() }
LaunchedEffect(Unit) {
withContext(Dispatchers.Default) {
withContext(WholphinDispatchers.Default) {
while (isActive) {
val now = LocalDateTime.now()
val time = getTimeFormatter().format(now)

View file

@ -1,7 +1,6 @@
package com.github.damontecres.wholphin.util
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import org.jellyfin.sdk.api.client.ApiClient
import org.jellyfin.sdk.api.client.ApiClientFactory
@ -21,7 +20,7 @@ import kotlin.coroutines.CoroutineContext
*/
class CoroutineContextApiClient(
private val client: ApiClient,
private val coroutineContext: CoroutineContext = Dispatchers.IO,
private val coroutineContext: CoroutineContext = WholphinDispatchers.IO,
) : ApiClient() {
override val baseUrl: String?
get() = client.baseUrl
@ -65,7 +64,7 @@ class CoroutineContextApiClient(
class CoroutineContextApiClientFactory(
private val factory: OkHttpFactory,
private val coroutineContext: CoroutineContext = Dispatchers.IO,
private val coroutineContext: CoroutineContext = WholphinDispatchers.IO,
) : ApiClientFactory,
SocketConnectionFactory {
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.google.common.cache.CacheBuilder
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
import kotlinx.coroutines.sync.Mutex
@ -85,7 +84,7 @@ abstract class RequestPager<T>(
get() = totalCount
private fun fetchPage(position: Int): Job =
scope.launch(ExceptionHandler() + Dispatchers.IO) {
scope.launch(ExceptionHandler() + WholphinDispatchers.IO) {
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.playback.CurrentPlayback
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import org.jellyfin.sdk.api.client.ApiClient
import org.jellyfin.sdk.api.client.extensions.playStateApi
@ -34,7 +33,7 @@ class TrackActivityPlaybackListener(
private val player: Player,
private val getState: () -> PlaybackItemState?,
) : Player.Listener {
private val coroutineScope = CoroutineScope(Dispatchers.Main)
private val coroutineScope = CoroutineScope(WholphinDispatchers.Main)
private val task: TimerTask =
object : TimerTask() {
override fun run() {
@ -57,7 +56,7 @@ class TrackActivityPlaybackListener(
PlaybackStartInfo(
canSeek = true,
itemId = state.itemId,
isPaused = withContext(Dispatchers.Main) { !player.isPlaying },
isPaused = withContext(WholphinDispatchers.Main) { !player.isPlaying },
playMethod = state.playMethod,
repeatMode = RepeatMode.REPEAT_NONE,
playbackOrder = PlaybackOrder.DEFAULT,
@ -117,11 +116,11 @@ class TrackActivityPlaybackListener(
launch("saveActivity") {
getState.invoke()?.let { state ->
val calcPosition =
withContext(Dispatchers.Main) {
withContext(WholphinDispatchers.Main) {
(if (position >= 0) position else player.currentPosition)
}
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")
api.playStateApi.reportPlaybackProgress(
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 com.github.damontecres.wholphin.data.CurrentUser
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.mockk
import io.mockk.verify
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.advanceUntilIdle
import kotlinx.coroutines.test.resetMain
import kotlinx.coroutines.test.runTest
import kotlinx.coroutines.test.setMain
import org.junit.After
import org.junit.Before
import org.junit.Rule
@ -36,14 +36,14 @@ class LatestNextUpSchedulerServiceTest {
@Before
fun setUp() {
Dispatchers.setMain(testDispatcher)
WholphinDispatchers.configure(testDispatcher)
every { mockActivity.lifecycle } returns lifecycleRegistry
every { mockServerRepository.current } returns currentUser
lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_RESUME)
}
@After
fun tearDown() = Dispatchers.resetMain()
fun tearDown() = WholphinDispatchers.reset()
private fun createService() =
LatestNextUpSchedulerService(
@ -56,7 +56,7 @@ class LatestNextUpSchedulerServiceTest {
@Test
fun cancels_latestNextUp_work_when_user_null() =
runTest {
runTest(testDispatcher) {
createService()
currentUser.value = null

View file

@ -5,15 +5,15 @@
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.every
import io.mockk.mockk
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.resetMain
import kotlinx.coroutines.test.runTest
import kotlinx.coroutines.test.setMain
import kotlinx.serialization.UseSerializers
import kotlinx.serialization.json.Json
import org.jellyfin.sdk.api.client.ApiClient
@ -56,14 +56,14 @@ class LatestNextUpServiceTests {
@OptIn(ExperimentalCoroutinesApi::class)
@Before
fun setUp() {
Dispatchers.setMain(testDispatcher)
WholphinDispatchers.configure(testDispatcher)
every { mockApi.tvShowsApi } returns mockTvShowsApi
}
@OptIn(ExperimentalCoroutinesApi::class)
@After
fun tearDown() {
Dispatchers.resetMain()
WholphinDispatchers.reset()
}
@Test

View file

@ -8,19 +8,19 @@ import androidx.work.WorkManager
import com.github.damontecres.wholphin.data.ServerRepository
import com.github.damontecres.wholphin.data.model.JellyfinUser
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.every
import io.mockk.mockk
import io.mockk.verify
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.resetMain
import kotlinx.coroutines.test.runTest
import kotlinx.coroutines.test.setMain
import org.jellyfin.sdk.api.client.ApiClient
import org.jellyfin.sdk.api.client.Response
import org.jellyfin.sdk.model.api.BaseItemDto
@ -48,12 +48,12 @@ class SuggestionServiceTest {
@Before
fun setUp() {
Dispatchers.setMain(testDispatcher)
WholphinDispatchers.configure(testDispatcher)
}
@After
fun tearDown() {
Dispatchers.resetMain()
WholphinDispatchers.reset()
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.model.JellyfinServer
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 io.mockk.every
import io.mockk.mockk
import io.mockk.slot
import io.mockk.verify
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.advanceUntilIdle
import kotlinx.coroutines.test.resetMain
import kotlinx.coroutines.test.runTest
import kotlinx.coroutines.test.setMain
import org.junit.After
import org.junit.Assert.assertEquals
import org.junit.Before
@ -43,14 +43,14 @@ class SuggestionsSchedulerServiceTest {
@Before
fun setUp() {
Dispatchers.setMain(testDispatcher)
WholphinDispatchers.configure(testDispatcher)
every { mockActivity.lifecycle } returns lifecycleRegistry
every { mockServerRepository.current } returns currentUser
lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_RESUME)
}
@After
fun tearDown() = Dispatchers.resetMain()
fun tearDown() = WholphinDispatchers.reset()
private fun createService() =
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.theme.WholphinTheme
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.HiltAndroidTest
import dagger.hilt.android.testing.HiltTestApplication
import io.mockk.coEvery
import io.mockk.every
import io.mockk.mockk
import io.mockk.mockkStatic
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.test.resetMain
import kotlinx.coroutines.test.setMain
import org.jellyfin.sdk.Jellyfin
import org.jellyfin.sdk.api.client.ApiClient
import org.jellyfin.sdk.api.client.extensions.quickConnectApi
@ -105,11 +104,7 @@ class BasicUiTests {
@OptIn(ExperimentalCoroutinesApi::class)
@Before
fun setup() {
Dispatchers.setMain(TestModule.testDispatcher)
mockkStatic(Dispatchers::class)
every { Dispatchers.IO } returns TestModule.testDispatcher
every { Dispatchers.Default } returns TestModule.testDispatcher
WholphinDispatchers.configure(TestModule.testDispatcher)
hiltRule.inject()
screensaverService = mockk(relaxed = true)
@ -130,7 +125,7 @@ class BasicUiTests {
@OptIn(ExperimentalCoroutinesApi::class)
@After
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.StandardOkHttpClient
import com.github.damontecres.wholphin.util.CoroutineContextApiClientFactory
import com.github.damontecres.wholphin.util.WholphinDispatchers
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
@ -46,7 +47,6 @@ import io.mockk.every
import io.mockk.mockk
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.asExecutor
import kotlinx.coroutines.test.StandardTestDispatcher
@ -225,7 +225,7 @@ object TestDatabaseModule {
.allowMainThreadQueries()
.setQueryCallback({ sqlQuery, args ->
Timber.v("sqlQuery=$sqlQuery, args=$args")
}, Dispatchers.IO.asExecutor())
}, WholphinDispatchers.IO.asExecutor())
.build()
@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
}