Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,9 @@
<string name="settings_media_source_sort">排序</string>
<string name="settings_media_source_stop_test">终止测试</string>
<string name="settings_media_source_start_test">开始测试</string>
<string name="settings_media_source_clear_invalid">清除无效源(%1$d)</string>
<string name="settings_media_source_clear_invalid_confirmation">确定要删除 %1$d 个无效数据源吗?</string>
<string name="settings_media_source_clear_invalid_result">已清除 %1$d 个无效源,失败 %2$d 个。</string>
<string name="settings_media_source_delete">删除数据源</string>
<string name="settings_media_source_delete_no_config">该数据源无特殊配置,删除后可以重新从模板直接添加,确认删除吗?</string>
<string name="settings_media_source_delete_with_config">该数据源有配置,删除后将丢失配置,之后从模板添加时需要重新配置,确认删除吗?</string>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,9 @@
<string name="settings_media_source_sort">排序</string>
<string name="settings_media_source_stop_test">終止測試</string>
<string name="settings_media_source_start_test">開始測試</string>
<string name="settings_media_source_clear_invalid">清除無效源(%1$d)</string>
<string name="settings_media_source_clear_invalid_confirmation">確定要刪除 %1$d 個無效數據源嗎?</string>
<string name="settings_media_source_clear_invalid_result">已清除 %1$d 個無效源,失敗 %2$d 個。</string>
<string name="settings_media_source_delete">刪除數據源</string>
<string name="settings_media_source_delete_no_config">該數據源無特殊配置,刪除後可以重新從模板直接添加,確認刪除嗎?</string>
<string name="settings_media_source_delete_with_config">該數據源有配置,刪除後將丟失配置,之後從模板添加時需要重新配置,確認刪除嗎?</string>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,9 @@
<string name="settings_media_source_sort">排序</string>
<string name="settings_media_source_stop_test">終止測試</string>
<string name="settings_media_source_start_test">開始測試</string>
<string name="settings_media_source_clear_invalid">清除無效來源(%1$d)</string>
<string name="settings_media_source_clear_invalid_confirmation">確定要刪除 %1$d 個無效資料來源嗎?</string>
<string name="settings_media_source_clear_invalid_result">已清除 %1$d 個無效來源,失敗 %2$d 個。</string>
<string name="settings_media_source_delete">刪除資料源</string>
<string name="settings_media_source_delete_no_config">該資料源無特殊配置,刪除後可以重新從模板直接添加,確認刪除嗎?</string>
<string name="settings_media_source_delete_with_config">該資料源有配置,刪除後將遺失配置,之後從模板添加時需要重新配置,確認刪除嗎?</string>
Expand Down
3 changes: 3 additions & 0 deletions app/shared/app-lang/src/androidMain/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,9 @@
<string name="settings_media_source_sort">Sort</string>
<string name="settings_media_source_stop_test">Stop test</string>
<string name="settings_media_source_start_test">Start test</string>
<string name="settings_media_source_clear_invalid">Clear invalid sources (%1$d)</string>
<string name="settings_media_source_clear_invalid_confirmation">Delete %1$d invalid data sources?</string>
<string name="settings_media_source_clear_invalid_result">Removed %1$d invalid sources, %2$d failed.</string>
<string name="settings_media_source_delete">Delete data source</string>
<string name="settings_media_source_delete_no_config">This data source has no special configuration. It can be re-added from a template after deletion. Confirm deletion?</string>
<string name="settings_media_source_delete_with_config">This data source has configurations. Deleting it will lose those configurations. They must be reconfigured when re-added from a template. Confirm deletion?</string>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,8 @@ import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
import androidx.compose.runtime.Stable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
Expand All @@ -78,10 +80,14 @@ import me.him188.ani.app.ui.foundation.LocalPlatform
import me.him188.ani.app.ui.foundation.animation.AniAnimatedVisibility
import me.him188.ani.app.ui.foundation.ifThen
import me.him188.ani.app.ui.foundation.interaction.onRightClickIfSupported
import me.him188.ani.app.ui.foundation.widgets.LocalToaster
import me.him188.ani.app.ui.lang.Lang
import me.him188.ani.app.ui.lang.settings_media_source_add
import me.him188.ani.app.ui.lang.settings_media_source_cancel
import me.him188.ani.app.ui.lang.settings_media_source_cancel_sort
import me.him188.ani.app.ui.lang.settings_media_source_clear_invalid
import me.him188.ani.app.ui.lang.settings_media_source_clear_invalid_confirmation
import me.him188.ani.app.ui.lang.settings_media_source_clear_invalid_result
import me.him188.ani.app.ui.lang.settings_media_source_delete
import me.him188.ani.app.ui.lang.settings_media_source_delete_can_readd
import me.him188.ani.app.ui.lang.settings_media_source_delete_confirm
Expand All @@ -99,6 +105,7 @@ import me.him188.ani.app.ui.lang.settings_media_source_select_template
import me.him188.ani.app.ui.lang.settings_media_source_sort
import me.him188.ani.app.ui.lang.settings_media_source_start_test
import me.him188.ani.app.ui.lang.settings_media_source_stop_test
import me.him188.ani.app.ui.settings.framework.ConnectionTestResult
import me.him188.ani.app.ui.settings.framework.ConnectionTesterResultIndicator
import me.him188.ani.app.ui.settings.framework.components.SettingsScope
import me.him188.ani.app.ui.settings.framework.components.TextButtonItem
Expand All @@ -112,6 +119,7 @@ import org.burnoutcrew.reorderable.ReorderableItem
import org.burnoutcrew.reorderable.detectReorder
import org.burnoutcrew.reorderable.detectReorderAfterLongPress
import org.burnoutcrew.reorderable.reorderable
import org.jetbrains.compose.resources.getString
import org.jetbrains.compose.resources.stringResource

