206 lines
6.0 KiB
Dart
206 lines
6.0 KiB
Dart
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<ChangelogPage> createState() => _ChangelogPageState();
|
||
}
|
||
|
||
class _ChangelogPageState extends State<ChangelogPage>
|
||
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<double> _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<double>(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<void> _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>(
|
||
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(),
|
||
),
|
||
);
|
||
}
|
||
}
|