Initial commit

This commit is contained in:
admin1
2026-07-14 11:11:36 +08:00
commit 656499cf94
604 changed files with 119518 additions and 0 deletions
@@ -0,0 +1,165 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:forui/forui.dart';
import '../../../providers/danmaku_settings_provider.dart';
import '../../../shared/constants/breakpoints.dart';
import '../../../shared/widgets/adaptive_modal.dart';
void showBlockedKeywordsDialog(
BuildContext context,
WidgetRef ref,
List<String> keywords,
) {
showAdaptiveModal<void>(
context: context,
maxWidth: 420,
compactMaxHeightFactor: 0.9,
builder: (ctx) => _BlockedKeywordsDialog(
keywords: keywords,
onAdd: (kw) =>
ref.read(danmakuSettingsProvider.notifier).addBlockedKeyword(kw),
onRemove: (kw) =>
ref.read(danmakuSettingsProvider.notifier).removeBlockedKeyword(kw),
),
);
}
class _BlockedKeywordsDialog extends StatefulWidget {
final List<String> keywords;
final ValueChanged<String> onAdd;
final ValueChanged<String> onRemove;
const _BlockedKeywordsDialog({
required this.keywords,
required this.onAdd,
required this.onRemove,
});
@override
State<_BlockedKeywordsDialog> createState() => _BlockedKeywordsDialogState();
}
class _BlockedKeywordsDialogState extends State<_BlockedKeywordsDialog> {
late final List<String> _keywords;
final _controller = TextEditingController();
@override
void initState() {
super.initState();
_keywords = List.of(widget.keywords);
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
void _add() {
final text = _controller.text.trim();
if (text.isEmpty || _keywords.contains(text)) return;
setState(() => _keywords.add(text));
widget.onAdd(text);
_controller.clear();
}
void _remove(String kw) {
setState(() => _keywords.remove(kw));
widget.onRemove(kw);
}
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
final isCompact = Breakpoints.isCompact(MediaQuery.sizeOf(context).width);
final chipsMaxHeight =
MediaQuery.sizeOf(context).height * (isCompact ? 0.38 : 0.34);
return SingleChildScrollView(
padding: EdgeInsets.fromLTRB(24, isCompact ? 16 : 24, 24, 24),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Text('屏蔽关键词', style: theme.textTheme.titleLarge),
const SizedBox(height: 8),
Text(
'匹配到这些关键词的弹幕将被隐藏',
style: theme.textTheme.bodySmall?.copyWith(
color: theme.colorScheme.onSurfaceVariant,
),
),
const SizedBox(height: 16),
Row(
children: [
Expanded(
child: TextField(
controller: _controller,
decoration: const InputDecoration(
hintText: '输入关键词后回车添加',
isDense: true,
),
textInputAction: TextInputAction.done,
autocorrect: false,
enableSuggestions: false,
onSubmitted: (_) => _add(),
),
),
const SizedBox(width: 8),
IconButton.filledTonal(
tooltip: '添加',
onPressed: _add,
icon: const Icon(Icons.add, size: 18),
),
],
),
const SizedBox(height: 12),
if (_keywords.isEmpty)
Padding(
padding: const EdgeInsets.symmetric(vertical: 24),
child: Text(
'暂无屏蔽关键词',
textAlign: TextAlign.center,
style: TextStyle(color: theme.colorScheme.onSurfaceVariant),
),
)
else
ConstrainedBox(
constraints: BoxConstraints(maxHeight: chipsMaxHeight),
child: SingleChildScrollView(
child: Wrap(
spacing: 8,
runSpacing: 8,
children: _keywords
.map(
(kw) => Chip(
label: Text(kw),
onDeleted: () => _remove(kw),
deleteIcon: const Icon(Icons.close, size: 14),
),
)
.toList(),
),
),
),
const SizedBox(height: 20),
if (isCompact)
FButton(
onPress: () => Navigator.pop(context),
child: const Text('完成'),
)
else
Align(
alignment: Alignment.centerRight,
child: FButton(
variant: FButtonVariant.ghost,
onPress: () => Navigator.pop(context),
child: const Text('完成'),
),
),
],
),
);
}
}