import 'package:flutter/material.dart'; import 'package:fl_chart/fl_chart.dart'; import 'package:provider/provider.dart'; import '../models/wakatime_summary.dart'; import '../services/wakatime_service.dart'; import '../services/auth_service.dart'; class WakatimeScreen extends StatefulWidget { const WakatimeScreen({super.key}); @override State createState() => _WakatimeScreenState(); } class _WakatimeScreenState extends State { final WakatimeService _wakatimeService = WakatimeService(); WakatimeSummary? _summary; bool _isLoading = false; String? _error; final TextEditingController _startDateController = TextEditingController(); final TextEditingController _endDateController = TextEditingController(); int? _touchedIndex; // 预定义的颜色列表 final List _colors = [ Colors.blue, Colors.red, Colors.green, Colors.orange, Colors.purple, Colors.teal, Colors.pink, Colors.indigo, Colors.amber, Colors.cyan, Colors.lime, Colors.brown, ]; @override void initState() { super.initState(); // 设置默认日期为今天和昨天 final now = DateTime.now(); _startDateController.text = _formatDate(now.subtract(const Duration(days: 1))); _endDateController.text = _formatDate(now); } String _formatDate(DateTime date) { return '${date.year}-${date.month.toString().padLeft(2, '0')}-${date.day.toString().padLeft(2, '0')}'; } String _formatDuration(int seconds) { final hours = seconds ~/ 3600; final minutes = (seconds % 3600) ~/ 60; return '${hours}小时${minutes}分钟'; } Future _fetchData() async { final authService = Provider.of(context, listen: false); final token = authService.credentials['wakatime_token']; if (token == null || token.isEmpty) { setState(() => _error = '请在设置中配置Wakatime API Token'); return; } setState(() { _isLoading = true; _error = null; }); try { final summary = await _wakatimeService.getSummary( token, _startDateController.text, _endDateController.text, ); setState(() { _summary = summary; _isLoading = false; }); } catch (e) { setState(() { _error = e.toString(); _isLoading = false; }); } } Widget _buildLegend(List data, String Function(dynamic) getName) { return Wrap( spacing: 16, runSpacing: 8, children: data.asMap().entries.map((entry) { final index = entry.key; final item = entry.value; final isTouched = index == _touchedIndex; return Row( mainAxisSize: MainAxisSize.min, children: [ Container( width: 16, height: 16, color: _colors[index % _colors.length], ), const SizedBox(width: 8), Text( '${getName(item)}${isTouched ? ' (${_formatDuration(item.totalSeconds)})' : ''}', style: TextStyle( fontSize: 12, fontWeight: isTouched ? FontWeight.bold : FontWeight.normal, ), ), ], ); }).toList(), ); } Widget _buildPieChart( List data, String title, String Function(dynamic) getName) { return Column( children: [ Text( title, style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold), ), const SizedBox(height: 16), _buildLegend(data, getName), const SizedBox(height: 16), SizedBox( height: 300, child: PieChart( PieChartData( pieTouchData: PieTouchData( touchCallback: (FlTouchEvent event, pieTouchResponse) { setState(() { if (!event.isInterestedForInteractions || pieTouchResponse == null || pieTouchResponse.touchedSection == null) { _touchedIndex = null; return; } _touchedIndex = pieTouchResponse.touchedSection!.touchedSectionIndex; }); }, ), sections: data.asMap().entries.map((entry) { final index = entry.key; final item = entry.value; final isTouched = index == _touchedIndex; final percent = item.percent; return PieChartSectionData( value: item.totalSeconds.toDouble(), title: percent > 5 ? '${getName(item)}\n${percent.toStringAsFixed(1)}%' : '', radius: isTouched ? 110 : 100, titleStyle: const TextStyle( fontSize: 12, fontWeight: FontWeight.bold, color: Colors.white, ), color: _colors[index % _colors.length], ); }).toList(), ), ), ), ], ); } Widget _buildProjectList() { final projects = _summary!.getAggregatedProjects(); return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const Text( '项目时间分布', style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold), ), const SizedBox(height: 16), ListView.builder( shrinkWrap: true, physics: const NeverScrollableScrollPhysics(), itemCount: projects.length, itemBuilder: (context, index) { final project = projects[index]; return Card( margin: const EdgeInsets.only(bottom: 8), child: Padding( padding: const EdgeInsets.all(16), child: Row( children: [ Container( width: 16, height: 16, color: _colors[index % _colors.length], ), const SizedBox(width: 16), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( project.name, style: const TextStyle( fontSize: 16, fontWeight: FontWeight.bold, ), ), const SizedBox(height: 4), Text( _formatDuration(project.totalSeconds.toInt()), style: TextStyle( fontSize: 14, color: Colors.grey[600], ), ), ], ), ), Text( '${project.percent.toStringAsFixed(1)}%', style: const TextStyle( fontSize: 16, fontWeight: FontWeight.bold, ), ), ], ), ), ); }, ), ], ); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text('Wakatime 统计'), ), body: SingleChildScrollView( padding: const EdgeInsets.all(16.0), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // 日期选择 Row( children: [ Expanded( child: TextField( controller: _startDateController, decoration: const InputDecoration( labelText: '开始日期', hintText: 'YYYY-MM-DD', ), ), ), const SizedBox(width: 16), Expanded( child: TextField( controller: _endDateController, decoration: const InputDecoration( labelText: '结束日期', hintText: 'YYYY-MM-DD', ), ), ), ], ), const SizedBox(height: 16), // 获取数据按钮 ElevatedButton( onPressed: _isLoading ? null : _fetchData, child: _isLoading ? const CircularProgressIndicator() : const Text('获取数据'), ), const SizedBox(height: 16), // 错误提示 if (_error != null) Text( _error!, style: const TextStyle(color: Colors.red), ), // 数据展示 if (_summary != null) ...[ const Text( '总编码时间', style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold), ), Text(_summary!.cumulativeTotal.text), const SizedBox(height: 24), // 语言使用饼图 _buildPieChart( _summary!.getAggregatedLanguages(), '编程语言使用情况', (lang) => lang.name, ), const SizedBox(height: 24), // 编辑器使用饼图 _buildPieChart( _summary!.getAggregatedEditors(), '编辑器使用情况', (editor) => editor.name, ), const SizedBox(height: 24), // 项目时间分布 _buildProjectList(), ], ], ), ), ); } @override void dispose() { _startDateController.dispose(); _endDateController.dispose(); super.dispose(); } }