Skip to content

Commit

Permalink
feat(download): re-run info fetching when hit error
Browse files Browse the repository at this point in the history
  • Loading branch information
JunkFood02 committed Aug 11, 2024
1 parent d52dc96 commit 734924d
Show file tree
Hide file tree
Showing 6 changed files with 174 additions and 65 deletions.
11 changes: 4 additions & 7 deletions app/src/main/java/com/junkfood/seal/Downloader.kt
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,6 @@ import com.junkfood.seal.util.VideoClip
import com.junkfood.seal.util.VideoInfo
import com.junkfood.seal.util.toHttpsUrl
import com.yausername.youtubedl_android.YoutubeDL
import java.util.concurrent.CancellationException
import kotlin.math.roundToInt
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
Expand All @@ -36,6 +34,8 @@ import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import java.util.concurrent.CancellationException
import kotlin.math.roundToInt

/** Singleton Downloader for state holder & perform downloads, used by `Activity` & `Service` */
object Downloader {
Expand Down Expand Up @@ -437,10 +437,6 @@ object Downloader {
Log.d(TAG, "downloadVideo: id=${videoInfo.id} " + videoInfo.title)
Log.d(TAG, "notificationId: $notificationId")

// TextUtil.makeToastSuspend(
// context.getString(R.string.download_start_msg).format(videoInfo.title)
// )

NotificationUtil.notifyProgress(notificationId = notificationId, title = videoInfo.title)
return DownloadUtil.downloadVideo(
videoInfo = videoInfo,
Expand All @@ -454,7 +450,8 @@ object Downloader {
notificationId = notificationId,
progress = progress.toInt(),
text = line,
title = videoInfo.title)
title = videoInfo.title,
taskId = taskId)
}
.onFailure {
manageDownloadError(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ class QuickDownloadActivity : ComponentActivity() {
config = Config(),
preferences = preferences,
onPreferencesUpdate = { preferences = it },
onActionPosted = {
onActionPost = {
viewModel.postAction(it)
if (it !is Action.FetchFormats && it !is Action.FetchPlaylist) {
finish()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -294,7 +294,7 @@ fun DownloadPage(
config = Config(),
preferences = preferences,
onPreferencesUpdate = { preferences = it },
onActionPosted = { dialogViewModel.postAction(it) },
onActionPost = { dialogViewModel.postAction(it) },
)
}
when (selectionState) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.ExperimentalMaterialApi
import androidx.compose.material.ModalBottomSheetValue
import androidx.compose.material.icons.Icons
Expand All @@ -42,6 +43,7 @@ import androidx.compose.material.icons.filled.SettingsSuggest
import androidx.compose.material.icons.filled.VideoFile
import androidx.compose.material.icons.outlined.Cancel
import androidx.compose.material.icons.outlined.DoneAll
import androidx.compose.material.icons.outlined.ErrorOutline
import androidx.compose.material.icons.outlined.ExpandMore
import androidx.compose.material.icons.outlined.FileDownload
import androidx.compose.material.icons.outlined.MoreVert
Expand All @@ -51,6 +53,7 @@ import androidx.compose.material3.Button
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.FilledTonalButton
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
Expand All @@ -72,15 +75,21 @@ import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha
import androidx.compose.ui.platform.LocalClipboardManager
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.LocalView
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.junkfood.seal.App
import com.junkfood.seal.R
import com.junkfood.seal.ui.common.HapticFeedback.longPressHapticFeedback
import com.junkfood.seal.ui.common.motion.materialSharedAxisX
import com.junkfood.seal.ui.component.DrawerSheetSubtitle
import com.junkfood.seal.ui.component.OutlinedButtonWithIcon
Expand Down Expand Up @@ -122,6 +131,7 @@ import com.junkfood.seal.util.PreferenceUtil.updateBoolean
import com.junkfood.seal.util.PreferenceUtil.updateInt
import com.junkfood.seal.util.SUBTITLE
import com.junkfood.seal.util.THUMBNAIL
import com.junkfood.seal.util.ToastUtil
import com.junkfood.seal.util.USE_CUSTOM_AUDIO_PRESET
import com.junkfood.seal.util.VIDEO_FORMAT
import com.junkfood.seal.util.VIDEO_QUALITY
Expand Down Expand Up @@ -220,7 +230,7 @@ fun ConfigureDialog(
preferences: DownloadUtil.DownloadPreferences,
onPreferencesUpdate: (DownloadUtil.DownloadPreferences) -> Unit,
state: DownloadDialogViewModel.SheetState = Configure,
onActionPosted: (Action) -> Unit = {}
onActionPost: (Action) -> Unit = {}
) {

var showVideoPresetDialog by remember { mutableStateOf(false) }
Expand Down Expand Up @@ -276,56 +286,152 @@ fun ConfigureDialog(
SealModalBottomSheet(
sheetState = sheetState,
contentPadding = PaddingValues(),
onDismissRequest = { onActionPosted(Action.HideSheet) }) {
AnimatedContent(
targetState = state,
label = "",
transitionSpec = {
materialSharedAxisX(initialOffsetX = { it / 4 }, targetOffsetX = { -it / 4 })
}) { state ->
when (state) {
Configure -> {
ConfigurePageImpl(
url = url,
config = config,
preferences = preferences,
onPresetEdit = { type ->
when (type) {
Audio -> showAudioPresetDialog = true

Video -> showVideoPresetDialog = true

else -> {}
}
},
onConfigSave = { Config.updatePreferences(it) },
settingChips = {
AdditionalSettings(
modifier = Modifier.padding(horizontal = 16.dp),
isQuickDownload = false,
preference = preferences,
selectedType = Audio,
onPreferenceUpdate = {
onPreferencesUpdate(
DownloadUtil.DownloadPreferences
.createFromPreferences())
})
},
onActionPost = { onActionPosted(it) })
}
onDismissRequest = { onActionPost(Action.HideSheet) },
) {
ConfigureDialogContent(
modifier = modifier,
state = state,
url = url,
config = config,
preferences = preferences,
onPreferencesUpdate = onPreferencesUpdate,
onPresetEdit = { type ->
when (type) {
Audio -> showAudioPresetDialog = true

is Error -> {
Text(state.throwable.stackTrace.contentToString())
}
Video -> showVideoPresetDialog = true

else -> {
Column(modifier = Modifier.fillMaxWidth().padding(vertical = 120.dp)) {
CircularProgressIndicator(
modifier = Modifier.align(Alignment.CenterHorizontally))
}
}
else -> {}
}
},
onActionPost = onActionPost)
}
}

@Composable
private fun ErrorPage(modifier: Modifier = Modifier, state: Error, onActionPost: (Action) -> Unit) {
val view = LocalView.current
val clipboardManager = LocalClipboardManager.current
val url =
state.action.run {
when (this) {
is Action.FetchFormats -> url
is Action.FetchPlaylist -> url
else -> {
throw IllegalArgumentException()
}
}
}
Column(modifier = modifier.fillMaxWidth(), horizontalAlignment = Alignment.CenterHorizontally) {
Icon(
imageVector = Icons.Outlined.ErrorOutline,
contentDescription = null,
modifier = Modifier.size(40.dp))
Text(
text = stringResource(R.string.fetch_info_error_msg),
style = MaterialTheme.typography.titleMedium,
modifier = Modifier.padding(top = 12.dp))
Text(
text = state.throwable.message.toString(),
style = MaterialTheme.typography.bodySmall.copy(fontFamily = FontFamily.Monospace),
color = MaterialTheme.colorScheme.onSurfaceVariant,
modifier =
Modifier.padding(vertical = 16.dp, horizontal = 20.dp)
.fillMaxWidth()
.verticalScroll(rememberScrollState()),
maxLines = 20,
overflow = TextOverflow.Clip)

Row {
Button(
onClick = {
view.longPressHapticFeedback()
clipboardManager.setText(
AnnotatedString(
App.getVersionReport() + "\nURL: ${url}\n${state.throwable.message}"))
ToastUtil.makeToast(R.string.error_copied)
}) {
Text(stringResource(R.string.copy_error_report))
}
Spacer(Modifier.width(8.dp))
FilledTonalButton(onClick = { onActionPost(state.action) }) { Text("Retry") }
}
}
}

@Composable
private fun ConfigureDialogContent(
modifier: Modifier = Modifier,
url: String,
state: DownloadDialogViewModel.SheetState,
config: Config,
preferences: DownloadUtil.DownloadPreferences,
onPreferencesUpdate: (DownloadUtil.DownloadPreferences) -> Unit,
onPresetEdit: (DownloadType?) -> Unit,
onActionPost: (Action) -> Unit,
) {
AnimatedContent(
modifier = modifier,
targetState = state,
label = "",
transitionSpec = {
materialSharedAxisX(initialOffsetX = { it / 4 }, targetOffsetX = { -it / 4 })
}) { state ->
when (state) {
Configure -> {
ConfigurePageImpl(
url = url,
config = config,
preferences = preferences,
onPresetEdit = onPresetEdit,
onConfigSave = { Config.updatePreferences(it) },
settingChips = {
AdditionalSettings(
modifier = Modifier.padding(horizontal = 16.dp),
isQuickDownload = false,
preference = preferences,
selectedType = Audio,
onPreferenceUpdate = {
onPreferencesUpdate(
DownloadUtil.DownloadPreferences.createFromPreferences())
})
},
onActionPost = { onActionPost(it) })
}

is Error -> {
ErrorPage(state = state, onActionPost = onActionPost)
}

else -> {
Column(modifier = Modifier.fillMaxWidth().padding(vertical = 120.dp)) {
CircularProgressIndicator(
modifier = Modifier.align(Alignment.CenterHorizontally))
}
}
}
}
}

@OptIn(ExperimentalMaterial3Api::class)
@Preview
@Composable
private fun ErrorPreview() {
SealModalBottomSheet(
onDismissRequest = {},
sheetState =
SheetState(
skipPartiallyExpanded = true,
initialValue = SheetValue.Expanded,
density = LocalDensity.current)) {
ErrorPage(
state =
Error(
action =
Action.FetchFormats(
url = "", audioOnly = true, preferences = PreferencesMock),
throwable = Exception("Not good")),
onActionPost = {})
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ class DownloadDialogViewModel : ViewModel() {

data class Loading(val taskKey: String, val job: Job) : SheetState

data class Error(val throwable: Throwable) : SheetState
data class Error(val action: Action, val throwable: Throwable) : SheetState
}

sealed interface SheetValue {
Expand Down Expand Up @@ -96,8 +96,8 @@ class DownloadDialogViewModel : ViewModel() {
fun postAction(action: Action) {
with(action) {
when (this) {
is Action.FetchFormats -> fetchFormat(url, preferences)
is Action.FetchPlaylist -> fetchPlaylist(url)
is Action.FetchFormats -> fetchFormat(this)
is Action.FetchPlaylist -> fetchPlaylist(this)
is Action.DownloadWithPreset -> downloadWithPreset(url, preferences)
is Action.RunCommand -> runCommand(url, template)
Action.HideSheet -> hideDialog()
Expand All @@ -109,7 +109,8 @@ class DownloadDialogViewModel : ViewModel() {
}
}

private fun fetchPlaylist(url: String) {
private fun fetchPlaylist(action: Action.FetchPlaylist) {
val (url) = action
// TODO: handle downloader state
Downloader.clearErrorState()

Expand All @@ -133,17 +134,22 @@ class DownloadDialogViewModel : ViewModel() {
}
}
}
.onFailure { th -> mSheetStateFlow.update { SheetState.Error(th) } }
.onFailure { th ->
mSheetStateFlow.update { SheetState.Error(action = action, throwable = th) }
}
}
mSheetStateFlow.update { SheetState.Loading(taskKey = "FetchPlaylist_$url", job = job) }
}

private fun fetchFormat(url: String, preferences: DownloadUtil.DownloadPreferences) {
private fun fetchFormat(action: Action.FetchFormats) {
val (url, audioOnly, preferences) = action

val job =
viewModelScope.launch(Dispatchers.IO) {
DownloadUtil.fetchVideoInfoFromUrl(
url = url, preferences = preferences, taskKey = "FetchFormat_$url")
url = url,
preferences = preferences.copy(extractAudio = audioOnly),
taskKey = "FetchFormat_$url")
.onSuccess { info ->
withContext(Dispatchers.Main) {
mSelectionStateFlow.update {
Expand All @@ -154,7 +160,7 @@ class DownloadDialogViewModel : ViewModel() {
}
.onFailure { th ->
withContext(Dispatchers.Main) {
mSheetStateFlow.update { SheetState.Error(th) }
mSheetStateFlow.update { SheetState.Error(action, throwable = th) }
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -200,7 +200,7 @@
<string name="restart">Restart</string>
<string name="status_error">Error</string>
<string name="copy_link">Copy link</string>
<string name="copy_error_report">Error report</string>
<string name="copy_error_report">Copy report</string>
<string name="video_resolution">Video resolution</string>
<string name="video_file_size">Video file size</string>
<string name="export_to_clipboard">Export to clipboard</string>
Expand Down

0 comments on commit 734924d

Please sign in to comment.