Skip to content

Commit 734924d

Browse files
committed
feat(download): re-run info fetching when hit error
1 parent d52dc96 commit 734924d

File tree

6 files changed

+174
-65
lines changed

6 files changed

+174
-65
lines changed

app/src/main/java/com/junkfood/seal/Downloader.kt

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,6 @@ import com.junkfood.seal.util.VideoClip
2626
import com.junkfood.seal.util.VideoInfo
2727
import com.junkfood.seal.util.toHttpsUrl
2828
import com.yausername.youtubedl_android.YoutubeDL
29-
import java.util.concurrent.CancellationException
30-
import kotlin.math.roundToInt
3129
import kotlinx.coroutines.Dispatchers
3230
import kotlinx.coroutines.Job
3331
import kotlinx.coroutines.delay
@@ -36,6 +34,8 @@ import kotlinx.coroutines.flow.asStateFlow
3634
import kotlinx.coroutines.flow.combine
3735
import kotlinx.coroutines.flow.update
3836
import kotlinx.coroutines.launch
37+
import java.util.concurrent.CancellationException
38+
import kotlin.math.roundToInt
3939

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

440-
// TextUtil.makeToastSuspend(
441-
// context.getString(R.string.download_start_msg).format(videoInfo.title)
442-
// )
443-
444440
NotificationUtil.notifyProgress(notificationId = notificationId, title = videoInfo.title)
445441
return DownloadUtil.downloadVideo(
446442
videoInfo = videoInfo,
@@ -454,7 +450,8 @@ object Downloader {
454450
notificationId = notificationId,
455451
progress = progress.toInt(),
456452
text = line,
457-
title = videoInfo.title)
453+
title = videoInfo.title,
454+
taskId = taskId)
458455
}
459456
.onFailure {
460457
manageDownloadError(

app/src/main/java/com/junkfood/seal/QuickDownloadActivity.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ class QuickDownloadActivity : ComponentActivity() {
111111
config = Config(),
112112
preferences = preferences,
113113
onPreferencesUpdate = { preferences = it },
114-
onActionPosted = {
114+
onActionPost = {
115115
viewModel.postAction(it)
116116
if (it !is Action.FetchFormats && it !is Action.FetchPlaylist) {
117117
finish()

app/src/main/java/com/junkfood/seal/ui/page/download/DownloadPage.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -294,7 +294,7 @@ fun DownloadPage(
294294
config = Config(),
295295
preferences = preferences,
296296
onPreferencesUpdate = { preferences = it },
297-
onActionPosted = { dialogViewModel.postAction(it) },
297+
onActionPost = { dialogViewModel.postAction(it) },
298298
)
299299
}
300300
when (selectionState) {

app/src/main/java/com/junkfood/seal/ui/page/downloadv2/DownloadDialogV2.kt

Lines changed: 153 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ import androidx.compose.foundation.lazy.items
3333
import androidx.compose.foundation.lazy.rememberLazyListState
3434
import androidx.compose.foundation.rememberScrollState
3535
import androidx.compose.foundation.shape.RoundedCornerShape
36+
import androidx.compose.foundation.verticalScroll
3637
import androidx.compose.material.ExperimentalMaterialApi
3738
import androidx.compose.material.ModalBottomSheetValue
3839
import androidx.compose.material.icons.Icons
@@ -42,6 +43,7 @@ import androidx.compose.material.icons.filled.SettingsSuggest
4243
import androidx.compose.material.icons.filled.VideoFile
4344
import androidx.compose.material.icons.outlined.Cancel
4445
import androidx.compose.material.icons.outlined.DoneAll
46+
import androidx.compose.material.icons.outlined.ErrorOutline
4547
import androidx.compose.material.icons.outlined.ExpandMore
4648
import androidx.compose.material.icons.outlined.FileDownload
4749
import androidx.compose.material.icons.outlined.MoreVert
@@ -51,6 +53,7 @@ import androidx.compose.material3.Button
5153
import androidx.compose.material3.ButtonDefaults
5254
import androidx.compose.material3.CircularProgressIndicator
5355
import androidx.compose.material3.ExperimentalMaterial3Api
56+
import androidx.compose.material3.FilledTonalButton
5457
import androidx.compose.material3.HorizontalDivider
5558
import androidx.compose.material3.Icon
5659
import androidx.compose.material3.MaterialTheme
@@ -72,15 +75,21 @@ import androidx.compose.runtime.setValue
7275
import androidx.compose.ui.Alignment
7376
import androidx.compose.ui.Modifier
7477
import androidx.compose.ui.draw.alpha
78+
import androidx.compose.ui.platform.LocalClipboardManager
7579
import androidx.compose.ui.platform.LocalDensity
80+
import androidx.compose.ui.platform.LocalView
7681
import androidx.compose.ui.res.stringResource
82+
import androidx.compose.ui.text.AnnotatedString
83+
import androidx.compose.ui.text.font.FontFamily
7784
import androidx.compose.ui.text.style.TextAlign
7885
import androidx.compose.ui.text.style.TextOverflow
7986
import androidx.compose.ui.tooling.preview.Preview
8087
import androidx.compose.ui.unit.Dp
8188
import androidx.compose.ui.unit.dp
8289
import androidx.lifecycle.compose.collectAsStateWithLifecycle
90+
import com.junkfood.seal.App
8391
import com.junkfood.seal.R
92+
import com.junkfood.seal.ui.common.HapticFeedback.longPressHapticFeedback
8493
import com.junkfood.seal.ui.common.motion.materialSharedAxisX
8594
import com.junkfood.seal.ui.component.DrawerSheetSubtitle
8695
import com.junkfood.seal.ui.component.OutlinedButtonWithIcon
@@ -122,6 +131,7 @@ import com.junkfood.seal.util.PreferenceUtil.updateBoolean
122131
import com.junkfood.seal.util.PreferenceUtil.updateInt
123132
import com.junkfood.seal.util.SUBTITLE
124133
import com.junkfood.seal.util.THUMBNAIL
134+
import com.junkfood.seal.util.ToastUtil
125135
import com.junkfood.seal.util.USE_CUSTOM_AUDIO_PRESET
126136
import com.junkfood.seal.util.VIDEO_FORMAT
127137
import com.junkfood.seal.util.VIDEO_QUALITY
@@ -220,7 +230,7 @@ fun ConfigureDialog(
220230
preferences: DownloadUtil.DownloadPreferences,
221231
onPreferencesUpdate: (DownloadUtil.DownloadPreferences) -> Unit,
222232
state: DownloadDialogViewModel.SheetState = Configure,
223-
onActionPosted: (Action) -> Unit = {}
233+
onActionPost: (Action) -> Unit = {}
224234
) {
225235

226236
var showVideoPresetDialog by remember { mutableStateOf(false) }
@@ -276,56 +286,152 @@ fun ConfigureDialog(
276286
SealModalBottomSheet(
277287
sheetState = sheetState,
278288
contentPadding = PaddingValues(),
279-
onDismissRequest = { onActionPosted(Action.HideSheet) }) {
280-
AnimatedContent(
281-
targetState = state,
282-
label = "",
283-
transitionSpec = {
284-
materialSharedAxisX(initialOffsetX = { it / 4 }, targetOffsetX = { -it / 4 })
285-
}) { state ->
286-
when (state) {
287-
Configure -> {
288-
ConfigurePageImpl(
289-
url = url,
290-
config = config,
291-
preferences = preferences,
292-
onPresetEdit = { type ->
293-
when (type) {
294-
Audio -> showAudioPresetDialog = true
295-
296-
Video -> showVideoPresetDialog = true
297-
298-
else -> {}
299-
}
300-
},
301-
onConfigSave = { Config.updatePreferences(it) },
302-
settingChips = {
303-
AdditionalSettings(
304-
modifier = Modifier.padding(horizontal = 16.dp),
305-
isQuickDownload = false,
306-
preference = preferences,
307-
selectedType = Audio,
308-
onPreferenceUpdate = {
309-
onPreferencesUpdate(
310-
DownloadUtil.DownloadPreferences
311-
.createFromPreferences())
312-
})
313-
},
314-
onActionPost = { onActionPosted(it) })
315-
}
289+
onDismissRequest = { onActionPost(Action.HideSheet) },
290+
) {
291+
ConfigureDialogContent(
292+
modifier = modifier,
293+
state = state,
294+
url = url,
295+
config = config,
296+
preferences = preferences,
297+
onPreferencesUpdate = onPreferencesUpdate,
298+
onPresetEdit = { type ->
299+
when (type) {
300+
Audio -> showAudioPresetDialog = true
316301

317-
is Error -> {
318-
Text(state.throwable.stackTrace.contentToString())
319-
}
302+
Video -> showVideoPresetDialog = true
320303

321-
else -> {
322-
Column(modifier = Modifier.fillMaxWidth().padding(vertical = 120.dp)) {
323-
CircularProgressIndicator(
324-
modifier = Modifier.align(Alignment.CenterHorizontally))
325-
}
326-
}
304+
else -> {}
305+
}
306+
},
307+
onActionPost = onActionPost)
308+
}
309+
}
310+
311+
@Composable
312+
private fun ErrorPage(modifier: Modifier = Modifier, state: Error, onActionPost: (Action) -> Unit) {
313+
val view = LocalView.current
314+
val clipboardManager = LocalClipboardManager.current
315+
val url =
316+
state.action.run {
317+
when (this) {
318+
is Action.FetchFormats -> url
319+
is Action.FetchPlaylist -> url
320+
else -> {
321+
throw IllegalArgumentException()
322+
}
323+
}
324+
}
325+
Column(modifier = modifier.fillMaxWidth(), horizontalAlignment = Alignment.CenterHorizontally) {
326+
Icon(
327+
imageVector = Icons.Outlined.ErrorOutline,
328+
contentDescription = null,
329+
modifier = Modifier.size(40.dp))
330+
Text(
331+
text = stringResource(R.string.fetch_info_error_msg),
332+
style = MaterialTheme.typography.titleMedium,
333+
modifier = Modifier.padding(top = 12.dp))
334+
Text(
335+
text = state.throwable.message.toString(),
336+
style = MaterialTheme.typography.bodySmall.copy(fontFamily = FontFamily.Monospace),
337+
color = MaterialTheme.colorScheme.onSurfaceVariant,
338+
modifier =
339+
Modifier.padding(vertical = 16.dp, horizontal = 20.dp)
340+
.fillMaxWidth()
341+
.verticalScroll(rememberScrollState()),
342+
maxLines = 20,
343+
overflow = TextOverflow.Clip)
344+
345+
Row {
346+
Button(
347+
onClick = {
348+
view.longPressHapticFeedback()
349+
clipboardManager.setText(
350+
AnnotatedString(
351+
App.getVersionReport() + "\nURL: ${url}\n${state.throwable.message}"))
352+
ToastUtil.makeToast(R.string.error_copied)
353+
}) {
354+
Text(stringResource(R.string.copy_error_report))
355+
}
356+
Spacer(Modifier.width(8.dp))
357+
FilledTonalButton(onClick = { onActionPost(state.action) }) { Text("Retry") }
358+
}
359+
}
360+
}
361+
362+
@Composable
363+
private fun ConfigureDialogContent(
364+
modifier: Modifier = Modifier,
365+
url: String,
366+
state: DownloadDialogViewModel.SheetState,
367+
config: Config,
368+
preferences: DownloadUtil.DownloadPreferences,
369+
onPreferencesUpdate: (DownloadUtil.DownloadPreferences) -> Unit,
370+
onPresetEdit: (DownloadType?) -> Unit,
371+
onActionPost: (Action) -> Unit,
372+
) {
373+
AnimatedContent(
374+
modifier = modifier,
375+
targetState = state,
376+
label = "",
377+
transitionSpec = {
378+
materialSharedAxisX(initialOffsetX = { it / 4 }, targetOffsetX = { -it / 4 })
379+
}) { state ->
380+
when (state) {
381+
Configure -> {
382+
ConfigurePageImpl(
383+
url = url,
384+
config = config,
385+
preferences = preferences,
386+
onPresetEdit = onPresetEdit,
387+
onConfigSave = { Config.updatePreferences(it) },
388+
settingChips = {
389+
AdditionalSettings(
390+
modifier = Modifier.padding(horizontal = 16.dp),
391+
isQuickDownload = false,
392+
preference = preferences,
393+
selectedType = Audio,
394+
onPreferenceUpdate = {
395+
onPreferencesUpdate(
396+
DownloadUtil.DownloadPreferences.createFromPreferences())
397+
})
398+
},
399+
onActionPost = { onActionPost(it) })
400+
}
401+
402+
is Error -> {
403+
ErrorPage(state = state, onActionPost = onActionPost)
404+
}
405+
406+
else -> {
407+
Column(modifier = Modifier.fillMaxWidth().padding(vertical = 120.dp)) {
408+
CircularProgressIndicator(
409+
modifier = Modifier.align(Alignment.CenterHorizontally))
327410
}
328411
}
412+
}
413+
}
414+
}
415+
416+
@OptIn(ExperimentalMaterial3Api::class)
417+
@Preview
418+
@Composable
419+
private fun ErrorPreview() {
420+
SealModalBottomSheet(
421+
onDismissRequest = {},
422+
sheetState =
423+
SheetState(
424+
skipPartiallyExpanded = true,
425+
initialValue = SheetValue.Expanded,
426+
density = LocalDensity.current)) {
427+
ErrorPage(
428+
state =
429+
Error(
430+
action =
431+
Action.FetchFormats(
432+
url = "", audioOnly = true, preferences = PreferencesMock),
433+
throwable = Exception("Not good")),
434+
onActionPost = {})
329435
}
330436
}
331437

app/src/main/java/com/junkfood/seal/ui/page/downloadv2/DownloadDialogViewModel.kt

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ class DownloadDialogViewModel : ViewModel() {
3535

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

38-
data class Error(val throwable: Throwable) : SheetState
38+
data class Error(val action: Action, val throwable: Throwable) : SheetState
3939
}
4040

4141
sealed interface SheetValue {
@@ -96,8 +96,8 @@ class DownloadDialogViewModel : ViewModel() {
9696
fun postAction(action: Action) {
9797
with(action) {
9898
when (this) {
99-
is Action.FetchFormats -> fetchFormat(url, preferences)
100-
is Action.FetchPlaylist -> fetchPlaylist(url)
99+
is Action.FetchFormats -> fetchFormat(this)
100+
is Action.FetchPlaylist -> fetchPlaylist(this)
101101
is Action.DownloadWithPreset -> downloadWithPreset(url, preferences)
102102
is Action.RunCommand -> runCommand(url, template)
103103
Action.HideSheet -> hideDialog()
@@ -109,7 +109,8 @@ class DownloadDialogViewModel : ViewModel() {
109109
}
110110
}
111111

112-
private fun fetchPlaylist(url: String) {
112+
private fun fetchPlaylist(action: Action.FetchPlaylist) {
113+
val (url) = action
113114
// TODO: handle downloader state
114115
Downloader.clearErrorState()
115116

@@ -133,17 +134,22 @@ class DownloadDialogViewModel : ViewModel() {
133134
}
134135
}
135136
}
136-
.onFailure { th -> mSheetStateFlow.update { SheetState.Error(th) } }
137+
.onFailure { th ->
138+
mSheetStateFlow.update { SheetState.Error(action = action, throwable = th) }
139+
}
137140
}
138141
mSheetStateFlow.update { SheetState.Loading(taskKey = "FetchPlaylist_$url", job = job) }
139142
}
140143

141-
private fun fetchFormat(url: String, preferences: DownloadUtil.DownloadPreferences) {
144+
private fun fetchFormat(action: Action.FetchFormats) {
145+
val (url, audioOnly, preferences) = action
142146

143147
val job =
144148
viewModelScope.launch(Dispatchers.IO) {
145149
DownloadUtil.fetchVideoInfoFromUrl(
146-
url = url, preferences = preferences, taskKey = "FetchFormat_$url")
150+
url = url,
151+
preferences = preferences.copy(extractAudio = audioOnly),
152+
taskKey = "FetchFormat_$url")
147153
.onSuccess { info ->
148154
withContext(Dispatchers.Main) {
149155
mSelectionStateFlow.update {
@@ -154,7 +160,7 @@ class DownloadDialogViewModel : ViewModel() {
154160
}
155161
.onFailure { th ->
156162
withContext(Dispatchers.Main) {
157-
mSheetStateFlow.update { SheetState.Error(th) }
163+
mSheetStateFlow.update { SheetState.Error(action, throwable = th) }
158164
}
159165
}
160166
}

app/src/main/res/values/strings.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -200,7 +200,7 @@
200200
<string name="restart">Restart</string>
201201
<string name="status_error">Error</string>
202202
<string name="copy_link">Copy link</string>
203-
<string name="copy_error_report">Error report</string>
203+
<string name="copy_error_report">Copy report</string>
204204
<string name="video_resolution">Video resolution</string>
205205
<string name="video_file_size">Video file size</string>
206206
<string name="export_to_clipboard">Export to clipboard</string>

0 commit comments

Comments
 (0)