diff --git a/lib/screens/wakatime_screen.dart b/lib/screens/wakatime_screen.dart index 022263d..8a14c91 100644 --- a/lib/screens/wakatime_screen.dart +++ b/lib/screens/wakatime_screen.dart @@ -12,7 +12,8 @@ class WakatimeScreen extends StatefulWidget { State createState() => _WakatimeScreenState(); } -class _WakatimeScreenState extends State { +class _WakatimeScreenState extends State + with SingleTickerProviderStateMixin { final WakatimeService _wakatimeService = WakatimeService(); WakatimeSummary? _summary; bool _isLoading = false; @@ -20,6 +21,9 @@ class _WakatimeScreenState extends State { final TextEditingController _startDateController = TextEditingController(); final TextEditingController _endDateController = TextEditingController(); int? _touchedIndex; + String? _touchedChartType; + late AnimationController _animationController; + late Animation _animation; // 预定义的颜色列表 final List _colors = [ @@ -40,11 +44,22 @@ class _WakatimeScreenState extends State { @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 { _summary = summary; _isLoading = false; }); + _animationController.reset(); + _animationController.forward(); } catch (e) { setState(() { _error = e.toString(); @@ -89,14 +106,16 @@ class _WakatimeScreenState extends State { } } - Widget _buildLegend(List data, String Function(dynamic) getName) { + Widget _buildLegend( + List 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 { ), 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 { ); } - Widget _buildPieChart( - List data, String title, String Function(dynamic) getName) { + Widget _buildPieChart(List data, String title, + String Function(dynamic) getName, String chartType) { return Column( children: [ Text( @@ -128,7 +147,7 @@ class _WakatimeScreenState extends State { 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 { 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 { ); } + Widget _buildTotalTime() { + if (_summary == null) return const SizedBox.shrink(); + + return FadeTransition( + opacity: _animation, + child: SlideTransition( + position: Tween( + 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 { 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 { // 数据展示 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 { _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 { void dispose() { _startDateController.dispose(); _endDateController.dispose(); + _animationController.dispose(); super.dispose(); } }