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,199 @@
import 'package:canvas_danmaku/canvas_danmaku.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:forui/forui.dart';
import '../../../../core/contracts/danmaku.dart';
import '../../../../providers/danmaku_settings_provider.dart';
import '../../../../shared/widgets/app_loading_ring.dart';
import '../danmaku/danmaku_match_tile.dart';
import '../player_drawer_panel.dart';
import 'panel_setting_rows.dart';
part 'danmaku_panel_body_view.dart';
extension DanmakuPanelSettingsCanvas on DanmakuPanelSettings {
DanmakuOption toDanmakuOption({double? screenHeight}) {
final double effectiveArea;
if (maxRows > 0 && screenHeight != null && screenHeight > 0) {
final lineHeightPx = _measureDanmakuLineHeight(
fontSize: fontSize,
lineHeight: lineHeight,
fontFamily: fontFamily,
);
final computed = (maxRows + 0.5) * lineHeightPx / screenHeight;
effectiveArea = computed.clamp(0.05, 1.0);
} else {
effectiveArea = area;
}
return DanmakuOption(
opacity: 1.0,
area: effectiveArea,
fontSize: fontSize,
fontWeight: bold ? 7 : 4,
fontFamily: fontFamily,
duration: 10.0 / speed.clamp(0.1, 5.0),
lineHeight: lineHeight,
hideTop: fixedToScroll ? true : !showTop,
hideScroll: !showScroll,
hideBottom: fixedToScroll ? true : !showBottom,
strokeWidth: strokeWidth,
safeArea: true,
massiveMode: false,
);
}
}
double _measureDanmakuLineHeight({
required double fontSize,
required double lineHeight,
String? fontFamily,
}) {
final painter = TextPainter(
text: TextSpan(
text: '弹幕',
style: TextStyle(
fontSize: fontSize,
height: lineHeight,
fontFamily: fontFamily,
),
),
textDirection: TextDirection.ltr,
)..layout();
return painter.height;
}
enum _DanmakuView { main, settings, filter, search }
class DanmakuPanelBody extends StatefulWidget {
final DanmakuPanelSettings settings;
final ValueChanged<DanmakuPanelSettings> onChanged;
final DanmakuSessionState sessionState;
final bool danmakuGlobalEnabled;
final bool danmakuSourceConfigured;
final List<DanmakuMatchCandidate> matches;
final int selectedMatchIndex;
final ValueChanged<int> onMatchSelected;
final VoidCallback onOpenSearch;
final bool isLoading;
final int commentCount;
final List<String> blockedKeywords;
final ValueChanged<String> onAddKeyword;
final ValueChanged<String> onRemoveKeyword;
final VoidCallback? onRetry;
final VoidCallback? onGoToSettings;
final VoidCallback? onToggleDanmakuEnabled;
const DanmakuPanelBody({
super.key,
required this.settings,
required this.onChanged,
required this.sessionState,
this.danmakuGlobalEnabled = true,
this.danmakuSourceConfigured = true,
this.matches = const [],
this.selectedMatchIndex = -1,
required this.onMatchSelected,
required this.onOpenSearch,
this.isLoading = false,
this.commentCount = 0,
this.blockedKeywords = const [],
required this.onAddKeyword,
required this.onRemoveKeyword,
this.onRetry,
this.onGoToSettings,
this.onToggleDanmakuEnabled,
});
@override
State<DanmakuPanelBody> createState() => _DanmakuPanelBodyState();
}
class _DanmakuPanelBodyState extends State<DanmakuPanelBody> {
_DanmakuView _view = _DanmakuView.main;
bool _navForward = true;
DanmakuPanelSettings get s => widget.settings;
void _emit(DanmakuPanelSettings next) => widget.onChanged(next);
void _navigateTo(_DanmakuView view) {
if (view == _view) return;
setState(() {
_navForward = view != _DanmakuView.main;
_view = view;
});
}
void _back() => _navigateTo(_DanmakuView.main);
@override
Widget build(BuildContext context) {
return Material(
color: Colors.transparent,
child: AnimatedSwitcher(
duration: const Duration(milliseconds: 200),
switchInCurve: Curves.easeOutCubic,
switchOutCurve: Curves.easeInCubic,
transitionBuilder: (child, animation) {
final isEntering =
(child.key as ValueKey<_DanmakuView>).value == _view;
final enterOffset = _navForward
? const Offset(0.2, 0.0)
: const Offset(-0.2, 0.0);
final exitOffset = _navForward
? const Offset(-0.15, 0.0)
: const Offset(0.15, 0.0);
final slide = isEntering
? Tween<Offset>(
begin: enterOffset,
end: Offset.zero,
).animate(animation)
: Tween<Offset>(
begin: Offset.zero,
end: exitOffset,
).animate(animation);
return ClipRect(
child: SlideTransition(
position: slide,
child: FadeTransition(opacity: animation, child: child),
),
);
},
child: KeyedSubtree(
key: ValueKey<_DanmakuView>(_view),
child: _buildView(),
),
),
);
}
Widget _buildView() {
if (_view == _DanmakuView.main) {
final emptyState = _buildEmptyState();
if (emptyState != null) return emptyState;
}
return switch (_view) {
_DanmakuView.main => _buildMainMenu(),
_DanmakuView.settings => _buildSettingsDrawer(),
_DanmakuView.filter => _buildFilterDrawer(),
_DanmakuView.search => _buildSearchDrawer(),
};
}
final TextEditingController _keywordController = TextEditingController();
@override
void dispose() {
_keywordController.dispose();
super.dispose();
}
}