feat(auth): 重构认证服务并添加时间格式化功能
- 重命名和更新认证服务中的配置键名 - 在主入口文件中添加时间格式化库的初始化 - 更新设置屏幕中的配置加载和保存逻辑 - 在各个卡片组件中使用时间格式化库显示时间信息
This commit is contained in:
parent
ae49aa13df
commit
7b1b7288eb
3
devtools_options.yaml
Normal file
3
devtools_options.yaml
Normal 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:
|
||||||
@ -2,8 +2,10 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'screens/home_screen.dart';
|
import 'screens/home_screen.dart';
|
||||||
import 'services/auth_service.dart';
|
import 'services/auth_service.dart';
|
||||||
|
import 'package:timeago/timeago.dart' as timeago;
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
|
timeago.setLocaleMessages('zh_CN', timeago.ZhCnMessages());
|
||||||
runApp(const MyApp());
|
runApp(const MyApp());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -20,7 +20,7 @@ class _HomeScreenState extends State<HomeScreen> {
|
|||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
Provider.of<AuthService>(context, listen: false).loadCredentials();
|
Provider.of<AuthService>(context, listen: false).loadConfigs();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|||||||
@ -33,14 +33,15 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
|||||||
|
|
||||||
Future<void> _loadSettings() async {
|
Future<void> _loadSettings() async {
|
||||||
final authService = Provider.of<AuthService>(context, listen: false);
|
final authService = Provider.of<AuthService>(context, listen: false);
|
||||||
await authService.loadCredentials();
|
await authService.loadConfigs();
|
||||||
|
|
||||||
setState(() {
|
setState(() {
|
||||||
_leetcodeController.text = authService.credentials['leetcode'] ?? '';
|
_leetcodeController.text =
|
||||||
_giteaController.text = authService.credentials['gitea'] ?? '';
|
authService.credentials['leetcode_cookie'] ?? '';
|
||||||
|
_giteaController.text = authService.credentials['gitea_token'] ?? '';
|
||||||
_giteaUsernameController.text =
|
_giteaUsernameController.text =
|
||||||
authService.credentials['gitea_username'] ?? '';
|
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);
|
final authService = Provider.of<AuthService>(context, listen: false);
|
||||||
|
|
||||||
if (_leetcodeController.text.isNotEmpty) {
|
if (_leetcodeController.text.isNotEmpty) {
|
||||||
await authService.saveCredentials('leetcode', _leetcodeController.text);
|
await authService.saveConfigs(
|
||||||
|
'leetcode_cookie',
|
||||||
|
_leetcodeController.text,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
if (_giteaController.text.isNotEmpty) {
|
if (_giteaController.text.isNotEmpty) {
|
||||||
await authService.saveCredentials('gitea', _giteaController.text);
|
await authService.saveConfigs('gitea_token', _giteaController.text);
|
||||||
}
|
}
|
||||||
if (_giteaUsernameController.text.isNotEmpty) {
|
if (_giteaUsernameController.text.isNotEmpty) {
|
||||||
await authService.saveCredentials(
|
await authService.saveConfigs(
|
||||||
'gitea_username',
|
'gitea_username',
|
||||||
_giteaUsernameController.text,
|
_giteaUsernameController.text,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (_kodboxController.text.isNotEmpty) {
|
if (_kodboxController.text.isNotEmpty) {
|
||||||
await authService.saveCredentials('kodbox', _kodboxController.text);
|
await authService.saveConfigs('kodbox_token', _kodboxController.text);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
|
|||||||
@ -9,21 +9,33 @@ class AuthService extends ChangeNotifier {
|
|||||||
bool get isAuthenticated => _isAuthenticated;
|
bool get isAuthenticated => _isAuthenticated;
|
||||||
Map<String, String> get credentials => _credentials;
|
Map<String, String> get credentials => _credentials;
|
||||||
|
|
||||||
Future<void> saveCredentials(String platform, String cookie) async {
|
Future<void> saveConfigs(String key, String value) async {
|
||||||
await _storage.write(key: 'cookie_$platform', value: cookie);
|
await _storage.write(key: key, value: value);
|
||||||
_credentials[platform] = cookie;
|
_credentials[key] = value;
|
||||||
_isAuthenticated = true;
|
_isAuthenticated = true;
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> loadCredentials() async {
|
Future<void> loadConfigs() async {
|
||||||
final leetcodeCookie = await _storage.read(key: 'cookie_leetcode');
|
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) {
|
if (leetcodeCookie != null) {
|
||||||
_credentials['leetcode'] = leetcodeCookie;
|
_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;
|
_isAuthenticated = true;
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> logout() async {
|
Future<void> logout() async {
|
||||||
await _storage.deleteAll();
|
await _storage.deleteAll();
|
||||||
|
|||||||
23
lib/utils/time_utils.dart
Normal file
23
lib/utils/time_utils.dart
Normal 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()}年前';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,8 +1,10 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:intl/intl.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'package:http/http.dart' as http;
|
import 'package:http/http.dart' as http;
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import '../services/auth_service.dart';
|
import '../services/auth_service.dart';
|
||||||
|
import 'package:timeago/timeago.dart' as timeago;
|
||||||
|
|
||||||
class GiteaCard extends StatefulWidget {
|
class GiteaCard extends StatefulWidget {
|
||||||
const GiteaCard({super.key});
|
const GiteaCard({super.key});
|
||||||
@ -60,7 +62,7 @@ class _GiteaCardState extends State<GiteaCard> {
|
|||||||
style: const TextStyle(fontSize: 12, color: Colors.grey),
|
style: const TextStyle(fontSize: 12, color: Colors.grey),
|
||||||
),
|
),
|
||||||
Text(
|
Text(
|
||||||
'提交时间: ${commit['Timestamp']}',
|
'提交时间: ${DateFormat('yyyy-MM-dd HH:mm:ss').format(DateTime.parse(commit['Timestamp']))}',
|
||||||
style: const TextStyle(fontSize: 12, color: Colors.grey),
|
style: const TextStyle(fontSize: 12, color: Colors.grey),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@ -81,7 +83,7 @@ class _GiteaCardState extends State<GiteaCard> {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
final authService = Provider.of<AuthService>(context, listen: false);
|
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'];
|
final username = authService.credentials['gitea_username'];
|
||||||
|
|
||||||
if (token == null || token.isEmpty) {
|
if (token == null || token.isEmpty) {
|
||||||
@ -172,7 +174,7 @@ class _GiteaCardState extends State<GiteaCard> {
|
|||||||
itemBuilder: (context, index) {
|
itemBuilder: (context, index) {
|
||||||
final activity = _activities[index];
|
final activity = _activities[index];
|
||||||
final repo = activity['repo'];
|
final repo = activity['repo'];
|
||||||
final created = DateTime.parse(activity['created']);
|
final created = activity['created'];
|
||||||
|
|
||||||
return Card(
|
return Card(
|
||||||
margin: const EdgeInsets.symmetric(vertical: 4.0),
|
margin: const EdgeInsets.symmetric(vertical: 4.0),
|
||||||
@ -224,7 +226,7 @@ class _GiteaCardState extends State<GiteaCard> {
|
|||||||
),
|
),
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
Text(
|
Text(
|
||||||
'时间: ${created.toString().substring(0, 19)}',
|
'时间: ${timeago.format(DateTime.parse(created), locale: 'zh_CN')}',
|
||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
color: Colors.grey,
|
color: Colors.grey,
|
||||||
|
|||||||
@ -3,6 +3,7 @@ import 'package:provider/provider.dart';
|
|||||||
import 'package:http/http.dart' as http;
|
import 'package:http/http.dart' as http;
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import '../services/auth_service.dart';
|
import '../services/auth_service.dart';
|
||||||
|
import 'package:timeago/timeago.dart' as timeago;
|
||||||
|
|
||||||
class KodBoxCard extends StatefulWidget {
|
class KodBoxCard extends StatefulWidget {
|
||||||
const KodBoxCard({super.key});
|
const KodBoxCard({super.key});
|
||||||
@ -24,7 +25,7 @@ class _KodBoxCardState extends State<KodBoxCard> {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
final authService = Provider.of<AuthService>(context, listen: false);
|
final authService = Provider.of<AuthService>(context, listen: false);
|
||||||
final token = authService.credentials['kodbox'];
|
final token = authService.credentials['kodbox_token'];
|
||||||
|
|
||||||
if (token == null || token.isEmpty) {
|
if (token == null || token.isEmpty) {
|
||||||
throw Exception('未配置 KodBox Token');
|
throw Exception('未配置 KodBox Token');
|
||||||
@ -74,11 +75,16 @@ class _KodBoxCardState extends State<KodBoxCard> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
String _formatTimestamp(String timestamp) {
|
String _getFilePath(Map<String, dynamic> desc) {
|
||||||
final date = DateTime.fromMillisecondsSinceEpoch(
|
if (desc['sourceInfo'] == false) {
|
||||||
(double.parse(timestamp) * 1000).round(),
|
// 处理删除文件的情况
|
||||||
);
|
final parentInfo = desc['parentInfo'];
|
||||||
return date.toString().substring(0, 19);
|
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) {
|
Widget _buildLogItem(Map<String, dynamic> log) {
|
||||||
@ -109,16 +115,16 @@ class _KodBoxCardState extends State<KodBoxCard> {
|
|||||||
Text(log['title'] ?? '未知操作'),
|
Text(log['title'] ?? '未知操作'),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
if (isFileOperation && desc['sourceInfo'] != null) ...[
|
if (isFileOperation) ...[
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
Text(
|
Text(
|
||||||
'文件路径: ${desc['sourceInfo']['pathDisplay']}',
|
'文件路径: ${_getFilePath(desc)}',
|
||||||
style: const TextStyle(color: Colors.blue),
|
style: const TextStyle(color: Colors.blue),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
Text(
|
Text(
|
||||||
'时间: ${_formatTimestamp(log['createTime'])}',
|
'时间: ${timeago.format(DateTime.fromMillisecondsSinceEpoch(int.parse(log['createTime']) * 1000), locale: 'zh_CN')}',
|
||||||
style: const TextStyle(fontSize: 12, color: Colors.grey),
|
style: const TextStyle(fontSize: 12, color: Colors.grey),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|||||||
@ -3,6 +3,7 @@ import 'package:provider/provider.dart';
|
|||||||
import 'package:http/http.dart' as http;
|
import 'package:http/http.dart' as http;
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import '../services/auth_service.dart';
|
import '../services/auth_service.dart';
|
||||||
|
import 'package:timeago/timeago.dart' as timeago;
|
||||||
|
|
||||||
class LeetCodeCard extends StatefulWidget {
|
class LeetCodeCard extends StatefulWidget {
|
||||||
const LeetCodeCard({super.key});
|
const LeetCodeCard({super.key});
|
||||||
@ -24,7 +25,7 @@ class _LeetCodeCardState extends State<LeetCodeCard> {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
final authService = Provider.of<AuthService>(context, listen: false);
|
final authService = Provider.of<AuthService>(context, listen: false);
|
||||||
final cookie = authService.credentials['leetcode'];
|
final cookie = authService.credentials['leetcode_cookie'];
|
||||||
|
|
||||||
if (cookie == null || cookie.isEmpty) {
|
if (cookie == null || cookie.isEmpty) {
|
||||||
throw Exception('未配置 LeetCode Cookie');
|
throw Exception('未配置 LeetCode Cookie');
|
||||||
@ -128,16 +129,13 @@ class _LeetCodeCardState extends State<LeetCodeCard> {
|
|||||||
itemBuilder: (context, index) {
|
itemBuilder: (context, index) {
|
||||||
final submission = _submissions[index];
|
final submission = _submissions[index];
|
||||||
final question = submission['question'];
|
final question = submission['question'];
|
||||||
final submitTime = DateTime.fromMillisecondsSinceEpoch(
|
|
||||||
submission['submitTime'] * 1000,
|
|
||||||
);
|
|
||||||
|
|
||||||
return ListTile(
|
return ListTile(
|
||||||
title: Text(
|
title: Text(
|
||||||
question['translatedTitle'] ?? question['title'],
|
question['translatedTitle'] ?? question['title'],
|
||||||
),
|
),
|
||||||
subtitle: Text(
|
subtitle: Text(
|
||||||
'提交时间: ${submitTime.toString().substring(0, 19)}',
|
'提交时间: ${timeago.format(DateTime.fromMillisecondsSinceEpoch(submission['submitTime'] * 1000), locale: 'zh_CN')}',
|
||||||
),
|
),
|
||||||
trailing: Text('#${question['questionFrontendId']}'),
|
trailing: Text('#${question['questionFrontendId']}'),
|
||||||
);
|
);
|
||||||
|
|||||||
@ -549,6 +549,14 @@ packages:
|
|||||||
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.7.4"
|
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:
|
typed_data:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|||||||
@ -40,6 +40,7 @@ dependencies:
|
|||||||
intl: ^0.19.0
|
intl: ^0.19.0
|
||||||
flutter_secure_storage: ^9.0.0
|
flutter_secure_storage: ^9.0.0
|
||||||
cached_network_image: ^3.3.1
|
cached_network_image: ^3.3.1
|
||||||
|
timeago: ^3.7.1
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user