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 onChanged; final DanmakuSessionState sessionState; final bool danmakuGlobalEnabled; final bool danmakuSourceConfigured; final List matches; final int selectedMatchIndex; final ValueChanged onMatchSelected; final VoidCallback onOpenSearch; final bool isLoading; final int commentCount; final List blockedKeywords; final ValueChanged onAddKeyword; final ValueChanged 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 createState() => _DanmakuPanelBodyState(); } class _DanmakuPanelBodyState extends State { _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( begin: enterOffset, end: Offset.zero, ).animate(animation) : Tween( 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(); } }