import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_markdown/flutter_markdown.dart'; import '../theme/app_colors.dart'; class ChangelogPage extends StatefulWidget { const ChangelogPage({super.key}); @override State createState() => _ChangelogPageState(); } class _ChangelogPageState extends State with SingleTickerProviderStateMixin { // ---------- 数据 ---------- String _markdownContent = ''; bool _isLoading = true; String? _error; // ---------- 滚动 ---------- final ScrollController _scrollController = ScrollController(); double _scrollProgress = 0.0; bool _isScrolling = false; // ---------- 呼吸动画 ---------- late AnimationController _breathController; late Animation _breathAnimation; // ---------- 原生刷新率控制 ---------- static final _frameRateChannel = MethodChannel( 'com.lxh.battery_health/frame_rate', ); @override void initState() { super.initState(); _loadChangelog(); _frameRateChannel.invokeMethod('setHighRefreshRate'); _breathController = AnimationController( vsync: this, duration: const Duration(milliseconds: 500), )..repeat(reverse: true); _breathAnimation = Tween(begin: 0.0, end: 0.03).animate( CurvedAnimation(parent: _breathController, curve: Curves.easeInOut), ); _scrollController.addListener(() { _updateProgress(); final isScrolling = _scrollController.position.isScrollingNotifier.value; if (isScrolling != _isScrolling) { setState(() { _isScrolling = isScrolling; }); if (isScrolling) { _breathController.repeat(reverse: true); } else { _breathController.stop(); _breathController.reset(); } } }); } @override void dispose() { _frameRateChannel.invokeMethod('clearRefreshRate'); _scrollController.removeListener(_updateProgress); _scrollController.dispose(); _breathController.dispose(); super.dispose(); } void _updateProgress() { if (!_scrollController.hasClients) return; final maxScroll = _scrollController.position.maxScrollExtent; final currentScroll = _scrollController.position.pixels; final progress = maxScroll > 0 ? currentScroll / maxScroll : 0.0; setState(() { _scrollProgress = progress; }); } Future _loadChangelog() async { try { final content = await rootBundle.loadString('assets/CHANGELOG.md'); setState(() { _markdownContent = content; _isLoading = false; }); } catch (e) { setState(() { _error = '无法加载更新日志: $e'; _isLoading = false; }); } } Widget _buildMarkdown(BuildContext context, String content) { final c = context.colors; return Markdown( data: content, shrinkWrap: true, physics: const NeverScrollableScrollPhysics(), styleSheet: MarkdownStyleSheet( h1: TextStyle( fontFamily: 'HarmonyOS_Sans_SC', fontSize: 24, fontWeight: FontWeight.w700, color: c.textPrimary, ), h2: TextStyle( fontFamily: 'HarmonyOS_Sans_SC', fontSize: 20, fontWeight: FontWeight.w600, color: c.textPrimary, ), h3: TextStyle( fontFamily: 'HarmonyOS_Sans_SC', fontSize: 18, fontWeight: FontWeight.w600, color: c.textSecondary, ), p: TextStyle( fontFamily: 'HarmonyOS_Sans_SC', fontSize: 16, color: c.textSecondary, ), listBullet: TextStyle( fontFamily: 'HarmonyOS_Sans_SC', fontSize: 16, color: c.textSecondary, ), code: TextStyle( fontFamily: 'HarmonyOS_Sans_SC', fontSize: 14, color: c.textSecondary, backgroundColor: c.surface, ), ), ); } @override Widget build(BuildContext context) { final c = context.colors; final breathOffset = _isScrolling ? _breathAnimation.value : 0.0; final displayProgress = (_scrollProgress + breathOffset).clamp(0.0, 1.0); return Scaffold( appBar: AppBar( title: Text( '更新日志', style: TextStyle( fontFamily: 'HarmonyOS_Sans_SC', color: c.textPrimary, ), ), elevation: 0, foregroundColor: c.textPrimary, iconTheme: IconThemeData(color: c.textPrimary), bottom: PreferredSize( preferredSize: const Size.fromHeight(2.0), child: LinearProgressIndicator( value: displayProgress, backgroundColor: c.divider, valueColor: AlwaysStoppedAnimation(c.progressBar), minHeight: 2.0, ), ), ), body: _isLoading ? const Center(child: CircularProgressIndicator()) : _error != null ? Center( child: Text( _error!, style: TextStyle( fontFamily: 'HarmonyOS_Sans_SC', color: c.textTertiary, ), ), ) : SingleChildScrollView( controller: _scrollController, padding: const EdgeInsets.all(16.0), child: _buildMarkdown(context, _markdownContent), ), ); } }