feat(wakatime_screen): 优化数据展示效果和用户交互(#5)
- 添加动画效果,提升用户体验 - 重新安排数据展示顺序,优先显示总编码时间 - 优化饼图和列表的展示方式 - 增加日期范围选择功能
This commit is contained in:
parent
eaff7a309b
commit
4cf1b4208a
@ -12,7 +12,8 @@ class WakatimeScreen extends StatefulWidget {
|
||||
State<WakatimeScreen> createState() => _WakatimeScreenState();
|
||||
}
|
||||
|
||||
class _WakatimeScreenState extends State<WakatimeScreen> {
|
||||
class _WakatimeScreenState extends State<WakatimeScreen>
|
||||
with SingleTickerProviderStateMixin {
|
||||
final WakatimeService _wakatimeService = WakatimeService();
|
||||
WakatimeSummary? _summary;
|
||||
bool _isLoading = false;
|
||||
@ -20,6 +21,9 @@ class _WakatimeScreenState extends State<WakatimeScreen> {
|
||||
final TextEditingController _startDateController = TextEditingController();
|
||||
final TextEditingController _endDateController = TextEditingController();
|
||||
int? _touchedIndex;
|
||||
String? _touchedChartType;
|
||||
late AnimationController _animationController;
|
||||
late Animation<double> _animation;
|
||||
|
||||
// 预定义的颜色列表
|
||||
final List<Color> _colors = [
|
||||
@ -40,11 +44,22 @@ class _WakatimeScreenState extends State<WakatimeScreen> {
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
// 设置默认日期为今天和昨天
|
||||
_animationController = AnimationController(
|
||||
duration: const Duration(milliseconds: 1500),
|
||||
vsync: this,
|
||||
);
|
||||
_animation = CurvedAnimation(
|
||||
parent: _animationController,
|
||||
curve: Curves.easeInOut,
|
||||
);
|
||||
_animationController.forward();
|
||||
|
||||
// 设置默认日期为今天和一周前
|
||||
final now = DateTime.now();
|
||||
_startDateController.text =
|
||||
_formatDate(now.subtract(const Duration(days: 1)));
|
||||
_formatDate(now.subtract(const Duration(days: 7)));
|
||||
_endDateController.text = _formatDate(now);
|
||||
_fetchData();
|
||||
}
|
||||
|
||||
String _formatDate(DateTime date) {
|
||||
@ -81,6 +96,8 @@ class _WakatimeScreenState extends State<WakatimeScreen> {
|
||||
_summary = summary;
|
||||
_isLoading = false;
|
||||
});
|
||||
_animationController.reset();
|
||||
_animationController.forward();
|
||||
} catch (e) {
|
||||
setState(() {
|
||||
_error = e.toString();
|
||||
@ -89,14 +106,16 @@ class _WakatimeScreenState extends State<WakatimeScreen> {
|
||||
}
|
||||
}
|
||||
|
||||
Widget _buildLegend(List<dynamic> data, String Function(dynamic) getName) {
|
||||
Widget _buildLegend(
|
||||
List<dynamic> data, String Function(dynamic) getName, String chartType) {
|
||||
return Wrap(
|
||||
spacing: 16,
|
||||
runSpacing: 8,
|
||||
children: data.asMap().entries.map((entry) {
|
||||
final index = entry.key;
|
||||
final item = entry.value;
|
||||
final isTouched = index == _touchedIndex;
|
||||
final isTouched =
|
||||
index == _touchedIndex && _touchedChartType == chartType;
|
||||
return Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
@ -107,7 +126,7 @@ class _WakatimeScreenState extends State<WakatimeScreen> {
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Text(
|
||||
'${getName(item)}${isTouched ? ' (${_formatDuration(item.totalSeconds)})' : ''}',
|
||||
'${getName(item)}${isTouched ? ' (${_formatDuration(item.totalSeconds.toInt())})' : ''}',
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight: isTouched ? FontWeight.bold : FontWeight.normal,
|
||||
@ -119,8 +138,8 @@ class _WakatimeScreenState extends State<WakatimeScreen> {
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildPieChart(
|
||||
List<dynamic> data, String title, String Function(dynamic) getName) {
|
||||
Widget _buildPieChart(List<dynamic> data, String title,
|
||||
String Function(dynamic) getName, String chartType) {
|
||||
return Column(
|
||||
children: [
|
||||
Text(
|
||||
@ -128,7 +147,7 @@ class _WakatimeScreenState extends State<WakatimeScreen> {
|
||||
style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
_buildLegend(data, getName),
|
||||
_buildLegend(data, getName, chartType),
|
||||
const SizedBox(height: 16),
|
||||
SizedBox(
|
||||
height: 300,
|
||||
@ -141,17 +160,20 @@ class _WakatimeScreenState extends State<WakatimeScreen> {
|
||||
pieTouchResponse == null ||
|
||||
pieTouchResponse.touchedSection == null) {
|
||||
_touchedIndex = null;
|
||||
_touchedChartType = null;
|
||||
return;
|
||||
}
|
||||
_touchedIndex =
|
||||
pieTouchResponse.touchedSection!.touchedSectionIndex;
|
||||
_touchedChartType = chartType;
|
||||
});
|
||||
},
|
||||
),
|
||||
sections: data.asMap().entries.map((entry) {
|
||||
final index = entry.key;
|
||||
final item = entry.value;
|
||||
final isTouched = index == _touchedIndex;
|
||||
final isTouched =
|
||||
index == _touchedIndex && _touchedChartType == chartType;
|
||||
final percent = item.percent;
|
||||
return PieChartSectionData(
|
||||
value: item.totalSeconds.toDouble(),
|
||||
@ -241,6 +263,71 @@ class _WakatimeScreenState extends State<WakatimeScreen> {
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildTotalTime() {
|
||||
if (_summary == null) return const SizedBox.shrink();
|
||||
|
||||
return FadeTransition(
|
||||
opacity: _animation,
|
||||
child: SlideTransition(
|
||||
position: Tween<Offset>(
|
||||
begin: const Offset(0, 0.3),
|
||||
end: Offset.zero,
|
||||
).animate(_animation),
|
||||
child: Container(
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.all(24),
|
||||
decoration: BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
colors: [
|
||||
Theme.of(context).primaryColor,
|
||||
Theme.of(context).primaryColor.withOpacity(0.8),
|
||||
],
|
||||
begin: Alignment.topLeft,
|
||||
end: Alignment.bottomRight,
|
||||
),
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Theme.of(context).primaryColor.withOpacity(0.3),
|
||||
blurRadius: 10,
|
||||
offset: const Offset(0, 5),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
const Text(
|
||||
'总编码时间',
|
||||
style: TextStyle(
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
_summary!.cumulativeTotal.text,
|
||||
style: const TextStyle(
|
||||
fontSize: 32,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
'${_startDateController.text} 至 ${_endDateController.text}',
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
color: Colors.white.withOpacity(0.8),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
@ -279,11 +366,13 @@ class _WakatimeScreenState extends State<WakatimeScreen> {
|
||||
const SizedBox(height: 16),
|
||||
|
||||
// 获取数据按钮
|
||||
ElevatedButton(
|
||||
onPressed: _isLoading ? null : _fetchData,
|
||||
child: _isLoading
|
||||
? const CircularProgressIndicator()
|
||||
: const Text('获取数据'),
|
||||
Center(
|
||||
child: ElevatedButton(
|
||||
onPressed: _isLoading ? null : _fetchData,
|
||||
child: _isLoading
|
||||
? const CircularProgressIndicator()
|
||||
: const Text('获取数据'),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
|
||||
@ -296,19 +385,11 @@ class _WakatimeScreenState extends State<WakatimeScreen> {
|
||||
|
||||
// 数据展示
|
||||
if (_summary != null) ...[
|
||||
const Text(
|
||||
'总编码时间',
|
||||
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
|
||||
),
|
||||
Text(_summary!.cumulativeTotal.text),
|
||||
_buildTotalTime(),
|
||||
const SizedBox(height: 24),
|
||||
|
||||
// 语言使用饼图
|
||||
_buildPieChart(
|
||||
_summary!.getAggregatedLanguages(),
|
||||
'编程语言使用情况',
|
||||
(lang) => lang.name,
|
||||
),
|
||||
// 项目时间分布
|
||||
_buildProjectList(),
|
||||
const SizedBox(height: 24),
|
||||
|
||||
// 编辑器使用饼图
|
||||
@ -316,11 +397,17 @@ class _WakatimeScreenState extends State<WakatimeScreen> {
|
||||
_summary!.getAggregatedEditors(),
|
||||
'编辑器使用情况',
|
||||
(editor) => editor.name,
|
||||
'editor',
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
|
||||
// 项目时间分布
|
||||
_buildProjectList(),
|
||||
// 语言使用饼图
|
||||
_buildPieChart(
|
||||
_summary!.getAggregatedLanguages(),
|
||||
'编程语言使用情况',
|
||||
(lang) => lang.name,
|
||||
'language',
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
@ -332,6 +419,7 @@ class _WakatimeScreenState extends State<WakatimeScreen> {
|
||||
void dispose() {
|
||||
_startDateController.dispose();
|
||||
_endDateController.dispose();
|
||||
_animationController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user