mirror of
https://github.com/JustinZeus/Wholphin.git
synced 2026-07-08 15:50:16 +02:00
Use ExceptionHandler
This commit is contained in:
parent
805f63cb0e
commit
9265a06fd7
20 changed files with 207 additions and 87 deletions
|
|
@ -46,6 +46,7 @@ import androidx.tv.material3.MaterialTheme
|
||||||
import androidx.tv.material3.Text
|
import androidx.tv.material3.Text
|
||||||
import androidx.tv.material3.surfaceColorAtElevation
|
import androidx.tv.material3.surfaceColorAtElevation
|
||||||
import com.github.damontecres.dolphin.ui.FontAwesome
|
import com.github.damontecres.dolphin.ui.FontAwesome
|
||||||
|
import com.github.damontecres.dolphin.util.ExceptionHandler
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
|
|
@ -229,7 +230,7 @@ fun ScrollableDialog(
|
||||||
val scope = rememberCoroutineScope()
|
val scope = rememberCoroutineScope()
|
||||||
|
|
||||||
fun scroll(reverse: Boolean = false) {
|
fun scroll(reverse: Boolean = false) {
|
||||||
scope.launch {
|
scope.launch(ExceptionHandler()) {
|
||||||
columnState.scrollBy(if (reverse) -scrollAmount else scrollAmount)
|
columnState.scrollBy(if (reverse) -scrollAmount else scrollAmount)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -38,6 +38,7 @@ import com.github.damontecres.dolphin.ui.components.TitleValueText
|
||||||
import com.github.damontecres.dolphin.ui.isNotNullOrBlank
|
import com.github.damontecres.dolphin.ui.isNotNullOrBlank
|
||||||
import com.github.damontecres.dolphin.ui.playOnClickSound
|
import com.github.damontecres.dolphin.ui.playOnClickSound
|
||||||
import com.github.damontecres.dolphin.ui.playSoundOnFocus
|
import com.github.damontecres.dolphin.ui.playSoundOnFocus
|
||||||
|
import com.github.damontecres.dolphin.util.ExceptionHandler
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import java.util.SortedMap
|
import java.util.SortedMap
|
||||||
import kotlin.time.Duration
|
import kotlin.time.Duration
|
||||||
|
|
@ -137,7 +138,7 @@ fun VideoDetailsHeader(
|
||||||
.background(bgColor, shape = RoundedCornerShape(8.dp))
|
.background(bgColor, shape = RoundedCornerShape(8.dp))
|
||||||
.onFocusChanged {
|
.onFocusChanged {
|
||||||
if (it.isFocused) {
|
if (it.isFocused) {
|
||||||
scope.launch { bringIntoViewRequester.bringIntoView() }
|
scope.launch(ExceptionHandler()) { bringIntoViewRequester.bringIntoView() }
|
||||||
}
|
}
|
||||||
}.playSoundOnFocus(true)
|
}.playSoundOnFocus(true)
|
||||||
.clickable(
|
.clickable(
|
||||||
|
|
@ -181,7 +182,7 @@ fun VideoDetailsHeader(
|
||||||
},
|
},
|
||||||
moreOnClick = { moreOnClick.invoke() },
|
moreOnClick = { moreOnClick.invoke() },
|
||||||
buttonOnFocusChanged = {
|
buttonOnFocusChanged = {
|
||||||
scope.launch { bringIntoViewRequester.bringIntoView() }
|
scope.launch(ExceptionHandler()) { bringIntoViewRequester.bringIntoView() }
|
||||||
},
|
},
|
||||||
focusRequester = focusRequester,
|
focusRequester = focusRequester,
|
||||||
modifier = Modifier,
|
modifier = Modifier,
|
||||||
|
|
|
||||||
|
|
@ -51,6 +51,7 @@ import com.github.damontecres.dolphin.ui.playback.isBackwardButton
|
||||||
import com.github.damontecres.dolphin.ui.playback.isForwardButton
|
import com.github.damontecres.dolphin.ui.playback.isForwardButton
|
||||||
import com.github.damontecres.dolphin.ui.playback.isPlayKeyUp
|
import com.github.damontecres.dolphin.ui.playback.isPlayKeyUp
|
||||||
import com.github.damontecres.dolphin.ui.tryRequestFocus
|
import com.github.damontecres.dolphin.ui.tryRequestFocus
|
||||||
|
import com.github.damontecres.dolphin.util.ExceptionHandler
|
||||||
import com.github.damontecres.dolphin.util.ItemPager
|
import com.github.damontecres.dolphin.util.ItemPager
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
|
|
@ -169,7 +170,7 @@ fun CardGrid(
|
||||||
}
|
}
|
||||||
|
|
||||||
val jump = { jump: Int ->
|
val jump = { jump: Int ->
|
||||||
scope.launch {
|
scope.launch(ExceptionHandler()) {
|
||||||
val newPosition =
|
val newPosition =
|
||||||
(gridState.firstVisibleItemIndex + jump).coerceIn(0..<pager.size)
|
(gridState.firstVisibleItemIndex + jump).coerceIn(0..<pager.size)
|
||||||
if (DEBUG) Timber.d("newPosition=$newPosition")
|
if (DEBUG) Timber.d("newPosition=$newPosition")
|
||||||
|
|
@ -179,7 +180,7 @@ fun CardGrid(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val jumpToTop = {
|
val jumpToTop = {
|
||||||
scope.launch {
|
scope.launch(ExceptionHandler()) {
|
||||||
if (focusedIndex < (columns * 6)) {
|
if (focusedIndex < (columns * 6)) {
|
||||||
// If close, animate the scroll
|
// If close, animate the scroll
|
||||||
gridState.animateScrollToItem(0, 0)
|
gridState.animateScrollToItem(0, 0)
|
||||||
|
|
@ -205,7 +206,7 @@ fun CardGrid(
|
||||||
if (DEBUG) Timber.d("Back long pressed: newPosition=$newPosition")
|
if (DEBUG) Timber.d("Back long pressed: newPosition=$newPosition")
|
||||||
if (newPosition > 0) {
|
if (newPosition > 0) {
|
||||||
focusOn(newPosition)
|
focusOn(newPosition)
|
||||||
scope.launch {
|
scope.launch(ExceptionHandler()) {
|
||||||
gridState.scrollToItem(newPosition, -columns)
|
gridState.scrollToItem(newPosition, -columns)
|
||||||
firstFocus.tryRequestFocus()
|
firstFocus.tryRequestFocus()
|
||||||
}
|
}
|
||||||
|
|
@ -366,7 +367,7 @@ fun CardGrid(
|
||||||
AlphabetButtons(
|
AlphabetButtons(
|
||||||
modifier = Modifier.align(Alignment.CenterVertically),
|
modifier = Modifier.align(Alignment.CenterVertically),
|
||||||
letterClicked = { letter ->
|
letterClicked = { letter ->
|
||||||
scope.launch {
|
scope.launch(ExceptionHandler()) {
|
||||||
val jumpPosition = letterPosition.invoke(letter)
|
val jumpPosition = letterPosition.invoke(letter)
|
||||||
Timber.d("Alphabet jump to $jumpPosition")
|
Timber.d("Alphabet jump to $jumpPosition")
|
||||||
gridState.scrollToItem(jumpPosition)
|
gridState.scrollToItem(jumpPosition)
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@ import com.github.damontecres.dolphin.preferences.UserPreferences
|
||||||
import com.github.damontecres.dolphin.ui.OneTimeLaunchedEffect
|
import com.github.damontecres.dolphin.ui.OneTimeLaunchedEffect
|
||||||
import com.github.damontecres.dolphin.ui.nav.Destination
|
import com.github.damontecres.dolphin.ui.nav.Destination
|
||||||
import com.github.damontecres.dolphin.ui.nav.NavigationManager
|
import com.github.damontecres.dolphin.ui.nav.NavigationManager
|
||||||
|
import com.github.damontecres.dolphin.util.ExceptionHandler
|
||||||
import com.github.damontecres.dolphin.util.ItemPager
|
import com.github.damontecres.dolphin.util.ItemPager
|
||||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
|
|
@ -43,7 +44,7 @@ class CollectionFolderViewModel
|
||||||
itemId: UUID,
|
itemId: UUID,
|
||||||
potential: BaseItem?,
|
potential: BaseItem?,
|
||||||
): Job =
|
): Job =
|
||||||
viewModelScope.launch {
|
viewModelScope.launch(ExceptionHandler()) {
|
||||||
super.init(itemId, potential)?.join()
|
super.init(itemId, potential)?.join()
|
||||||
setup()
|
setup()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ import androidx.lifecycle.viewModelScope
|
||||||
import com.github.damontecres.dolphin.data.model.BaseItem
|
import com.github.damontecres.dolphin.data.model.BaseItem
|
||||||
import com.github.damontecres.dolphin.data.model.DolphinModel
|
import com.github.damontecres.dolphin.data.model.DolphinModel
|
||||||
import com.github.damontecres.dolphin.data.model.convertModel
|
import com.github.damontecres.dolphin.data.model.convertModel
|
||||||
|
import com.github.damontecres.dolphin.util.ExceptionHandler
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import org.jellyfin.sdk.api.client.ApiClient
|
import org.jellyfin.sdk.api.client.ApiClient
|
||||||
|
|
@ -32,7 +33,7 @@ abstract class ItemViewModel<T : DolphinModel>(
|
||||||
if (item.value?.id == itemId) {
|
if (item.value?.id == itemId) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
return viewModelScope.launch {
|
return viewModelScope.launch(ExceptionHandler()) {
|
||||||
try {
|
try {
|
||||||
val fetchedItem = api.userLibraryApi.getItem(itemId).content
|
val fetchedItem = api.userLibraryApi.getItem(itemId).content
|
||||||
val modelInstance = convertModel(fetchedItem, api)
|
val modelInstance = convertModel(fetchedItem, api)
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,7 @@ import com.github.damontecres.dolphin.preferences.UserPreferences
|
||||||
import com.github.damontecres.dolphin.ui.cards.ItemRow
|
import com.github.damontecres.dolphin.ui.cards.ItemRow
|
||||||
import com.github.damontecres.dolphin.ui.nav.Destination
|
import com.github.damontecres.dolphin.ui.nav.Destination
|
||||||
import com.github.damontecres.dolphin.ui.nav.NavigationManager
|
import com.github.damontecres.dolphin.ui.nav.NavigationManager
|
||||||
|
import com.github.damontecres.dolphin.util.ExceptionHandler
|
||||||
import com.github.damontecres.dolphin.util.ItemPager
|
import com.github.damontecres.dolphin.util.ItemPager
|
||||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
|
|
@ -46,7 +47,7 @@ class SeasonViewModel
|
||||||
itemId: UUID,
|
itemId: UUID,
|
||||||
potential: BaseItem?,
|
potential: BaseItem?,
|
||||||
): Job? =
|
): Job? =
|
||||||
viewModelScope.launch {
|
viewModelScope.launch(ExceptionHandler()) {
|
||||||
super.init(itemId, potential)?.join()
|
super.init(itemId, potential)?.join()
|
||||||
item.value?.let { item ->
|
item.value?.let { item ->
|
||||||
val request =
|
val request =
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@ import com.github.damontecres.dolphin.ui.indexOfFirstOrNull
|
||||||
import com.github.damontecres.dolphin.ui.nav.Destination
|
import com.github.damontecres.dolphin.ui.nav.Destination
|
||||||
import com.github.damontecres.dolphin.ui.nav.NavigationManager
|
import com.github.damontecres.dolphin.ui.nav.NavigationManager
|
||||||
import com.github.damontecres.dolphin.util.ApiRequestPager
|
import com.github.damontecres.dolphin.util.ApiRequestPager
|
||||||
|
import com.github.damontecres.dolphin.util.ExceptionHandler
|
||||||
import com.github.damontecres.dolphin.util.GetEpisodesRequestHandler
|
import com.github.damontecres.dolphin.util.GetEpisodesRequestHandler
|
||||||
import com.github.damontecres.dolphin.util.ItemPager
|
import com.github.damontecres.dolphin.util.ItemPager
|
||||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
|
|
@ -52,7 +53,7 @@ class SeriesViewModel
|
||||||
itemId: UUID,
|
itemId: UUID,
|
||||||
potential: BaseItem?,
|
potential: BaseItem?,
|
||||||
): Job =
|
): Job =
|
||||||
viewModelScope.launch {
|
viewModelScope.launch(ExceptionHandler()) {
|
||||||
super.init(itemId, potential)?.join()
|
super.init(itemId, potential)?.join()
|
||||||
item.value?.let { item ->
|
item.value?.let { item ->
|
||||||
val request =
|
val request =
|
||||||
|
|
@ -90,7 +91,7 @@ class SeriesViewModel
|
||||||
season: Int?,
|
season: Int?,
|
||||||
episode: Int?,
|
episode: Int?,
|
||||||
) {
|
) {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch(ExceptionHandler()) {
|
||||||
init(itemId, potential).join()
|
init(itemId, potential).join()
|
||||||
season?.let { seasonNum ->
|
season?.let { seasonNum ->
|
||||||
val targetSeasonPosition =
|
val targetSeasonPosition =
|
||||||
|
|
@ -103,7 +104,7 @@ class SeriesViewModel
|
||||||
}
|
}
|
||||||
|
|
||||||
fun loadEpisodes(season: Int): Deferred<ApiRequestPager<*>> =
|
fun loadEpisodes(season: Int): Deferred<ApiRequestPager<*>> =
|
||||||
viewModelScope.async {
|
viewModelScope.async(ExceptionHandler()) {
|
||||||
val request =
|
val request =
|
||||||
GetEpisodesRequest(
|
GetEpisodesRequest(
|
||||||
seriesId = item.value!!.id,
|
seriesId = item.value!!.id,
|
||||||
|
|
@ -130,7 +131,7 @@ class SeriesViewModel
|
||||||
itemId: UUID,
|
itemId: UUID,
|
||||||
played: Boolean,
|
played: Boolean,
|
||||||
listIndex: Int,
|
listIndex: Int,
|
||||||
) = viewModelScope.launch {
|
) = viewModelScope.launch(ExceptionHandler()) {
|
||||||
if (played) {
|
if (played) {
|
||||||
api.playStateApi.markPlayedItem(itemId)
|
api.playStateApi.markPlayedItem(itemId)
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -142,7 +143,7 @@ class SeriesViewModel
|
||||||
fun refreshEpisode(
|
fun refreshEpisode(
|
||||||
itemId: UUID,
|
itemId: UUID,
|
||||||
listIndex: Int,
|
listIndex: Int,
|
||||||
) = viewModelScope.launch {
|
) = viewModelScope.launch(ExceptionHandler()) {
|
||||||
val base = api.userLibraryApi.getItem(itemId).content
|
val base = api.userLibraryApi.getItem(itemId).content
|
||||||
val item = BaseItem.from(base, api)
|
val item = BaseItem.from(base, api)
|
||||||
episodes.value =
|
episodes.value =
|
||||||
|
|
|
||||||
|
|
@ -10,9 +10,11 @@ import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.height
|
import androidx.compose.foundation.layout.height
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.size
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.foundation.lazy.items
|
import androidx.compose.foundation.lazy.items
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.livedata.observeAsState
|
import androidx.compose.runtime.livedata.observeAsState
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
|
@ -22,6 +24,8 @@ import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.draw.alpha
|
import androidx.compose.ui.draw.alpha
|
||||||
import androidx.compose.ui.draw.drawWithContent
|
import androidx.compose.ui.draw.drawWithContent
|
||||||
|
import androidx.compose.ui.focus.FocusRequester
|
||||||
|
import androidx.compose.ui.focus.focusRequester
|
||||||
import androidx.compose.ui.graphics.Brush
|
import androidx.compose.ui.graphics.Brush
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.layout.ContentScale
|
import androidx.compose.ui.layout.ContentScale
|
||||||
|
|
@ -37,11 +41,15 @@ import com.github.damontecres.dolphin.data.model.BaseItem
|
||||||
import com.github.damontecres.dolphin.preferences.UserPreferences
|
import com.github.damontecres.dolphin.preferences.UserPreferences
|
||||||
import com.github.damontecres.dolphin.ui.cards.BannerCard
|
import com.github.damontecres.dolphin.ui.cards.BannerCard
|
||||||
import com.github.damontecres.dolphin.ui.cards.ItemRow
|
import com.github.damontecres.dolphin.ui.cards.ItemRow
|
||||||
|
import com.github.damontecres.dolphin.ui.components.CircularProgress
|
||||||
import com.github.damontecres.dolphin.ui.components.DotSeparatedRow
|
import com.github.damontecres.dolphin.ui.components.DotSeparatedRow
|
||||||
|
import com.github.damontecres.dolphin.ui.ifElse
|
||||||
import com.github.damontecres.dolphin.ui.isNotNullOrBlank
|
import com.github.damontecres.dolphin.ui.isNotNullOrBlank
|
||||||
import com.github.damontecres.dolphin.ui.nav.NavigationManager
|
import com.github.damontecres.dolphin.ui.nav.NavigationManager
|
||||||
import com.github.damontecres.dolphin.ui.roundMinutes
|
import com.github.damontecres.dolphin.ui.roundMinutes
|
||||||
import com.github.damontecres.dolphin.ui.timeRemaining
|
import com.github.damontecres.dolphin.ui.timeRemaining
|
||||||
|
import com.github.damontecres.dolphin.ui.tryRequestFocus
|
||||||
|
import com.github.damontecres.dolphin.util.LoadingState
|
||||||
import com.github.damontecres.dolphin.util.formatDateTime
|
import com.github.damontecres.dolphin.util.formatDateTime
|
||||||
import org.jellyfin.sdk.model.api.BaseItemKind
|
import org.jellyfin.sdk.model.api.BaseItemKind
|
||||||
import org.jellyfin.sdk.model.extensions.ticks
|
import org.jellyfin.sdk.model.extensions.ticks
|
||||||
|
|
@ -59,8 +67,39 @@ fun MainPage(
|
||||||
modifier: Modifier,
|
modifier: Modifier,
|
||||||
viewModel: MainViewModel = hiltViewModel(),
|
viewModel: MainViewModel = hiltViewModel(),
|
||||||
) {
|
) {
|
||||||
|
val loading by viewModel.loadingState.observeAsState(LoadingState.Loading)
|
||||||
|
when (val state = loading) {
|
||||||
|
is LoadingState.Error -> Text(text = "Error: ${state.message}", modifier = modifier)
|
||||||
|
LoadingState.Loading ->
|
||||||
|
Box(
|
||||||
|
modifier = modifier,
|
||||||
|
contentAlignment = Alignment.Center,
|
||||||
|
) {
|
||||||
|
CircularProgress(modifier = Modifier.size(250.dp))
|
||||||
|
}
|
||||||
|
|
||||||
|
LoadingState.Success -> {
|
||||||
val homeRows by viewModel.homeRows.observeAsState(listOf())
|
val homeRows by viewModel.homeRows.observeAsState(listOf())
|
||||||
var focusedItem by remember { mutableStateOf<BaseItem?>(null) }
|
MainPageContent(navigationManager, homeRows, modifier)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun MainPageContent(
|
||||||
|
navigationManager: NavigationManager,
|
||||||
|
homeRows: List<HomeRow>,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
) {
|
||||||
|
var focusedItem by remember {
|
||||||
|
mutableStateOf<BaseItem?>(
|
||||||
|
homeRows.getOrNull(0)?.items?.getOrNull(
|
||||||
|
0,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
val focusRequester = remember { FocusRequester() }
|
||||||
|
LaunchedEffect(Unit) { if (focusedItem != null) focusRequester.tryRequestFocus() }
|
||||||
Box(modifier = modifier) {
|
Box(modifier = modifier) {
|
||||||
if (focusedItem?.backdropImageUrl.isNotNullOrBlank()) {
|
if (focusedItem?.backdropImageUrl.isNotNullOrBlank()) {
|
||||||
val gradientColor = MaterialTheme.colorScheme.background
|
val gradientColor = MaterialTheme.colorScheme.background
|
||||||
|
|
@ -136,7 +175,11 @@ fun MainPage(
|
||||||
playPercent = item?.data?.userData?.playedPercentage ?: 0.0,
|
playPercent = item?.data?.userData?.playedPercentage ?: 0.0,
|
||||||
onClick = onClick,
|
onClick = onClick,
|
||||||
onLongClick = onLongClick,
|
onLongClick = onLongClick,
|
||||||
modifier = modifier,
|
modifier =
|
||||||
|
modifier.ifElse(
|
||||||
|
focusedItem == item,
|
||||||
|
Modifier.focusRequester(focusRequester),
|
||||||
|
),
|
||||||
interactionSource = null,
|
interactionSource = null,
|
||||||
cardHeight = 200.dp,
|
cardHeight = 200.dp,
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,8 @@ import androidx.lifecycle.viewModelScope
|
||||||
import com.github.damontecres.dolphin.data.model.BaseItem
|
import com.github.damontecres.dolphin.data.model.BaseItem
|
||||||
import com.github.damontecres.dolphin.ui.DefaultItemFields
|
import com.github.damontecres.dolphin.ui.DefaultItemFields
|
||||||
import com.github.damontecres.dolphin.ui.isNotNullOrBlank
|
import com.github.damontecres.dolphin.ui.isNotNullOrBlank
|
||||||
|
import com.github.damontecres.dolphin.util.ExceptionHandler
|
||||||
|
import com.github.damontecres.dolphin.util.LoadingState
|
||||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
@ -29,10 +31,11 @@ class MainViewModel
|
||||||
constructor(
|
constructor(
|
||||||
val api: ApiClient,
|
val api: ApiClient,
|
||||||
) : ViewModel() {
|
) : ViewModel() {
|
||||||
|
val loadingState = MutableLiveData<LoadingState>(LoadingState.Loading)
|
||||||
val homeRows = MutableLiveData<List<HomeRow>>()
|
val homeRows = MutableLiveData<List<HomeRow>>()
|
||||||
|
|
||||||
init {
|
init {
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
viewModelScope.launch(ExceptionHandler() + Dispatchers.IO) {
|
||||||
val user = api.userApi.getCurrentUser().content
|
val user = api.userApi.getCurrentUser().content
|
||||||
val displayPrefs =
|
val displayPrefs =
|
||||||
api.displayPreferencesApi
|
api.displayPreferencesApi
|
||||||
|
|
@ -145,6 +148,7 @@ class MainViewModel
|
||||||
.filter { it.items.isNotEmpty() }
|
.filter { it.items.isNotEmpty() }
|
||||||
withContext(Dispatchers.Main) {
|
withContext(Dispatchers.Main) {
|
||||||
this@MainViewModel.homeRows.value = homeRows
|
this@MainViewModel.homeRows.value = homeRows
|
||||||
|
loadingState.value = LoadingState.Success
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.focusGroup
|
import androidx.compose.foundation.focusGroup
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.PaddingValues
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
import androidx.compose.foundation.layout.fillMaxHeight
|
import androidx.compose.foundation.layout.fillMaxHeight
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
|
@ -25,6 +26,7 @@ import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.focus.FocusRequester
|
import androidx.compose.ui.focus.FocusRequester
|
||||||
import androidx.compose.ui.focus.focusRequester
|
import androidx.compose.ui.focus.focusRequester
|
||||||
|
import androidx.compose.ui.focus.focusRestorer
|
||||||
import androidx.compose.ui.graphics.vector.ImageVector
|
import androidx.compose.ui.graphics.vector.ImageVector
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.platform.LocalView
|
import androidx.compose.ui.platform.LocalView
|
||||||
|
|
@ -46,11 +48,13 @@ import androidx.tv.material3.NavigationDrawerScope
|
||||||
import androidx.tv.material3.ProvideTextStyle
|
import androidx.tv.material3.ProvideTextStyle
|
||||||
import androidx.tv.material3.Text
|
import androidx.tv.material3.Text
|
||||||
import androidx.tv.material3.rememberDrawerState
|
import androidx.tv.material3.rememberDrawerState
|
||||||
|
import androidx.tv.material3.surfaceColorAtElevation
|
||||||
import com.github.damontecres.dolphin.R
|
import com.github.damontecres.dolphin.R
|
||||||
import com.github.damontecres.dolphin.data.ServerRepository
|
import com.github.damontecres.dolphin.data.ServerRepository
|
||||||
import com.github.damontecres.dolphin.data.model.Library
|
import com.github.damontecres.dolphin.data.model.Library
|
||||||
import com.github.damontecres.dolphin.preferences.UserPreferences
|
import com.github.damontecres.dolphin.preferences.UserPreferences
|
||||||
import com.github.damontecres.dolphin.ui.FontAwesome
|
import com.github.damontecres.dolphin.ui.FontAwesome
|
||||||
|
import com.github.damontecres.dolphin.util.ExceptionHandler
|
||||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import org.jellyfin.sdk.api.client.ApiClient
|
import org.jellyfin.sdk.api.client.ApiClient
|
||||||
|
|
@ -69,7 +73,7 @@ class NavDrawerViewModel
|
||||||
val libraries = MutableLiveData<List<Library>>(listOf())
|
val libraries = MutableLiveData<List<Library>>(listOf())
|
||||||
|
|
||||||
init {
|
init {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch(ExceptionHandler()) {
|
||||||
val userViews =
|
val userViews =
|
||||||
api.userViewsApi
|
api.userViewsApi
|
||||||
.getUserViews()
|
.getUserViews()
|
||||||
|
|
@ -105,42 +109,45 @@ fun NavDrawer(
|
||||||
NavigationDrawer(
|
NavigationDrawer(
|
||||||
modifier =
|
modifier =
|
||||||
modifier
|
modifier
|
||||||
.focusRequester(drawerFocusRequester),
|
.focusRequester(drawerFocusRequester)
|
||||||
|
.background(MaterialTheme.colorScheme.background),
|
||||||
drawerState = drawerState,
|
drawerState = drawerState,
|
||||||
drawerContent = {
|
drawerContent = {
|
||||||
ProvideTextStyle(MaterialTheme.typography.labelMedium) {
|
ProvideTextStyle(MaterialTheme.typography.labelMedium) {
|
||||||
LazyColumn(
|
Column(
|
||||||
state = listState,
|
verticalArrangement = Arrangement.spacedBy(8.dp),
|
||||||
contentPadding = PaddingValues(0.dp),
|
|
||||||
horizontalAlignment = Alignment.CenterHorizontally,
|
|
||||||
verticalArrangement = Arrangement.SpaceBetween,
|
|
||||||
modifier =
|
modifier =
|
||||||
Modifier
|
Modifier
|
||||||
|
.focusGroup()
|
||||||
|
// TODO fix focus restorer
|
||||||
|
.focusRestorer()
|
||||||
.fillMaxHeight()
|
.fillMaxHeight()
|
||||||
.background(MaterialTheme.colorScheme.background)
|
.background(MaterialTheme.colorScheme.surfaceColorAtElevation(1.dp)),
|
||||||
.focusGroup(),
|
|
||||||
) {
|
) {
|
||||||
item {
|
|
||||||
IconNavItem(
|
IconNavItem(
|
||||||
text = user?.name ?: "",
|
text = user?.name ?: "",
|
||||||
icon = Icons.Default.AccountCircle,
|
icon = Icons.Default.AccountCircle,
|
||||||
onClick = { navigationManager.navigateTo(Destination.UserList) },
|
onClick = { navigationManager.navigateTo(Destination.UserList) },
|
||||||
)
|
)
|
||||||
}
|
|
||||||
item {
|
|
||||||
IconNavItem(
|
IconNavItem(
|
||||||
text = "Search",
|
text = "Search",
|
||||||
icon = Icons.Default.Search,
|
icon = Icons.Default.Search,
|
||||||
onClick = { navigationManager.navigateTo(Destination.Search) },
|
onClick = { navigationManager.navigateTo(Destination.Search) },
|
||||||
)
|
)
|
||||||
}
|
|
||||||
item {
|
|
||||||
IconNavItem(
|
IconNavItem(
|
||||||
text = "Home",
|
text = "Home",
|
||||||
icon = Icons.Default.Home,
|
icon = Icons.Default.Home,
|
||||||
onClick = { navigationManager.goToHome() },
|
onClick = { navigationManager.goToHome() },
|
||||||
)
|
)
|
||||||
}
|
LazyColumn(
|
||||||
|
state = listState,
|
||||||
|
contentPadding = PaddingValues(0.dp),
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
|
verticalArrangement = Arrangement.spacedBy(8.dp),
|
||||||
|
modifier =
|
||||||
|
Modifier
|
||||||
|
.weight(1f),
|
||||||
|
) {
|
||||||
items(libraries) {
|
items(libraries) {
|
||||||
LibraryNavItem(
|
LibraryNavItem(
|
||||||
library = it,
|
library = it,
|
||||||
|
|
@ -154,15 +161,15 @@ fun NavDrawer(
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
item {
|
}
|
||||||
IconNavItem(
|
IconNavItem(
|
||||||
text = "Settings",
|
text = "Settings",
|
||||||
icon = Icons.Default.Settings,
|
icon = Icons.Default.Settings,
|
||||||
onClick = { navigationManager.navigateTo(Destination.Settings) },
|
onClick = { navigationManager.navigateTo(Destination.Settings) },
|
||||||
|
modifier = Modifier,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
},
|
},
|
||||||
) {
|
) {
|
||||||
// Drawer content
|
// Drawer content
|
||||||
|
|
|
||||||
|
|
@ -59,6 +59,7 @@ import androidx.tv.material3.Text
|
||||||
import com.github.damontecres.dolphin.R
|
import com.github.damontecres.dolphin.R
|
||||||
import com.github.damontecres.dolphin.ui.AppColors
|
import com.github.damontecres.dolphin.ui.AppColors
|
||||||
import com.github.damontecres.dolphin.ui.tryRequestFocus
|
import com.github.damontecres.dolphin.ui.tryRequestFocus
|
||||||
|
import com.github.damontecres.dolphin.util.ExceptionHandler
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.isActive
|
import kotlinx.coroutines.isActive
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
@ -117,13 +118,13 @@ fun PlaybackControls(
|
||||||
|
|
||||||
val bringIntoViewRequester = remember { BringIntoViewRequester() }
|
val bringIntoViewRequester = remember { BringIntoViewRequester() }
|
||||||
val onControllerInteraction = {
|
val onControllerInteraction = {
|
||||||
scope.launch {
|
scope.launch(ExceptionHandler()) {
|
||||||
bringIntoViewRequester.bringIntoView()
|
bringIntoViewRequester.bringIntoView()
|
||||||
}
|
}
|
||||||
controllerViewState.pulseControls()
|
controllerViewState.pulseControls()
|
||||||
}
|
}
|
||||||
val onControllerInteractionForDialog = {
|
val onControllerInteractionForDialog = {
|
||||||
scope.launch {
|
scope.launch(ExceptionHandler()) {
|
||||||
bringIntoViewRequester.bringIntoView()
|
bringIntoViewRequester.bringIntoView()
|
||||||
}
|
}
|
||||||
controllerViewState.pulseControls(Int.MAX_VALUE)
|
controllerViewState.pulseControls(Int.MAX_VALUE)
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ import androidx.media3.common.MediaItem
|
||||||
import androidx.media3.exoplayer.ExoPlayer
|
import androidx.media3.exoplayer.ExoPlayer
|
||||||
import com.github.damontecres.dolphin.preferences.UserPreferences
|
import com.github.damontecres.dolphin.preferences.UserPreferences
|
||||||
import com.github.damontecres.dolphin.ui.nav.Destination
|
import com.github.damontecres.dolphin.ui.nav.Destination
|
||||||
|
import com.github.damontecres.dolphin.util.ExceptionHandler
|
||||||
import com.github.damontecres.dolphin.util.TrackActivityPlaybackListener
|
import com.github.damontecres.dolphin.util.TrackActivityPlaybackListener
|
||||||
import com.github.damontecres.dolphin.util.profile.PlaybackListener
|
import com.github.damontecres.dolphin.util.profile.PlaybackListener
|
||||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
|
|
@ -95,7 +96,7 @@ class PlaybackViewModel
|
||||||
this.deviceProfile = deviceProfile
|
this.deviceProfile = deviceProfile
|
||||||
val itemId = destination.itemId
|
val itemId = destination.itemId
|
||||||
val item = destination.item
|
val item = destination.item
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
viewModelScope.launch(ExceptionHandler() + Dispatchers.IO) {
|
||||||
val base = item?.data ?: api.userLibraryApi.getItem(itemId).content
|
val base = item?.data ?: api.userLibraryApi.getItem(itemId).content
|
||||||
dto = base
|
dto = base
|
||||||
|
|
||||||
|
|
@ -265,7 +266,7 @@ class PlaybackViewModel
|
||||||
|
|
||||||
fun changeAudioStream(index: Int) {
|
fun changeAudioStream(index: Int) {
|
||||||
val itemId = currentPlayback.value?.itemId ?: return
|
val itemId = currentPlayback.value?.itemId ?: return
|
||||||
viewModelScope.launch {
|
viewModelScope.launch(ExceptionHandler()) {
|
||||||
changeStreams(
|
changeStreams(
|
||||||
itemId,
|
itemId,
|
||||||
index,
|
index,
|
||||||
|
|
@ -277,7 +278,7 @@ class PlaybackViewModel
|
||||||
|
|
||||||
fun changeSubtitleStream(index: Int?) {
|
fun changeSubtitleStream(index: Int?) {
|
||||||
val itemId = currentPlayback.value?.itemId ?: return
|
val itemId = currentPlayback.value?.itemId ?: return
|
||||||
viewModelScope.launch {
|
viewModelScope.launch(ExceptionHandler()) {
|
||||||
changeStreams(
|
changeStreams(
|
||||||
itemId,
|
itemId,
|
||||||
currentPlayback.value?.audioIndex,
|
currentPlayback.value?.audioIndex,
|
||||||
|
|
|
||||||
|
|
@ -43,6 +43,7 @@ import com.github.damontecres.dolphin.ui.components.DialogParams
|
||||||
import com.github.damontecres.dolphin.ui.components.DialogPopup
|
import com.github.damontecres.dolphin.ui.components.DialogPopup
|
||||||
import com.github.damontecres.dolphin.ui.components.EditTextBox
|
import com.github.damontecres.dolphin.ui.components.EditTextBox
|
||||||
import com.github.damontecres.dolphin.ui.nav.NavigationManager
|
import com.github.damontecres.dolphin.ui.nav.NavigationManager
|
||||||
|
import com.github.damontecres.dolphin.util.ExceptionHandler
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
@Suppress("UNCHECKED_CAST")
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
|
@ -64,14 +65,14 @@ fun <T> ComposablePreference(
|
||||||
val title = stringResource(preference.title)
|
val title = stringResource(preference.title)
|
||||||
|
|
||||||
val onClick: () -> Unit = {
|
val onClick: () -> Unit = {
|
||||||
scope.launch {
|
scope.launch(ExceptionHandler()) {
|
||||||
when (preference) {
|
when (preference) {
|
||||||
else -> {}
|
else -> {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val onLongClick: () -> Unit = {
|
val onLongClick: () -> Unit = {
|
||||||
scope.launch {
|
scope.launch(ExceptionHandler()) {
|
||||||
when (preference) {
|
when (preference) {
|
||||||
else -> null
|
else -> null
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -44,6 +44,7 @@ import com.github.damontecres.dolphin.ui.nav.NavigationManager
|
||||||
import com.github.damontecres.dolphin.ui.playOnClickSound
|
import com.github.damontecres.dolphin.ui.playOnClickSound
|
||||||
import com.github.damontecres.dolphin.ui.playSoundOnFocus
|
import com.github.damontecres.dolphin.ui.playSoundOnFocus
|
||||||
import com.github.damontecres.dolphin.ui.tryRequestFocus
|
import com.github.damontecres.dolphin.ui.tryRequestFocus
|
||||||
|
import com.github.damontecres.dolphin.util.ExceptionHandler
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
val basicPreferences =
|
val basicPreferences =
|
||||||
|
|
@ -295,7 +296,7 @@ fun PreferencesContent(
|
||||||
}
|
}
|
||||||
|
|
||||||
PreferenceValidation.Valid -> {
|
PreferenceValidation.Valid -> {
|
||||||
scope.launch {
|
scope.launch(ExceptionHandler()) {
|
||||||
preferences =
|
preferences =
|
||||||
viewModel.preferenceDataStore.updateData { prefs ->
|
viewModel.preferenceDataStore.updateData { prefs ->
|
||||||
pref.setter(prefs, newValue)
|
pref.setter(prefs, newValue)
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ import com.github.damontecres.dolphin.data.JellyfinServer
|
||||||
import com.github.damontecres.dolphin.data.JellyfinServerDao
|
import com.github.damontecres.dolphin.data.JellyfinServerDao
|
||||||
import com.github.damontecres.dolphin.data.JellyfinUser
|
import com.github.damontecres.dolphin.data.JellyfinUser
|
||||||
import com.github.damontecres.dolphin.data.ServerRepository
|
import com.github.damontecres.dolphin.data.ServerRepository
|
||||||
|
import com.github.damontecres.dolphin.util.ExceptionHandler
|
||||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
|
|
@ -42,7 +43,7 @@ class SwitchUserViewModel
|
||||||
private var quickConnectJob: Job? = null
|
private var quickConnectJob: Job? = null
|
||||||
|
|
||||||
init {
|
init {
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
viewModelScope.launch(ExceptionHandler() + Dispatchers.IO) {
|
||||||
val allServers =
|
val allServers =
|
||||||
serverDao
|
serverDao
|
||||||
.getServers()
|
.getServers()
|
||||||
|
|
@ -87,7 +88,7 @@ class SwitchUserViewModel
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
viewModelScope.launch(ExceptionHandler() + Dispatchers.IO) {
|
||||||
serverRepository.currentServer?.let {
|
serverRepository.currentServer?.let {
|
||||||
val quickConnect =
|
val quickConnect =
|
||||||
jellyfin
|
jellyfin
|
||||||
|
|
@ -112,7 +113,7 @@ class SwitchUserViewModel
|
||||||
user: JellyfinUser,
|
user: JellyfinUser,
|
||||||
callback: () -> Unit,
|
callback: () -> Unit,
|
||||||
) {
|
) {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch(ExceptionHandler()) {
|
||||||
serverRepository.changeUser(server, user)
|
serverRepository.changeUser(server, user)
|
||||||
callback.invoke()
|
callback.invoke()
|
||||||
}
|
}
|
||||||
|
|
@ -125,7 +126,7 @@ class SwitchUserViewModel
|
||||||
onAuthenticated: () -> Unit,
|
onAuthenticated: () -> Unit,
|
||||||
) {
|
) {
|
||||||
quickConnectJob?.cancel()
|
quickConnectJob?.cancel()
|
||||||
viewModelScope.launch {
|
viewModelScope.launch(ExceptionHandler()) {
|
||||||
try {
|
try {
|
||||||
val api = jellyfin.createApi(baseUrl = server.url)
|
val api = jellyfin.createApi(baseUrl = server.url)
|
||||||
val authenticationResult by api.userApi.authenticateUserByName(
|
val authenticationResult by api.userApi.authenticateUserByName(
|
||||||
|
|
@ -149,7 +150,7 @@ class SwitchUserViewModel
|
||||||
onAuthenticated: () -> Unit,
|
onAuthenticated: () -> Unit,
|
||||||
) {
|
) {
|
||||||
quickConnectJob?.cancel()
|
quickConnectJob?.cancel()
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
viewModelScope.launch(ExceptionHandler() + Dispatchers.IO) {
|
||||||
try {
|
try {
|
||||||
val api = jellyfin.createApi(server.url)
|
val api = jellyfin.createApi(server.url)
|
||||||
var state =
|
var state =
|
||||||
|
|
@ -162,7 +163,7 @@ class SwitchUserViewModel
|
||||||
}
|
}
|
||||||
|
|
||||||
quickConnectJob =
|
quickConnectJob =
|
||||||
viewModelScope.launch {
|
viewModelScope.launch(ExceptionHandler()) {
|
||||||
while (!state.authenticated) {
|
while (!state.authenticated) {
|
||||||
delay(5_000L)
|
delay(5_000L)
|
||||||
state =
|
state =
|
||||||
|
|
@ -203,7 +204,7 @@ class SwitchUserViewModel
|
||||||
serverUrl: String,
|
serverUrl: String,
|
||||||
callback: () -> Unit,
|
callback: () -> Unit,
|
||||||
) {
|
) {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch(ExceptionHandler()) {
|
||||||
try {
|
try {
|
||||||
val serverInfo by jellyfin
|
val serverInfo by jellyfin
|
||||||
.createApi(serverUrl)
|
.createApi(serverUrl)
|
||||||
|
|
|
||||||
|
|
@ -131,7 +131,7 @@ class ApiRequestPager<T>(
|
||||||
get() = totalCount
|
get() = totalCount
|
||||||
|
|
||||||
private fun fetchPage(position: Int): Job =
|
private fun fetchPage(position: Int): Job =
|
||||||
scope.launch(Dispatchers.IO) {
|
scope.launch(ExceptionHandler() + Dispatchers.IO) {
|
||||||
mutex.withLock {
|
mutex.withLock {
|
||||||
val pageNumber = position / pageSize
|
val pageNumber = position / pageSize
|
||||||
if (cachedPages.getIfPresent(pageNumber) == null) {
|
if (cachedPages.getIfPresent(pageNumber) == null) {
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,42 @@
|
||||||
|
package com.github.damontecres.dolphin.util
|
||||||
|
|
||||||
|
import android.widget.Toast
|
||||||
|
import com.github.damontecres.dolphin.DolphinApplication
|
||||||
|
import kotlinx.coroutines.CancellationException
|
||||||
|
import kotlinx.coroutines.CoroutineExceptionHandler
|
||||||
|
import timber.log.Timber
|
||||||
|
import kotlin.coroutines.CoroutineContext
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A general [CoroutineExceptionHandler] which can optionally show [Toast]s when an exception is thrown
|
||||||
|
*
|
||||||
|
* Note: a toast will be shown for each parameter given, up to three!
|
||||||
|
*
|
||||||
|
* @param autoToast automatically show a toast with the exception's message
|
||||||
|
*/
|
||||||
|
class ExceptionHandler(
|
||||||
|
private val autoToast: Boolean = false,
|
||||||
|
) : CoroutineExceptionHandler {
|
||||||
|
override val key: CoroutineContext.Key<*>
|
||||||
|
get() = CoroutineExceptionHandler
|
||||||
|
|
||||||
|
override fun handleException(
|
||||||
|
context: CoroutineContext,
|
||||||
|
exception: Throwable,
|
||||||
|
) {
|
||||||
|
if (exception is CancellationException) {
|
||||||
|
// Don't log/toast cancellations
|
||||||
|
return
|
||||||
|
}
|
||||||
|
Timber.e(exception, "Exception in coroutine")
|
||||||
|
|
||||||
|
if (autoToast) {
|
||||||
|
Toast
|
||||||
|
.makeText(
|
||||||
|
DolphinApplication.instance,
|
||||||
|
"Error: ${exception.message}",
|
||||||
|
Toast.LENGTH_LONG,
|
||||||
|
).show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -80,7 +80,7 @@ class ItemPager(
|
||||||
get() = totalCount
|
get() = totalCount
|
||||||
|
|
||||||
private fun fetchPage(position: Int): Job =
|
private fun fetchPage(position: Int): Job =
|
||||||
scope.launch(Dispatchers.IO) {
|
scope.launch(ExceptionHandler() + Dispatchers.IO) {
|
||||||
mutex.withLock {
|
mutex.withLock {
|
||||||
val pageNumber = position / pageSize
|
val pageNumber = position / pageSize
|
||||||
if (cachedPages.getIfPresent(pageNumber) == null) {
|
if (cachedPages.getIfPresent(pageNumber) == null) {
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
package com.github.damontecres.dolphin.util
|
||||||
|
|
||||||
|
sealed interface LoadingState {
|
||||||
|
object Loading : LoadingState
|
||||||
|
|
||||||
|
object Success : LoadingState
|
||||||
|
|
||||||
|
data class Error(
|
||||||
|
val message: String? = null,
|
||||||
|
val exception: Exception? = null,
|
||||||
|
) : LoadingState
|
||||||
|
}
|
||||||
|
|
@ -74,7 +74,7 @@ class TrackActivityPlaybackListener(
|
||||||
fun release() {
|
fun release() {
|
||||||
task.cancel()
|
task.cancel()
|
||||||
TIMER.purge()
|
TIMER.purge()
|
||||||
coroutineScope.launch {
|
coroutineScope.launch(ExceptionHandler()) {
|
||||||
api.playStateApi.reportPlaybackStopped(
|
api.playStateApi.reportPlaybackStopped(
|
||||||
PlaybackStopInfo(
|
PlaybackStopInfo(
|
||||||
itemId = itemId,
|
itemId = itemId,
|
||||||
|
|
@ -104,7 +104,7 @@ class TrackActivityPlaybackListener(
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun saveActivity(position: Long) {
|
private fun saveActivity(position: Long) {
|
||||||
coroutineScope.launch {
|
coroutineScope.launch(ExceptionHandler()) {
|
||||||
val totalDuration = totalPlayDurationMilliseconds.get()
|
val totalDuration = totalPlayDurationMilliseconds.get()
|
||||||
val calcPosition =
|
val calcPosition =
|
||||||
(if (position >= 0) position else player.currentPosition).milliseconds
|
(if (position >= 0) position else player.currentPosition).milliseconds
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue