192 lines
5.3 KiB
Dart
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),
|
|
),
|
|
);
|
|
}
|
|
}
|