import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; // 🟢 添加这一行 import 'package:flutter/services.dart' show rootBundle; import 'package:flutter_markdown/flutter_markdown.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; Widget? _cachedMarkdown; // ---------- 滚动 ---------- final ScrollController _scrollController = ScrollController(); double _scrollProgress = 0.0; bool _isScrolling = false; // ---------- 呼吸动画(维持高刷感知) ---------- late AnimationController _breathController; late Animation _breathAnimation; // ---------- 原生刷新率控制 ---------- // 🟢 改用 static final(不是 const) 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; _cachedMarkdown = _buildMarkdown(content); }); } catch (e) { setState(() { _error = '无法加载更新日志: $e'; _isLoading = false; }); } } Widget _buildMarkdown(String content) { return Markdown( data: content, shrinkWrap: true, physics: const NeverScrollableScrollPhysics(), styleSheet: MarkdownStyleSheet( h1: const TextStyle( fontFamily: 'HarmonyOS_Sans_SC', fontSize: 24, fontWeight: FontWeight.w700, color: Colors.black, ), h2: const TextStyle( fontFamily: 'HarmonyOS_Sans_SC', fontSize: 20, fontWeight: FontWeight.w600, color: Colors.black87, ), h3: const TextStyle( fontFamily: 'HarmonyOS_Sans_SC', fontSize: 18, fontWeight: FontWeight.w600, color: Colors.black87, ), p: const TextStyle( fontFamily: 'HarmonyOS_Sans_SC', fontSize: 16, color: Colors.black87, ), listBullet: const TextStyle( fontFamily: 'HarmonyOS_Sans_SC', fontSize: 16, color: Colors.black87, ), code: const TextStyle( fontFamily: 'HarmonyOS_Sans_SC', fontSize: 14, color: Colors.black87, backgroundColor: Color(0xFFF5F5F5), ), ), ); } // ---------- Build ---------- @override Widget build(BuildContext context) { final breathOffset = _isScrolling ? _breathAnimation.value : 0.0; final displayProgress = (_scrollProgress + breathOffset).clamp(0.0, 1.0); return Scaffold( backgroundColor: Colors.white, appBar: AppBar( title: const Text( '更新日志', style: TextStyle( fontFamily: 'HarmonyOS_Sans_SC', color: Colors.black, ), ), backgroundColor: Colors.white, elevation: 0, foregroundColor: Colors.black, iconTheme: const IconThemeData(color: Colors.black), bottom: PreferredSize( preferredSize: const Size.fromHeight(2.0), child: LinearProgressIndicator( value: displayProgress, backgroundColor: Colors.grey.shade200, valueColor: const AlwaysStoppedAnimation( Color.fromRGBO(157, 212, 237, 1.0), ), minHeight: 2.0, ), ), ), body: _isLoading ? const Center(child: CircularProgressIndicator()) : _error != null ? Center( child: Text( _error!, style: const TextStyle( fontFamily: 'HarmonyOS_Sans_SC', color: Colors.grey, ), ), ) : SingleChildScrollView( controller: _scrollController, padding: const EdgeInsets.all(16.0), child: _cachedMarkdown ?? const SizedBox.shrink(), ), ); } }