@Stable
Expand All @@ -127,7 +135,9 @@ internal fun SettingsScope.MediaSourceGroup(
) {
val navigator = LocalNavigator.current
val uiScope = rememberCoroutineScope()
val toaster = LocalToaster.current
var showSelectTemplate by remember { mutableStateOf(false) }
var showConfirmClearDialog by rememberSaveable { mutableStateOf(false) }
if (showSelectTemplate) {
// 选一个数据源来添加
SelectMediaSourceTemplateDialog(
Expand Down Expand Up @@ -170,6 +180,31 @@ internal fun SettingsScope.MediaSourceGroup(
val sorter = rememberSorterState<MediaSourcePresentation>(
onComplete = { list -> state.reorderMediaSources(newOrder = list.map { it.instanceId }) },
)
val isEditTaskRunning by edit.editTaskRunningFlow.collectAsState()
val canMutateMediaSources = !isEditTaskRunning
val invalidSources by remember {
derivedStateOf {
// 不能用 remember(state.mediaSources) 缓存:tester.result 是原地变化,不会改变列表 identity。
state.mediaSources.filter {
it.connectionTester.result == ConnectionTestResult.FAILED
}
}
}
val isEditingUiActive = edit.editMediaSourceState != null || showSelectTemplate
val showClearButton = remember(
invalidSources,
state.mediaSourceTesters.anyTesting,
isEditTaskRunning,
sorter.isSorting,
isEditingUiActive,
) {
invalidSources.isNotEmpty() &&
!state.mediaSourceTesters.anyTesting &&
!isEditTaskRunning &&
!sorter.isSorting &&
!isEditingUiActive
}
val platform = LocalPlatform.current

Group(
title = { Text(stringResource(Lang.settings_media_source_list, state.mediaSources.size)) },
Expand All @@ -196,6 +231,7 @@ internal fun SettingsScope.MediaSourceGroup(
edit.cancelEdit()
showSelectTemplate = true
},
enabled = canMutateMediaSources,
) {
Icon(Icons.Rounded.Add, contentDescription = stringResource(Lang.settings_media_source_add))
}
Expand Down Expand Up @@ -229,6 +265,58 @@ internal fun SettingsScope.MediaSourceGroup(
}
},
) {
if (showConfirmClearDialog) {
AlertDialog(
onDismissRequest = { showConfirmClearDialog = false },
icon = { Icon(Icons.Rounded.Delete, null, tint = MaterialTheme.colorScheme.error) },
title = {
Text(
stringResource(
Lang.settings_media_source_clear_invalid,
invalidSources.size,
),
)
},
text = {
Text(
stringResource(
Lang.settings_media_source_clear_invalid_confirmation,
invalidSources.size,
),
)
},
confirmButton = {
TextButton(
{
val targets = invalidSources.toList()
edit.deleteMediaSources(targets) { result ->
toaster.toast(
getString(
Lang.settings_media_source_clear_invalid_result,
result.deleted,
result.failed,
),
)
}
showConfirmClearDialog = false
},
) {
Text(
stringResource(Lang.settings_media_source_delete_confirm),
color = MaterialTheme.colorScheme.error,
)
}
},
dismissButton = {
TextButton(
{
showConfirmClearDialog = false
},
) { Text(stringResource(Lang.settings_media_source_cancel)) }
},
)
}

Box {
Column(
Modifier
Expand All @@ -246,7 +334,6 @@ internal fun SettingsScope.MediaSourceGroup(
edit.startEditing(item)
}
}
val platform = LocalPlatform.current

var showMoreDropdown by remember { mutableStateOf(false) }
var showConfirmDeletionDialog by rememberSaveable { mutableStateOf(false) }
Expand All @@ -268,6 +355,7 @@ internal fun SettingsScope.MediaSourceGroup(
edit.deleteMediaSource(item);
showConfirmDeletionDialog = false
},
enabled = canMutateMediaSources,
) {
Text(
stringResource(Lang.settings_media_source_delete_confirm),
Expand All @@ -288,6 +376,7 @@ internal fun SettingsScope.MediaSourceGroup(
MediaSourceItem(
item,
Modifier.combinedClickable(
enabled = canMutateMediaSources,
onClickLabel = "编辑",
onLongClick = {
if (platform.isMobile()) {
Expand All @@ -297,7 +386,9 @@ internal fun SettingsScope.MediaSourceGroup(
onLongClickLabel = "开始排序",
onClick = startEditing,
).onRightClickIfSupported {
showMoreDropdown = true
if (canMutateMediaSources) {
showMoreDropdown = true
}
},
) {
IconButton({}, enabled = false) { // 放在 button 里保持 padding 一致
Expand All @@ -308,7 +399,10 @@ internal fun SettingsScope.MediaSourceGroup(
}

Box {
IconButton(onClick = { showMoreDropdown = true }) {
IconButton(
onClick = { showMoreDropdown = true },
enabled = canMutateMediaSources,
) {
Icon(
Icons.Rounded.MoreVert,
contentDescription = "更多",
Expand All @@ -317,6 +411,7 @@ internal fun SettingsScope.MediaSourceGroup(

MoreOptionsDropdown(
showMoreDropdown,
enabled = canMutateMediaSources,
onDismissRequest = { showMoreDropdown = false },
onDeleteRequest = { showConfirmDeletionDialog = true },
item,
Expand Down Expand Up @@ -368,6 +463,18 @@ internal fun SettingsScope.MediaSourceGroup(

HorizontalDividerItem()

if (showClearButton) {
TextButtonItem(
onClick = { showConfirmClearDialog = true },
title = {
Text(
stringResource(Lang.settings_media_source_clear_invalid, invalidSources.size),
color = MaterialTheme.colorScheme.error,
)
},
)
HorizontalDividerItem()
}

TextButtonItem(
onClick = {
Expand Down Expand Up @@ -471,6 +578,7 @@ internal fun SettingsScope.MediaSourceItem(
@Composable
private fun MoreOptionsDropdown(
showMore: Boolean,
enabled: Boolean,
onDismissRequest: () -> Unit,
onDeleteRequest: () -> Unit,
item: MediaSourcePresentation,
Expand All @@ -482,6 +590,7 @@ private fun MoreOptionsDropdown(
onDismissRequest = onDismissRequest,
) {
DropdownMenuItem(
enabled = enabled,
leadingIcon = {
if (item.isEnabled) {
Icon(Icons.Rounded.VisibilityOff, null)
Expand All @@ -502,6 +611,7 @@ private fun MoreOptionsDropdown(
},
)
DropdownMenuItem(
enabled = enabled,
leadingIcon = { Icon(Icons.Rounded.Edit, null) },
text = { Text(stringResource(Lang.settings_media_source_edit)) }, // 直接点击数据源一行也可以编辑, 但还是在这里放一个按钮以免有人不知道
onClick = {
Expand All @@ -510,6 +620,7 @@ private fun MoreOptionsDropdown(
},
)
DropdownMenuItem(
enabled = enabled,
leadingIcon = { Icon(Icons.Rounded.Delete, null, tint = MaterialTheme.colorScheme.error) },
text = {
Text(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
Expand Down Expand Up @@ -147,6 +148,11 @@ class EditMediaSourceState(
private val onSetEnabled: suspend (instanceId: String, enabled: Boolean) -> Unit,
private val backgroundScope: CoroutineScope,
) {
data class DeleteMediaSourcesResult(
val deleted: Int,
val failed: Int,
)

var editMediaSourceState by mutableStateOf<EditingMediaSource?>(null)
private set

Expand Down Expand Up @@ -185,6 +191,7 @@ class EditMediaSourceState(
}

private val editTasker = MonoTasker(backgroundScope)
val editTaskRunningFlow get() = editTasker.isRunning

Comment thread
BagunoMushi marked this conversation as resolved.
fun confirmEdit(state: EditingMediaSource): Job {
return editTasker.launch {
Expand Down Expand Up @@ -224,6 +231,35 @@ class EditMediaSourceState(
}
}

fun deleteMediaSources(
items: List<MediaSourcePresentation>,
onComplete: suspend (DeleteMediaSourcesResult) -> Unit = {},
) {
editTasker.launch {
val targets = items.distinctBy { it.instanceId }
var deleted = 0
var failed = 0
targets.forEach { item ->
runCatching {
onDelete(item.instanceId)
}.onSuccess {
deleted++
}.onFailure { throwable ->
if (throwable is CancellationException) throw throwable
failed++
}
}
withContext(Dispatchers.Main) {
onComplete(
DeleteMediaSourcesResult(
deleted = deleted,
failed = failed,
),
)
}
}
}

fun toggleMediaSourceEnabled(item: MediaSourcePresentation, enabled: Boolean) {
editTasker.launch {
onSetEnabled(item.instanceId, enabled)
Expand Down
Loading