Files
MEIZU-Battery-Healthy/lib/pages/changelog_page.dart
T

192 lines
5.3 KiB
Dart

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<ChangelogPage> createState() => _ChangelogPageState();
}
class _ChangelogPageState extends State<ChangelogPage>
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<double> _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<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;
});
} 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,
bottom: PreferredSize(
preferredSize: const Size.fromHeight(2.0),
child: LinearProgressIndicator(
value: displayProgress,
backgroundColor: c.divider,
valueColor: AlwaysStoppedAnimation<Color>(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),
),
);
}
}