diff --git a/devtools_options.yaml b/devtools_options.yaml new file mode 100644 index 0000000..fa0b357 --- /dev/null +++ b/devtools_options.yaml @@ -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: diff --git a/lib/main.dart b/lib/main.dart index 333e995..da73f48 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -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()); } diff --git a/lib/screens/home_screen.dart b/lib/screens/home_screen.dart index 00139db..674b498 100644 --- a/lib/screens/home_screen.dart +++ b/lib/screens/home_screen.dart @@ -20,7 +20,7 @@ class _HomeScreenState extends State { @override void initState() { super.initState(); - Provider.of(context, listen: false).loadCredentials(); + Provider.of(context, listen: false).loadConfigs(); } @override diff --git a/lib/screens/settings_screen.dart b/lib/screens/settings_screen.dart index d4d879e..d7b4488 100644 --- a/lib/screens/settings_screen.dart +++ b/lib/screens/settings_screen.dart @@ -33,14 +33,15 @@ class _SettingsScreenState extends State { Future _loadSettings() async { final authService = Provider.of(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 { final authService = Provider.of(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) { diff --git a/lib/services/auth_service.dart b/lib/services/auth_service.dart index 309412c..8d04765 100644 --- a/lib/services/auth_service.dart +++ b/lib/services/auth_service.dart @@ -9,20 +9,32 @@ class AuthService extends ChangeNotifier { bool get isAuthenticated => _isAuthenticated; Map get credentials => _credentials; - Future saveCredentials(String platform, String cookie) async { - await _storage.write(key: 'cookie_$platform', value: cookie); - _credentials[platform] = cookie; + Future saveConfigs(String key, String value) async { + await _storage.write(key: key, value: value); + _credentials[key] = value; _isAuthenticated = true; notifyListeners(); } - Future loadCredentials() async { - final leetcodeCookie = await _storage.read(key: 'cookie_leetcode'); + Future 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 logout() async { diff --git a/lib/utils/time_utils.dart b/lib/utils/time_utils.dart new file mode 100644 index 0000000..5d504ac --- /dev/null +++ b/lib/utils/time_utils.dart @@ -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()}年前'; + } + } +} diff --git a/lib/widgets/gitea_card.dart b/lib/widgets/gitea_card.dart index 80f7789..8867259 100644 --- a/lib/widgets/gitea_card.dart +++ b/lib/widgets/gitea_card.dart @@ -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 { 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 { try { final authService = Provider.of(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 { 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 { ), 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, diff --git a/lib/widgets/kodbox_card.dart b/lib/widgets/kodbox_card.dart index 359ed17..3eeea08 100644 --- a/lib/widgets/kodbox_card.dart +++ b/lib/widgets/kodbox_card.dart @@ -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 { try { final authService = Provider.of(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 { } } - String _formatTimestamp(String timestamp) { - final date = DateTime.fromMillisecondsSinceEpoch( - (double.parse(timestamp) * 1000).round(), - ); - return date.toString().substring(0, 19); + String _getFilePath(Map 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 log) { @@ -109,16 +115,16 @@ class _KodBoxCardState extends State { 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), ), ], diff --git a/lib/widgets/leetcode_card.dart b/lib/widgets/leetcode_card.dart index 39c4d94..fc8b4af 100644 --- a/lib/widgets/leetcode_card.dart +++ b/lib/widgets/leetcode_card.dart @@ -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 { try { final authService = Provider.of(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 { 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']}'), ); diff --git a/pubspec.lock b/pubspec.lock index b380607..c6dfd74 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -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: diff --git a/pubspec.yaml b/pubspec.yaml index 16083f9..90a2d67 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -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: