feat(gitea): 添加 Gitea 数据卡片并更新相关设置
- 在 HomeScreen 中添加 GiteaCard 组件 - 在 SettingsScreen 中增加 Gitea 用户名和 API Token 设置 - 优化 LeetCodeCard 组件,增加空 cookie 检查 - 调整 SettingsScreen 中平台设置的布局结构
This commit is contained in:
parent
1945d3207a
commit
89c0dfc1aa
@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import '../services/auth_service.dart';
|
||||
import '../widgets/leetcode_card.dart';
|
||||
import '../widgets/gitea_card.dart';
|
||||
import 'settings_screen.dart';
|
||||
|
||||
class HomeScreen extends StatefulWidget {
|
||||
@ -87,7 +88,7 @@ class _HomeScreenState extends State<HomeScreen> {
|
||||
case 'LeetCode':
|
||||
return const LeetCodeCard();
|
||||
case 'Gitea':
|
||||
return const Center(child: Text('Gitea 数据卡片开发中...'));
|
||||
return const GiteaCard();
|
||||
case 'KodBox':
|
||||
return const Center(child: Text('KodBox 数据卡片开发中...'));
|
||||
default:
|
||||
|
||||
@ -13,6 +13,7 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
||||
final _formKey = GlobalKey<FormState>();
|
||||
final _leetcodeController = TextEditingController();
|
||||
final _giteaController = TextEditingController();
|
||||
final _giteaUsernameController = TextEditingController();
|
||||
final _kodboxController = TextEditingController();
|
||||
|
||||
@override
|
||||
@ -25,6 +26,7 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
||||
void dispose() {
|
||||
_leetcodeController.dispose();
|
||||
_giteaController.dispose();
|
||||
_giteaUsernameController.dispose();
|
||||
_kodboxController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
@ -36,6 +38,8 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
||||
setState(() {
|
||||
_leetcodeController.text = authService.credentials['leetcode'] ?? '';
|
||||
_giteaController.text = authService.credentials['gitea'] ?? '';
|
||||
_giteaUsernameController.text =
|
||||
authService.credentials['gitea_username'] ?? '';
|
||||
_kodboxController.text = authService.credentials['kodbox'] ?? '';
|
||||
});
|
||||
}
|
||||
@ -50,6 +54,12 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
||||
if (_giteaController.text.isNotEmpty) {
|
||||
await authService.saveCredentials('gitea', _giteaController.text);
|
||||
}
|
||||
if (_giteaUsernameController.text.isNotEmpty) {
|
||||
await authService.saveCredentials(
|
||||
'gitea_username',
|
||||
_giteaUsernameController.text,
|
||||
);
|
||||
}
|
||||
if (_kodboxController.text.isNotEmpty) {
|
||||
await authService.saveCredentials('kodbox', _kodboxController.text);
|
||||
}
|
||||
@ -75,22 +85,58 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
||||
_buildPlatformSettings(
|
||||
title: 'LeetCode 设置',
|
||||
icon: Icons.code,
|
||||
children: [
|
||||
TextFormField(
|
||||
controller: _leetcodeController,
|
||||
decoration: const InputDecoration(
|
||||
labelText: 'Cookie',
|
||||
hintText: '请输入 LeetCode Cookie',
|
||||
border: OutlineInputBorder(),
|
||||
),
|
||||
obscureText: true,
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
_buildPlatformSettings(
|
||||
title: 'Gitea 设置',
|
||||
icon: Icons.storage,
|
||||
children: [
|
||||
TextFormField(
|
||||
controller: _giteaUsernameController,
|
||||
decoration: const InputDecoration(
|
||||
labelText: '用户名',
|
||||
hintText: '请输入 Gitea 用户名',
|
||||
border: OutlineInputBorder(),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
TextFormField(
|
||||
controller: _giteaController,
|
||||
decoration: const InputDecoration(
|
||||
labelText: 'API Token',
|
||||
hintText: '请输入 Gitea API Token',
|
||||
border: OutlineInputBorder(),
|
||||
),
|
||||
obscureText: true,
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
_buildPlatformSettings(
|
||||
title: 'KodBox 设置',
|
||||
icon: Icons.folder,
|
||||
children: [
|
||||
TextFormField(
|
||||
controller: _kodboxController,
|
||||
decoration: const InputDecoration(
|
||||
labelText: 'API Token',
|
||||
hintText: '请输入 KodBox API Token',
|
||||
border: OutlineInputBorder(),
|
||||
),
|
||||
obscureText: true,
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
ElevatedButton(onPressed: _saveSettings, child: const Text('保存设置')),
|
||||
@ -103,8 +149,7 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
||||
Widget _buildPlatformSettings({
|
||||
required String title,
|
||||
required IconData icon,
|
||||
required TextEditingController controller,
|
||||
required String hintText,
|
||||
required List<Widget> children,
|
||||
}) {
|
||||
return Card(
|
||||
child: Padding(
|
||||
@ -126,15 +171,7 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
TextFormField(
|
||||
controller: controller,
|
||||
decoration: InputDecoration(
|
||||
labelText: '认证信息',
|
||||
hintText: hintText,
|
||||
border: const OutlineInputBorder(),
|
||||
),
|
||||
obscureText: true,
|
||||
),
|
||||
...children,
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
245
lib/widgets/gitea_card.dart
Normal file
245
lib/widgets/gitea_card.dart
Normal file
@ -0,0 +1,245 @@
|
||||
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';
|
||||
|
||||
class GiteaCard extends StatefulWidget {
|
||||
const GiteaCard({super.key});
|
||||
|
||||
@override
|
||||
State<GiteaCard> createState() => _GiteaCardState();
|
||||
}
|
||||
|
||||
class _GiteaCardState extends State<GiteaCard> {
|
||||
List<dynamic> _activities = [];
|
||||
bool _isLoading = false;
|
||||
String? _error;
|
||||
|
||||
String _getOperationTypeText(String opType) {
|
||||
switch (opType) {
|
||||
case 'create_repo':
|
||||
return '创建仓库';
|
||||
case 'commit_repo':
|
||||
return '提交代码';
|
||||
case 'push_tag':
|
||||
return '推送标签';
|
||||
case 'create_issue':
|
||||
return '创建 Issue';
|
||||
case 'comment_issue':
|
||||
return '评论 Issue';
|
||||
case 'create_pull_request':
|
||||
return '创建 Pull Request';
|
||||
case 'merge_pull_request':
|
||||
return '合并 Pull Request';
|
||||
default:
|
||||
return opType;
|
||||
}
|
||||
}
|
||||
|
||||
Widget _buildCommitContent(String content) {
|
||||
try {
|
||||
final data = jsonDecode(content);
|
||||
final commits = data['Commits'] as List;
|
||||
|
||||
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['AuthorName']} <${commit['AuthorEmail']}>',
|
||||
style: const TextStyle(fontSize: 12, color: Colors.grey),
|
||||
),
|
||||
Text(
|
||||
'提交时间: ${commit['Timestamp']}',
|
||||
style: const TextStyle(fontSize: 12, color: Colors.grey),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}).toList(),
|
||||
);
|
||||
} catch (e) {
|
||||
return Text(content);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _fetchActivities() async {
|
||||
setState(() {
|
||||
_isLoading = true;
|
||||
_error = null;
|
||||
});
|
||||
|
||||
try {
|
||||
final authService = Provider.of<AuthService>(context, listen: false);
|
||||
final token = authService.credentials['gitea'];
|
||||
final username = authService.credentials['gitea_username'];
|
||||
|
||||
if (token == null || token.isEmpty) {
|
||||
throw Exception('未配置 Gitea Token');
|
||||
}
|
||||
if (username == null || username.isEmpty) {
|
||||
throw Exception('未配置 Gitea 用户名');
|
||||
}
|
||||
|
||||
final response = await http.get(
|
||||
Uri.parse(
|
||||
'https://git.jdysya.top/api/v1/users/$username/activities/feeds',
|
||||
),
|
||||
headers: {'Authorization': 'token $token'},
|
||||
);
|
||||
|
||||
if (response.statusCode == 200) {
|
||||
setState(() {
|
||||
_activities = jsonDecode(response.body);
|
||||
_isLoading = false;
|
||||
});
|
||||
} else {
|
||||
throw Exception('请求失败: ${response.statusCode}');
|
||||
}
|
||||
} catch (e) {
|
||||
setState(() {
|
||||
_error = e.toString();
|
||||
_isLoading = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_fetchActivities();
|
||||
}
|
||||
|
||||
@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(
|
||||
'Gitea 最近活动',
|
||||
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
|
||||
),
|
||||
IconButton(
|
||||
icon: const Icon(Icons.refresh),
|
||||
onPressed: _isLoading ? null : _fetchActivities,
|
||||
),
|
||||
],
|
||||
),
|
||||
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 (_activities.isEmpty)
|
||||
const Center(
|
||||
child: Padding(
|
||||
padding: EdgeInsets.all(16.0),
|
||||
child: Text('暂无活动记录'),
|
||||
),
|
||||
)
|
||||
else
|
||||
Expanded(
|
||||
child: ListView.builder(
|
||||
itemCount: _activities.length,
|
||||
itemBuilder: (context, index) {
|
||||
final activity = _activities[index];
|
||||
final repo = activity['repo'];
|
||||
final created = DateTime.parse(activity['created']);
|
||||
|
||||
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(
|
||||
radius: 16,
|
||||
child: Text(
|
||||
activity['act_user']['login'][0]
|
||||
.toUpperCase(),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Text(
|
||||
activity['act_user']['login'],
|
||||
style: const TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Text(
|
||||
_getOperationTypeText(activity['op_type']),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
if (repo != null)
|
||||
Text(
|
||||
repo['full_name'],
|
||||
style: const TextStyle(
|
||||
color: Colors.blue,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
if (activity['content'] != null)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 8.0),
|
||||
child:
|
||||
activity['op_type'] == 'commit_repo'
|
||||
? _buildCommitContent(
|
||||
activity['content'],
|
||||
)
|
||||
: Text(activity['content']),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
'时间: ${created.toString().substring(0, 19)}',
|
||||
style: const TextStyle(
|
||||
fontSize: 12,
|
||||
color: Colors.grey,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -26,7 +26,7 @@ class _LeetCodeCardState extends State<LeetCodeCard> {
|
||||
final authService = Provider.of<AuthService>(context, listen: false);
|
||||
final cookie = authService.credentials['leetcode'];
|
||||
|
||||
if (cookie == null) {
|
||||
if (cookie == null || cookie.isEmpty) {
|
||||
throw Exception('未配置 LeetCode Cookie');
|
||||
}
|
||||
|
||||
@ -122,9 +122,8 @@ class _LeetCodeCardState extends State<LeetCodeCard> {
|
||||
),
|
||||
)
|
||||
else
|
||||
ListView.builder(
|
||||
shrinkWrap: true,
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
Expanded(
|
||||
child: ListView.builder(
|
||||
itemCount: _submissions.length,
|
||||
itemBuilder: (context, index) {
|
||||
final submission = _submissions[index];
|
||||
@ -144,6 +143,7 @@ class _LeetCodeCardState extends State<LeetCodeCard> {
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user