import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'package:http/http.dart' as http; import 'dart:convert'; import '../services/auth_service.dart'; import 'package:timeago/timeago.dart' as timeago; class LeetCodeCard extends StatefulWidget { const LeetCodeCard({super.key}); @override State createState() => _LeetCodeCardState(); } class _LeetCodeCardState extends State { List _submissions = []; Map? _progress; bool _isLoading = false; String? _error; Future _fetchData() async { setState(() { _isLoading = true; _error = null; }); try { final authService = Provider.of(context, listen: false); final cookie = authService.credentials['leetcode_cookie']; final userSlug = authService.credentials['leetcode_user_slug']; if (cookie == null || cookie.isEmpty) { throw Exception('未配置 LeetCode Cookie'); } if (userSlug == null || userSlug.isEmpty) { throw Exception('未配置 LeetCode User Slug'); } // 获取提交记录 final submissionsResponse = await http.post( Uri.parse('https://leetcode.cn/graphql/noj-go/'), headers: {'Content-Type': 'application/json', 'Cookie': cookie}, body: jsonEncode({ 'query': ''' query recentAcSubmissions(\$userSlug: String!) { recentACSubmissions(userSlug: \$userSlug) { submissionId submitTime question { title translatedTitle titleSlug questionFrontendId } } } ''', 'variables': {'userSlug': userSlug}, 'operationName': 'recentAcSubmissions', }), ); // 获取进度数据 final progressResponse = await http.post( Uri.parse('https://leetcode.cn/graphql/'), headers: {'Content-Type': 'application/json', 'Cookie': cookie}, body: jsonEncode({ 'query': ''' query userProfileUserQuestionProgressV2(\$userSlug: String!) { userProfileUserQuestionProgressV2(userSlug: \$userSlug) { numAcceptedQuestions { count difficulty } numFailedQuestions { count difficulty } numUntouchedQuestions { count difficulty } userSessionBeatsPercentage { difficulty percentage } totalQuestionBeatsPercentage } } ''', 'variables': {'userSlug': userSlug}, 'operationName': 'userProfileUserQuestionProgressV2', }), ); if (submissionsResponse.statusCode == 200 && progressResponse.statusCode == 200) { final submissionsData = jsonDecode(submissionsResponse.body); final progressData = jsonDecode(progressResponse.body); setState(() { _submissions = submissionsData['data']['recentACSubmissions']; _progress = progressData['data']['userProfileUserQuestionProgressV2']; _isLoading = false; }); } else { throw Exception('请求失败: ${submissionsResponse.statusCode}'); } } catch (e) { setState(() { _error = e.toString(); _isLoading = false; }); } } @override void initState() { super.initState(); _fetchData(); } Widget _buildProgressCard() { if (_progress == null) return const SizedBox.shrink(); final acceptedQuestions = _progress!['numAcceptedQuestions'] as List; final totalBeatsPercentage = _progress!['totalQuestionBeatsPercentage'] as double; return Card( child: Padding( padding: const EdgeInsets.all(16.0), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const Text( '解题进度', style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold), ), const SizedBox(height: 8), ...acceptedQuestions.map((q) { final difficulty = q['difficulty'] as String; final count = q['count'] as int; return Padding( padding: const EdgeInsets.symmetric(vertical: 4.0), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [Text(difficulty), Text('$count 题')], ), ); }), const Divider(), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ const Text('击败用户'), Text('${totalBeatsPercentage.toStringAsFixed(1)}%'), ], ), ], ), ), ); } @override Widget build(BuildContext context) { return Card( child: Padding( padding: const EdgeInsets.all(16.0), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ const Text( 'LeetCode 最近提交', style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold), ), IconButton( icon: const Icon(Icons.refresh), onPressed: _isLoading ? null : _fetchData, ), ], ), 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 Expanded( child: SingleChildScrollView( child: Column( children: [ _buildProgressCard(), const SizedBox(height: 16), if (_submissions.isEmpty) const Center( child: Padding( padding: EdgeInsets.all(16.0), child: Text('暂无提交记录'), ), ) else ListView.builder( shrinkWrap: true, physics: const NeverScrollableScrollPhysics(), itemCount: _submissions.length, itemBuilder: (context, index) { final submission = _submissions[index]; final question = submission['question']; return ListTile( title: Text( question['translatedTitle'] ?? question['title'], ), subtitle: Text( '提交时间: ${timeago.format(DateTime.fromMillisecondsSinceEpoch(submission['submitTime'] * 1000), locale: 'zh_CN')}', ), trailing: Text( '#${question['questionFrontendId']}', ), ); }, ), ], ), ), ), ], ), ), ); } }