import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import '../services/auth_service.dart'; import '../services/github_service.dart'; import '../models/github_event.dart'; import 'package:timeago/timeago.dart' as timeago; class GithubCard extends StatefulWidget { const GithubCard({super.key}); @override State createState() => _GithubCardState(); } class _GithubCardState extends State { final GithubService _githubService = GithubService(); List _events = []; bool _isLoading = false; String? _error; String _getEventTypeText(String type) { switch (type) { case 'PushEvent': return '推送代码'; case 'CreateEvent': return '创建仓库'; case 'IssuesEvent': return 'Issue 操作'; case 'PullRequestEvent': return 'Pull Request 操作'; case 'ForkEvent': return 'Fork 仓库'; case 'WatchEvent': return '关注仓库'; case 'StarEvent': return 'Star 仓库'; default: return type; } } Widget _buildCommitContent(List commits) { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: commits.map((commit) { return Padding( padding: const EdgeInsets.symmetric(vertical: 4.0), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( commit.message, style: const TextStyle(fontWeight: FontWeight.bold), ), Text( '作者: ${commit.author.name} <${commit.author.email}>', style: const TextStyle(fontSize: 12, color: Colors.grey), ), ], ), ); }).toList(), ); } Future _fetchEvents() async { setState(() { _isLoading = true; _error = null; }); try { final authService = Provider.of(context, listen: false); final username = authService.credentials['github_username']; final token = authService.credentials['github_token']; if (username == null || token == null) { throw Exception('请在设置中配置 GitHub 用户名和 Token'); } final events = await _githubService.getUserEvents(username, token); setState(() { _events = events; _isLoading = false; }); } catch (e) { setState(() { _error = e.toString(); _isLoading = false; }); } } @override void initState() { super.initState(); _fetchEvents(); } @override Widget build(BuildContext context) { return Card( elevation: 4, shadowColor: Colors.deepPurple.withOpacity(0.3), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(16), side: BorderSide(color: Colors.deepPurple.withOpacity(0.1)), ), margin: const EdgeInsets.symmetric(vertical: 8.0), child: Padding( padding: const EdgeInsets.all(16.0), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ const Text( 'GitHub 最近活动', style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold), ), IconButton( icon: const Icon(Icons.refresh), onPressed: _isLoading ? null : _fetchEvents, ), ], ), if (_isLoading) const Center( child: Padding( padding: EdgeInsets.all(16.0), child: CircularProgressIndicator(), ), ) else if (_error != null) Center( child: Padding( padding: const EdgeInsets.all(16.0), child: Text( _error!, style: const TextStyle(color: Colors.red), ), ), ) else if (_events.isEmpty) const Center( child: Padding( padding: EdgeInsets.all(16.0), child: Text('暂无活动记录'), ), ) else Expanded( child: ListView.builder( itemCount: _events.length, itemBuilder: (context, index) { final event = _events[index]; return Card( margin: const EdgeInsets.symmetric(vertical: 4.0), child: Padding( padding: const EdgeInsets.all(12.0), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ CircleAvatar( backgroundImage: NetworkImage( event.actor.avatarUrl, ), ), const SizedBox(width: 8), Text( event.actor.login, style: const TextStyle( fontWeight: FontWeight.bold, ), ), const SizedBox(width: 8), Text(_getEventTypeText(event.type)), ], ), const SizedBox(height: 8), Text( event.repo.name, style: const TextStyle( color: Colors.blue, fontWeight: FontWeight.bold, ), ), if (event.type == 'PushEvent' && event.payload.commits != null) Padding( padding: const EdgeInsets.only(top: 8.0), child: _buildCommitContent( event.payload.commits!, ), ), const SizedBox(height: 8), Text( '时间: ${timeago.format(event.createdAt, locale: 'zh_CN')}', style: const TextStyle( fontSize: 12, color: Colors.grey, ), ), ], ), ), ); }, ), ), ], ), ), ); } }