217 lines
7.7 KiB
Dart
217 lines
7.7 KiB
Dart
|
|
import 'package:flutter/material.dart';
|
||
|
|
import 'package:forui/forui.dart';
|
||
|
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||
|
|
|
||
|
|
import '../../../core/contracts/danmaku.dart';
|
||
|
|
import '../../../providers/danmaku_settings_provider.dart';
|
||
|
|
import '../../../shared/widgets/setting_select.dart';
|
||
|
|
import '../utils/settings_labels.dart';
|
||
|
|
import '../widgets/blocked_keywords_dialog.dart';
|
||
|
|
import '../widgets/danmaku_sources_dialog.dart';
|
||
|
|
import '../widgets/settings_card.dart';
|
||
|
|
import '../widgets/settings_stepper_row.dart';
|
||
|
|
|
||
|
|
class DanmakuSettingsTab extends ConsumerWidget {
|
||
|
|
const DanmakuSettingsTab({super.key});
|
||
|
|
|
||
|
|
@override
|
||
|
|
Widget build(BuildContext context, WidgetRef ref) {
|
||
|
|
final danmakuSettings = ref.watch(danmakuSettingsProvider);
|
||
|
|
|
||
|
|
return SettingsTabScroll(
|
||
|
|
children: [
|
||
|
|
danmakuSettings.when(
|
||
|
|
data: (data) => Column(
|
||
|
|
children: [
|
||
|
|
SettingsCard(
|
||
|
|
children: [
|
||
|
|
SettingRow(
|
||
|
|
title: '全局启用弹幕',
|
||
|
|
trailing: FSwitch(
|
||
|
|
value: data.enabled,
|
||
|
|
onChange: (v) => ref
|
||
|
|
.read(danmakuSettingsProvider.notifier)
|
||
|
|
.setEnabled(v),
|
||
|
|
),
|
||
|
|
),
|
||
|
|
],
|
||
|
|
),
|
||
|
|
if (data.enabled) ...[
|
||
|
|
_buildDanmakuDisplayCard(context, ref, data.panelSettings),
|
||
|
|
SettingsCard(
|
||
|
|
children: [
|
||
|
|
SettingRow(
|
||
|
|
title: '简繁转换',
|
||
|
|
subtitle: '仅对兼容原版 dandanplay 的源生效',
|
||
|
|
trailing: SettingSelect<ChineseConvertMode>(
|
||
|
|
value: data.chineseConvert,
|
||
|
|
options: ChineseConvertMode.values,
|
||
|
|
labelOf: chineseConvertLabel,
|
||
|
|
onChanged: (v) {
|
||
|
|
if (v != null) {
|
||
|
|
ref
|
||
|
|
.read(danmakuSettingsProvider.notifier)
|
||
|
|
.setChineseConvert(v);
|
||
|
|
}
|
||
|
|
},
|
||
|
|
),
|
||
|
|
),
|
||
|
|
SettingRow(
|
||
|
|
title: '弹幕源',
|
||
|
|
subtitle: danmakuSourcesSummary(data.sources),
|
||
|
|
trailing: const Icon(Icons.chevron_right, size: 18),
|
||
|
|
onTap: () =>
|
||
|
|
showDanmakuSourcesDialog(context, data.sources),
|
||
|
|
),
|
||
|
|
SettingRow(
|
||
|
|
title: '合并重复弹幕',
|
||
|
|
trailing: FSwitch(
|
||
|
|
value: data.mergeDuplicates,
|
||
|
|
onChange: (v) => ref
|
||
|
|
.read(danmakuSettingsProvider.notifier)
|
||
|
|
.setMergeDuplicates(v),
|
||
|
|
),
|
||
|
|
),
|
||
|
|
SettingRow(
|
||
|
|
title: '屏蔽关键词',
|
||
|
|
subtitle: data.blockedKeywords.isEmpty
|
||
|
|
? '未设置'
|
||
|
|
: '${data.blockedKeywords.length} 个',
|
||
|
|
trailing: const Icon(Icons.chevron_right, size: 18),
|
||
|
|
onTap: () => showBlockedKeywordsDialog(
|
||
|
|
context,
|
||
|
|
ref,
|
||
|
|
data.blockedKeywords,
|
||
|
|
),
|
||
|
|
),
|
||
|
|
],
|
||
|
|
),
|
||
|
|
],
|
||
|
|
],
|
||
|
|
),
|
||
|
|
loading: () => const SizedBox.shrink(),
|
||
|
|
error: (e, _) => settingsLoadError(e),
|
||
|
|
),
|
||
|
|
],
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
Widget _buildDanmakuDisplayCard(
|
||
|
|
BuildContext context,
|
||
|
|
WidgetRef ref,
|
||
|
|
DanmakuPanelSettings p,
|
||
|
|
) {
|
||
|
|
void emit(DanmakuPanelSettings next) =>
|
||
|
|
ref.read(danmakuSettingsProvider.notifier).updatePanelSettings(next);
|
||
|
|
|
||
|
|
double inc(double v, double step, double min, double max) =>
|
||
|
|
double.parse((v + step).clamp(min, max).toStringAsFixed(2));
|
||
|
|
double dec(double v, double step, double min, double max) =>
|
||
|
|
double.parse((v - step).clamp(min, max).toStringAsFixed(2));
|
||
|
|
|
||
|
|
String opacityStr(double v) =>
|
||
|
|
(v * 10).round() == 10 ? '1.0' : '0.${(v * 10).round()}';
|
||
|
|
|
||
|
|
return Column(
|
||
|
|
children: [
|
||
|
|
SettingsCard(
|
||
|
|
children: [
|
||
|
|
StepperRow(
|
||
|
|
title: '字号大小',
|
||
|
|
display: p.fontSize.round().toString(),
|
||
|
|
onDecrement: p.fontSize > 12
|
||
|
|
? () => emit(p.copyWith(fontSize: dec(p.fontSize, 2, 12, 40)))
|
||
|
|
: null,
|
||
|
|
onIncrement: p.fontSize < 40
|
||
|
|
? () => emit(p.copyWith(fontSize: inc(p.fontSize, 2, 12, 40)))
|
||
|
|
: null,
|
||
|
|
),
|
||
|
|
StepperRow(
|
||
|
|
title: '基础速度',
|
||
|
|
display: '${p.speed.toStringAsFixed(2)}x',
|
||
|
|
onDecrement: p.speed > 0.25
|
||
|
|
? () => emit(p.copyWith(speed: dec(p.speed, 0.25, 0.25, 2.0)))
|
||
|
|
: null,
|
||
|
|
onIncrement: p.speed < 2.0
|
||
|
|
? () => emit(p.copyWith(speed: inc(p.speed, 0.25, 0.25, 2.0)))
|
||
|
|
: null,
|
||
|
|
),
|
||
|
|
StepperRow(
|
||
|
|
title: '不透明度',
|
||
|
|
display: opacityStr(p.opacity),
|
||
|
|
onDecrement: p.opacity > 0.1
|
||
|
|
? () =>
|
||
|
|
emit(p.copyWith(opacity: dec(p.opacity, 0.1, 0.1, 1.0)))
|
||
|
|
: null,
|
||
|
|
onIncrement: p.opacity < 1.0
|
||
|
|
? () =>
|
||
|
|
emit(p.copyWith(opacity: inc(p.opacity, 0.1, 0.1, 1.0)))
|
||
|
|
: null,
|
||
|
|
),
|
||
|
|
StepperRow(
|
||
|
|
title: '描边大小',
|
||
|
|
display: p.strokeWidth.toStringAsFixed(1),
|
||
|
|
onDecrement: p.strokeWidth > 0.0
|
||
|
|
? () => emit(
|
||
|
|
p.copyWith(
|
||
|
|
strokeWidth: dec(p.strokeWidth, 0.5, 0.0, 3.0),
|
||
|
|
),
|
||
|
|
)
|
||
|
|
: null,
|
||
|
|
onIncrement: p.strokeWidth < 3.0
|
||
|
|
? () => emit(
|
||
|
|
p.copyWith(
|
||
|
|
strokeWidth: inc(p.strokeWidth, 0.5, 0.0, 3.0),
|
||
|
|
),
|
||
|
|
)
|
||
|
|
: null,
|
||
|
|
),
|
||
|
|
StepperRow(
|
||
|
|
title: '行间距',
|
||
|
|
display: p.lineHeight.toStringAsFixed(1),
|
||
|
|
onDecrement: p.lineHeight > 1.0
|
||
|
|
? () => emit(
|
||
|
|
p.copyWith(lineHeight: dec(p.lineHeight, 0.1, 1.0, 3.0)),
|
||
|
|
)
|
||
|
|
: null,
|
||
|
|
onIncrement: p.lineHeight < 3.0
|
||
|
|
? () => emit(
|
||
|
|
p.copyWith(lineHeight: inc(p.lineHeight, 0.1, 1.0, 3.0)),
|
||
|
|
)
|
||
|
|
: null,
|
||
|
|
),
|
||
|
|
StepperRow(
|
||
|
|
title: '弹幕行数',
|
||
|
|
display: p.maxRows == 0 ? '自动' : '${p.maxRows} 行',
|
||
|
|
onDecrement: p.maxRows > 0
|
||
|
|
? () => emit(p.copyWith(maxRows: p.maxRows - 1))
|
||
|
|
: null,
|
||
|
|
onIncrement: p.maxRows < 10
|
||
|
|
? () => emit(p.copyWith(maxRows: p.maxRows + 1))
|
||
|
|
: null,
|
||
|
|
),
|
||
|
|
],
|
||
|
|
),
|
||
|
|
SettingsCard(
|
||
|
|
children: [
|
||
|
|
SettingRow(
|
||
|
|
title: '粗体',
|
||
|
|
trailing: FSwitch(
|
||
|
|
value: p.bold,
|
||
|
|
onChange: (v) => emit(p.copyWith(bold: v)),
|
||
|
|
),
|
||
|
|
),
|
||
|
|
SettingRow(
|
||
|
|
title: '固定弹幕转滚动',
|
||
|
|
trailing: FSwitch(
|
||
|
|
value: p.fixedToScroll,
|
||
|
|
onChange: (v) => emit(p.copyWith(fixedToScroll: v)),
|
||
|
|
),
|
||
|
|
),
|
||
|
|
],
|
||
|
|
),
|
||
|
|
],
|
||
|
|
);
|
||
|
|
}
|
||
|
|
}
|