feat(auth): 重构认证服务并添加时间格式化功能

- 重命名和更新认证服务中的配置键名
- 在主入口文件中添加时间格式化库的初始化
- 更新设置屏幕中的配置加载和保存逻辑
- 在各个卡片组件中使用时间格式化库显示时间信息
This commit is contained in:
高手 2025-06-09 18:49:10 +08:00
parent ae49aa13df
commit 7b1b7288eb
11 changed files with 94 additions and 35 deletions

3
devtools_options.yaml Normal file
View File

@ -0,0 +1,3 @@
description: This file stores settings for Dart & Flutter DevTools.
documentation: https://docs.flutter.dev/tools/devtools/extensions#configure-extension-enablement-states
extensions:

View File

@ -2,8 +2,10 @@ import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'screens/home_screen.dart';
import 'services/auth_service.dart';
import 'package:timeago/timeago.dart' as timeago;
void main() {
timeago.setLocaleMessages('zh_CN', timeago.ZhCnMessages());
runApp(const MyApp());
}

View File

@ -20,7 +20,7 @@ class _HomeScreenState extends State<HomeScreen> {
@override
void initState() {
super.initState();
Provider.of<AuthService>(context, listen: false).loadCredentials();
Provider.of<AuthService>(context, listen: false).loadConfigs();
}
@override

View File

@ -33,14 +33,15 @@ class _SettingsScreenState extends State<SettingsScreen> {
Future<void> _loadSettings() async {
final authService = Provider.of<AuthService>(context, listen: false);
await authService.loadCredentials();
await authService.loadConfigs();
setState(() {
_leetcodeController.text = authService.credentials['leetcode'] ?? '';
_giteaController.text = authService.credentials['gitea'] ?? '';
_leetcodeController.text =
authService.credentials['leetcode_cookie'] ?? '';
_giteaController.text = authService.credentials['gitea_token'] ?? '';
_giteaUsernameController.text =
authService.credentials['gitea_username'] ?? '';
_kodboxController.text = authService.credentials['kodbox'] ?? '';
_kodboxController.text = authService.credentials['kodbox_token'] ?? '';
});
}
@ -49,19 +50,22 @@ class _SettingsScreenState extends State<SettingsScreen> {
final authService = Provider.of<AuthService>(context, listen: false);
if (_leetcodeController.text.isNotEmpty) {
await authService.saveCredentials('leetcode', _leetcodeController.text);
await authService.saveConfigs(
'leetcode_cookie',
_leetcodeController.text,
);
}
if (_giteaController.text.isNotEmpty) {
await authService.saveCredentials('gitea', _giteaController.text);
await authService.saveConfigs('gitea_token', _giteaController.text);
}
if (_giteaUsernameController.text.isNotEmpty) {
await authService.saveCredentials(
await authService.saveConfigs(
'gitea_username',
_giteaUsernameController.text,
);
}
if (_kodboxController.text.isNotEmpty) {
await authService.saveCredentials('kodbox', _kodboxController.text);
await authService.saveConfigs('kodbox_token', _kodboxController.text);
}
if (mounted) {

View File

@ -9,20 +9,32 @@ class AuthService extends ChangeNotifier {
bool get isAuthenticated => _isAuthenticated;
Map<String, String> get credentials => _credentials;
Future<void> saveCredentials(String platform, String cookie) async {
await _storage.write(key: 'cookie_$platform', value: cookie);
_credentials[platform] = cookie;
Future<void> saveConfigs(String key, String value) async {
await _storage.write(key: key, value: value);
_credentials[key] = value;
_isAuthenticated = true;
notifyListeners();
}
Future<void> loadCredentials() async {
final leetcodeCookie = await _storage.read(key: 'cookie_leetcode');
Future<void> loadConfigs() async {
final leetcodeCookie = await _storage.read(key: 'leetcode_cookie');
final giteaToken = await _storage.read(key: 'gitea_token');
final giteaUserName = await _storage.read(key: 'gitea_username');
final kodboxToken = await _storage.read(key: 'kodbox_token');
if (leetcodeCookie != null) {
_credentials['leetcode'] = leetcodeCookie;
_isAuthenticated = true;
notifyListeners();
_credentials['leetcode_cookie'] = leetcodeCookie;
}
if (giteaToken != null) {
_credentials['gitea_token'] = giteaToken;
}
if (giteaUserName != null) {
_credentials['gitea_username'] = giteaUserName;
}
if (kodboxToken != null) {
_credentials['kodbox_token'] = kodboxToken;
}
_isAuthenticated = true;
notifyListeners();
}
Future<void> logout() async {

23
lib/utils/time_utils.dart Normal file
View File

@ -0,0 +1,23 @@
class TimeUtils {
static String getRelativeTime(String timestamp) {
final now = DateTime.now();
final date = DateTime.fromMillisecondsSinceEpoch(
(double.parse(timestamp) * 1000).round(),
);
final difference = now.difference(date);
if (difference.inSeconds < 60) {
return '刚刚';
} else if (difference.inMinutes < 60) {
return '${difference.inMinutes}分钟前';
} else if (difference.inHours < 24) {
return '${difference.inHours}小时前';
} else if (difference.inDays < 30) {
return '${difference.inDays}天前';
} else if (difference.inDays < 365) {
return '${(difference.inDays / 30).round()}个月前';
} else {
return '${(difference.inDays / 365).round()}年前';
}
}
}

View File

@ -1,8 +1,10 @@
import 'package:flutter/material.dart';
import 'package:intl/intl.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 GiteaCard extends StatefulWidget {
const GiteaCard({super.key});
@ -60,7 +62,7 @@ class _GiteaCardState extends State<GiteaCard> {
style: const TextStyle(fontSize: 12, color: Colors.grey),
),
Text(
'提交时间: ${commit['Timestamp']}',
'提交时间: ${DateFormat('yyyy-MM-dd HH:mm:ss').format(DateTime.parse(commit['Timestamp']))}',
style: const TextStyle(fontSize: 12, color: Colors.grey),
),
],
@ -81,7 +83,7 @@ class _GiteaCardState extends State<GiteaCard> {
try {
final authService = Provider.of<AuthService>(context, listen: false);
final token = authService.credentials['gitea'];
final token = authService.credentials['gitea_token'];
final username = authService.credentials['gitea_username'];
if (token == null || token.isEmpty) {
@ -172,7 +174,7 @@ class _GiteaCardState extends State<GiteaCard> {
itemBuilder: (context, index) {
final activity = _activities[index];
final repo = activity['repo'];
final created = DateTime.parse(activity['created']);
final created = activity['created'];
return Card(
margin: const EdgeInsets.symmetric(vertical: 4.0),
@ -224,7 +226,7 @@ class _GiteaCardState extends State<GiteaCard> {
),
const SizedBox(height: 8),
Text(
'时间: ${created.toString().substring(0, 19)}',
'时间: ${timeago.format(DateTime.parse(created), locale: 'zh_CN')}',
style: const TextStyle(
fontSize: 12,
color: Colors.grey,

View File

@ -3,6 +3,7 @@ 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 KodBoxCard extends StatefulWidget {
const KodBoxCard({super.key});
@ -24,7 +25,7 @@ class _KodBoxCardState extends State<KodBoxCard> {
try {
final authService = Provider.of<AuthService>(context, listen: false);
final token = authService.credentials['kodbox'];
final token = authService.credentials['kodbox_token'];
if (token == null || token.isEmpty) {
throw Exception('未配置 KodBox Token');
@ -74,11 +75,16 @@ class _KodBoxCardState extends State<KodBoxCard> {
}
}
String _formatTimestamp(String timestamp) {
final date = DateTime.fromMillisecondsSinceEpoch(
(double.parse(timestamp) * 1000).round(),
);
return date.toString().substring(0, 19);
String _getFilePath(Map<String, dynamic> desc) {
if (desc['sourceInfo'] == false) {
//
final parentInfo = desc['parentInfo'];
final fileName = desc['desc']?['content'] ?? '未知文件';
return '${parentInfo['pathDisplay']}$fileName';
} else if (desc['sourceInfo'] != null) {
return desc['sourceInfo']['pathDisplay'] ?? '未知路径';
}
return '未知路径';
}
Widget _buildLogItem(Map<String, dynamic> log) {
@ -109,16 +115,16 @@ class _KodBoxCardState extends State<KodBoxCard> {
Text(log['title'] ?? '未知操作'),
],
),
if (isFileOperation && desc['sourceInfo'] != null) ...[
if (isFileOperation) ...[
const SizedBox(height: 8),
Text(
'文件路径: ${desc['sourceInfo']['pathDisplay']}',
'文件路径: ${_getFilePath(desc)}',
style: const TextStyle(color: Colors.blue),
),
],
const SizedBox(height: 8),
Text(
'时间: ${_formatTimestamp(log['createTime'])}',
'时间: ${timeago.format(DateTime.fromMillisecondsSinceEpoch(int.parse(log['createTime']) * 1000), locale: 'zh_CN')}',
style: const TextStyle(fontSize: 12, color: Colors.grey),
),
],

View File

@ -3,6 +3,7 @@ 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});
@ -24,7 +25,7 @@ class _LeetCodeCardState extends State<LeetCodeCard> {
try {
final authService = Provider.of<AuthService>(context, listen: false);
final cookie = authService.credentials['leetcode'];
final cookie = authService.credentials['leetcode_cookie'];
if (cookie == null || cookie.isEmpty) {
throw Exception('未配置 LeetCode Cookie');
@ -128,16 +129,13 @@ class _LeetCodeCardState extends State<LeetCodeCard> {
itemBuilder: (context, index) {
final submission = _submissions[index];
final question = submission['question'];
final submitTime = DateTime.fromMillisecondsSinceEpoch(
submission['submitTime'] * 1000,
);
return ListTile(
title: Text(
question['translatedTitle'] ?? question['title'],
),
subtitle: Text(
'提交时间: ${submitTime.toString().substring(0, 19)}',
'提交时间: ${timeago.format(DateTime.fromMillisecondsSinceEpoch(submission['submitTime'] * 1000), locale: 'zh_CN')}',
),
trailing: Text('#${question['questionFrontendId']}'),
);

View File

@ -549,6 +549,14 @@ packages:
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
source: hosted
version: "0.7.4"
timeago:
dependency: "direct main"
description:
name: timeago
sha256: b05159406a97e1cbb2b9ee4faa9fb096fe0e2dfcd8b08fcd2a00553450d3422e
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
source: hosted
version: "3.7.1"
typed_data:
dependency: transitive
description:

View File

@ -40,6 +40,7 @@ dependencies:
intl: ^0.19.0
flutter_secure_storage: ^9.0.0
cached_network_image: ^3.3.1
timeago: ^3.7.1
dev_dependencies:
flutter_test: