feat(wakatime): 添加 Wakatime 平台支持

- 在首页添加 Wakatime 卡片和相关功能
- 在设置页面添加 Wakatime API Token 配置
- 更新 auth_service 以支持 Wakatime 认证
- 添加 fl_chart 依赖用于 Wakatime 数据图表展示
- 更新 flutter_lints 版本到 2.0.0
This commit is contained in:
高手 2025-06-10 12:07:33 +08:00
parent 66f46980f0
commit 569b4f4498
8 changed files with 972 additions and 9 deletions

View File

@ -0,0 +1,541 @@
class WakatimeSummary {
final List<SummaryData> data;
final String start;
final String end;
final CumulativeTotal cumulativeTotal;
final DailyAverage dailyAverage;
WakatimeSummary({
required this.data,
required this.start,
required this.end,
required this.cumulativeTotal,
required this.dailyAverage,
});
factory WakatimeSummary.fromJson(Map<String, dynamic> json) {
return WakatimeSummary(
data: (json['data'] as List).map((e) => SummaryData.fromJson(e)).toList(),
start: json['start'],
end: json['end'],
cumulativeTotal: CumulativeTotal.fromJson(json['cumulative_total']),
dailyAverage: DailyAverage.fromJson(json['daily_average']),
);
}
//
List<Language> getAggregatedLanguages() {
final Map<String, Language> aggregated = {};
for (var dayData in data) {
for (var lang in dayData.languages) {
if (aggregated.containsKey(lang.name)) {
final existing = aggregated[lang.name]!;
aggregated[lang.name] = Language(
name: lang.name,
totalSeconds: existing.totalSeconds + lang.totalSeconds,
digital: _formatTime(existing.totalSeconds + lang.totalSeconds),
decimal: ((existing.totalSeconds + lang.totalSeconds) / 3600)
.toStringAsFixed(2),
text: _formatTimeText(existing.totalSeconds + lang.totalSeconds),
hours: ((existing.totalSeconds + lang.totalSeconds) / 3600).floor(),
minutes: ((existing.totalSeconds + lang.totalSeconds) % 3600 / 60)
.floor(),
seconds: ((existing.totalSeconds + lang.totalSeconds) % 60).floor(),
percent: 0, //
);
} else {
aggregated[lang.name] = lang;
}
}
}
final totalSeconds = aggregated.values
.fold<double>(0, (sum, lang) => sum + lang.totalSeconds);
final result = aggregated.values
.map((lang) => Language(
name: lang.name,
totalSeconds: lang.totalSeconds,
digital: lang.digital,
decimal: lang.decimal,
text: lang.text,
hours: lang.hours,
minutes: lang.minutes,
seconds: lang.seconds,
percent: (lang.totalSeconds / totalSeconds * 100),
))
.toList();
// 使
result.sort((a, b) => b.totalSeconds.compareTo(a.totalSeconds));
return result;
}
//
List<Editor> getAggregatedEditors() {
final Map<String, Editor> aggregated = {};
for (var dayData in data) {
for (var editor in dayData.editors) {
if (aggregated.containsKey(editor.name)) {
final existing = aggregated[editor.name]!;
aggregated[editor.name] = Editor(
name: editor.name,
totalSeconds: existing.totalSeconds + editor.totalSeconds,
digital: _formatTime(existing.totalSeconds + editor.totalSeconds),
decimal: ((existing.totalSeconds + editor.totalSeconds) / 3600)
.toStringAsFixed(2),
text: _formatTimeText(existing.totalSeconds + editor.totalSeconds),
hours:
((existing.totalSeconds + editor.totalSeconds) / 3600).floor(),
minutes: ((existing.totalSeconds + editor.totalSeconds) % 3600 / 60)
.floor(),
seconds:
((existing.totalSeconds + editor.totalSeconds) % 60).floor(),
percent: 0, //
);
} else {
aggregated[editor.name] = editor;
}
}
}
final totalSeconds = aggregated.values
.fold<double>(0, (sum, editor) => sum + editor.totalSeconds);
final result = aggregated.values
.map((editor) => Editor(
name: editor.name,
totalSeconds: editor.totalSeconds,
digital: editor.digital,
decimal: editor.decimal,
text: editor.text,
hours: editor.hours,
minutes: editor.minutes,
seconds: editor.seconds,
percent: (editor.totalSeconds / totalSeconds * 100),
))
.toList();
// 使
result.sort((a, b) => b.totalSeconds.compareTo(a.totalSeconds));
return result;
}
//
List<Project> getAggregatedProjects() {
final Map<String, Project> aggregated = {};
for (var dayData in data) {
for (var project in dayData.projects) {
if (aggregated.containsKey(project.name)) {
final existing = aggregated[project.name]!;
aggregated[project.name] = Project(
name: project.name,
totalSeconds: existing.totalSeconds + project.totalSeconds,
color: project.color,
digital: _formatTime(existing.totalSeconds + project.totalSeconds),
decimal: ((existing.totalSeconds + project.totalSeconds) / 3600)
.toStringAsFixed(2),
text: _formatTimeText(existing.totalSeconds + project.totalSeconds),
hours:
((existing.totalSeconds + project.totalSeconds) / 3600).floor(),
minutes:
((existing.totalSeconds + project.totalSeconds) % 3600 / 60)
.floor(),
seconds:
((existing.totalSeconds + project.totalSeconds) % 60).floor(),
percent: 0, //
);
} else {
aggregated[project.name] = project;
}
}
}
final totalSeconds = aggregated.values
.fold<double>(0, (sum, project) => sum + project.totalSeconds);
final result = aggregated.values
.map((project) => Project(
name: project.name,
totalSeconds: project.totalSeconds,
color: project.color,
digital: project.digital,
decimal: project.decimal,
text: project.text,
hours: project.hours,
minutes: project.minutes,
seconds: project.seconds,
percent: (project.totalSeconds / totalSeconds * 100),
))
.toList();
// 使
result.sort((a, b) => b.totalSeconds.compareTo(a.totalSeconds));
return result;
}
// HH:mm:ss
String _formatTime(double seconds) {
final hours = (seconds / 3600).floor();
final minutes = ((seconds % 3600) / 60).floor();
final secs = (seconds % 60).floor();
return '${hours.toString().padLeft(2, '0')}:${minutes.toString().padLeft(2, '0')}:${secs.toString().padLeft(2, '0')}';
}
//
String _formatTimeText(double seconds) {
final hours = (seconds / 3600).floor();
final minutes = ((seconds % 3600) / 60).floor();
final secs = (seconds % 60).floor();
if (hours > 0) {
return '$hours hr${hours > 1 ? 's' : ''} ${minutes > 0 ? '$minutes min${minutes > 1 ? 's' : ''}' : ''}';
} else if (minutes > 0) {
return '$minutes min${minutes > 1 ? 's' : ''}';
} else {
return '$secs sec${secs > 1 ? 's' : ''}';
}
}
}
class SummaryData {
final GrandTotal grandTotal;
final Range range;
final List<Project> projects;
final List<Language> languages;
final List<Editor> editors;
final List<OperatingSystem> operatingSystems;
final List<Category> categories;
SummaryData({
required this.grandTotal,
required this.range,
required this.projects,
required this.languages,
required this.editors,
required this.operatingSystems,
required this.categories,
});
factory SummaryData.fromJson(Map<String, dynamic> json) {
return SummaryData(
grandTotal: GrandTotal.fromJson(json['grand_total']),
range: Range.fromJson(json['range']),
projects:
(json['projects'] as List).map((e) => Project.fromJson(e)).toList(),
languages:
(json['languages'] as List).map((e) => Language.fromJson(e)).toList(),
editors:
(json['editors'] as List).map((e) => Editor.fromJson(e)).toList(),
operatingSystems: (json['operating_systems'] as List)
.map((e) => OperatingSystem.fromJson(e))
.toList(),
categories: (json['categories'] as List)
.map((e) => Category.fromJson(e))
.toList(),
);
}
}
class GrandTotal {
final int hours;
final int minutes;
final double totalSeconds;
final String digital;
final String decimal;
final String text;
GrandTotal({
required this.hours,
required this.minutes,
required this.totalSeconds,
required this.digital,
required this.decimal,
required this.text,
});
factory GrandTotal.fromJson(Map<String, dynamic> json) {
return GrandTotal(
hours: json['hours'],
minutes: json['minutes'],
totalSeconds: json['total_seconds'].toDouble(),
digital: json['digital'],
decimal: json['decimal'],
text: json['text'],
);
}
}
class Range {
final String start;
final String end;
final String date;
final String text;
final String timezone;
Range({
required this.start,
required this.end,
required this.date,
required this.text,
required this.timezone,
});
factory Range.fromJson(Map<String, dynamic> json) {
return Range(
start: json['start'],
end: json['end'],
date: json['date'],
text: json['text'],
timezone: json['timezone'],
);
}
}
class Project {
final String name;
final double totalSeconds;
final String? color;
final String digital;
final String decimal;
final String text;
final int hours;
final int minutes;
final int seconds;
final double percent;
Project({
required this.name,
required this.totalSeconds,
this.color,
required this.digital,
required this.decimal,
required this.text,
required this.hours,
required this.minutes,
required this.seconds,
required this.percent,
});
factory Project.fromJson(Map<String, dynamic> json) {
return Project(
name: json['name'],
totalSeconds: json['total_seconds'].toDouble(),
color: json['color'],
digital: json['digital'],
decimal: json['decimal'],
text: json['text'],
hours: json['hours'],
minutes: json['minutes'],
seconds: json['seconds'],
percent: json['percent'].toDouble(),
);
}
}
class Language {
final String name;
final double totalSeconds;
final String digital;
final String decimal;
final String text;
final int hours;
final int minutes;
final int seconds;
final double percent;
Language({
required this.name,
required this.totalSeconds,
required this.digital,
required this.decimal,
required this.text,
required this.hours,
required this.minutes,
required this.seconds,
required this.percent,
});
factory Language.fromJson(Map<String, dynamic> json) {
return Language(
name: json['name'],
totalSeconds: json['total_seconds'].toDouble(),
digital: json['digital'],
decimal: json['decimal'],
text: json['text'],
hours: json['hours'],
minutes: json['minutes'],
seconds: json['seconds'],
percent: json['percent'].toDouble(),
);
}
}
class Editor {
final String name;
final double totalSeconds;
final String digital;
final String decimal;
final String text;
final int hours;
final int minutes;
final int seconds;
final double percent;
Editor({
required this.name,
required this.totalSeconds,
required this.digital,
required this.decimal,
required this.text,
required this.hours,
required this.minutes,
required this.seconds,
required this.percent,
});
factory Editor.fromJson(Map<String, dynamic> json) {
return Editor(
name: json['name'],
totalSeconds: json['total_seconds'].toDouble(),
digital: json['digital'],
decimal: json['decimal'],
text: json['text'],
hours: json['hours'],
minutes: json['minutes'],
seconds: json['seconds'],
percent: json['percent'].toDouble(),
);
}
}
class OperatingSystem {
final String name;
final double totalSeconds;
final String digital;
final String decimal;
final String text;
final int hours;
final int minutes;
final int seconds;
final double percent;
OperatingSystem({
required this.name,
required this.totalSeconds,
required this.digital,
required this.decimal,
required this.text,
required this.hours,
required this.minutes,
required this.seconds,
required this.percent,
});
factory OperatingSystem.fromJson(Map<String, dynamic> json) {
return OperatingSystem(
name: json['name'],
totalSeconds: json['total_seconds'].toDouble(),
digital: json['digital'],
decimal: json['decimal'],
text: json['text'],
hours: json['hours'],
minutes: json['minutes'],
seconds: json['seconds'],
percent: json['percent'].toDouble(),
);
}
}
class Category {
final String name;
final double totalSeconds;
final String digital;
final String decimal;
final String text;
final int hours;
final int minutes;
final int seconds;
final double percent;
Category({
required this.name,
required this.totalSeconds,
required this.digital,
required this.decimal,
required this.text,
required this.hours,
required this.minutes,
required this.seconds,
required this.percent,
});
factory Category.fromJson(Map<String, dynamic> json) {
return Category(
name: json['name'],
totalSeconds: json['total_seconds'].toDouble(),
digital: json['digital'],
decimal: json['decimal'],
text: json['text'],
hours: json['hours'],
minutes: json['minutes'],
seconds: json['seconds'],
percent: json['percent'].toDouble(),
);
}
}
class CumulativeTotal {
final double seconds;
final String text;
final String digital;
final String decimal;
CumulativeTotal({
required this.seconds,
required this.text,
required this.digital,
required this.decimal,
});
factory CumulativeTotal.fromJson(Map<String, dynamic> json) {
return CumulativeTotal(
seconds: json['seconds'].toDouble(),
text: json['text'],
digital: json['digital'],
decimal: json['decimal'],
);
}
}
class DailyAverage {
final int holidays;
final int daysMinusHolidays;
final int daysIncludingHolidays;
final int seconds;
final int secondsIncludingOtherLanguage;
final String text;
final String textIncludingOtherLanguage;
DailyAverage({
required this.holidays,
required this.daysMinusHolidays,
required this.daysIncludingHolidays,
required this.seconds,
required this.secondsIncludingOtherLanguage,
required this.text,
required this.textIncludingOtherLanguage,
});
factory DailyAverage.fromJson(Map<String, dynamic> json) {
return DailyAverage(
holidays: json['holidays'],
daysMinusHolidays: json['days_minus_holidays'],
daysIncludingHolidays: json['days_including_holidays'],
seconds: json['seconds'],
secondsIncludingOtherLanguage: json['seconds_including_other_language'],
text: json['text'],
textIncludingOtherLanguage: json['text_including_other_language'],
);
}
}

View File

@ -6,6 +6,7 @@ import '../widgets/gitea_card.dart';
import '../widgets/kodbox_card.dart';
import '../widgets/github_card.dart';
import 'settings_screen.dart';
import 'wakatime_screen.dart';
class HomeScreen extends StatefulWidget {
const HomeScreen({super.key});
@ -16,7 +17,13 @@ class HomeScreen extends StatefulWidget {
class _HomeScreenState extends State<HomeScreen> {
int _selectedIndex = 0;
final List<String> _platforms = ['LeetCode', 'Gitea', 'KodBox', 'GitHub'];
final List<String> _platforms = [
'LeetCode',
'Gitea',
'KodBox',
'GitHub',
'Wakatime'
];
@override
void initState() {
@ -82,6 +89,8 @@ class _HomeScreenState extends State<HomeScreen> {
return Icons.folder;
case 'GitHub':
return Icons.code;
case 'Wakatime':
return Icons.timer;
default:
return Icons.help_outline;
}
@ -97,6 +106,8 @@ class _HomeScreenState extends State<HomeScreen> {
return const KodBoxCard();
case 'GitHub':
return const GithubCard();
case 'Wakatime':
return const WakatimeScreen();
default:
return const Center(child: Text('未知平台'));
}

View File

@ -18,6 +18,7 @@ class _SettingsScreenState extends State<SettingsScreen> {
final _kodboxController = TextEditingController();
final _githubUsernameController = TextEditingController();
final _githubTokenController = TextEditingController();
final _wakatimeTokenController = TextEditingController();
@override
void initState() {
@ -34,6 +35,7 @@ class _SettingsScreenState extends State<SettingsScreen> {
_kodboxController.dispose();
_githubUsernameController.dispose();
_githubTokenController.dispose();
_wakatimeTokenController.dispose();
super.dispose();
}
@ -54,6 +56,8 @@ class _SettingsScreenState extends State<SettingsScreen> {
authService.credentials['github_username'] ?? '';
_githubTokenController.text =
authService.credentials['github_token'] ?? '';
_wakatimeTokenController.text =
authService.credentials['wakatime_token'] ?? '';
});
}
@ -97,6 +101,12 @@ class _SettingsScreenState extends State<SettingsScreen> {
_githubTokenController.text,
);
}
if (_wakatimeTokenController.text.isNotEmpty) {
await authService.saveConfigs(
'wakatime_token',
_wakatimeTokenController.text,
);
}
if (mounted) {
ScaffoldMessenger.of(
@ -206,6 +216,22 @@ class _SettingsScreenState extends State<SettingsScreen> {
),
],
),
const SizedBox(height: 16),
_buildPlatformSettings(
title: 'Wakatime 设置',
icon: Icons.timer,
children: [
TextFormField(
controller: _wakatimeTokenController,
decoration: const InputDecoration(
labelText: 'API Token',
hintText: '请输入 Wakatime API Token',
border: OutlineInputBorder(),
),
obscureText: true,
),
],
),
const SizedBox(height: 24),
ElevatedButton(onPressed: _saveSettings, child: const Text('保存设置')),
],

View File

@ -0,0 +1,337 @@
import 'package:flutter/material.dart';
import 'package:fl_chart/fl_chart.dart';
import 'package:provider/provider.dart';
import '../models/wakatime_summary.dart';
import '../services/wakatime_service.dart';
import '../services/auth_service.dart';
class WakatimeScreen extends StatefulWidget {
const WakatimeScreen({super.key});
@override
State<WakatimeScreen> createState() => _WakatimeScreenState();
}
class _WakatimeScreenState extends State<WakatimeScreen> {
final WakatimeService _wakatimeService = WakatimeService();
WakatimeSummary? _summary;
bool _isLoading = false;
String? _error;
final TextEditingController _startDateController = TextEditingController();
final TextEditingController _endDateController = TextEditingController();
int? _touchedIndex;
//
final List<Color> _colors = [
Colors.blue,
Colors.red,
Colors.green,
Colors.orange,
Colors.purple,
Colors.teal,
Colors.pink,
Colors.indigo,
Colors.amber,
Colors.cyan,
Colors.lime,
Colors.brown,
];
@override
void initState() {
super.initState();
//
final now = DateTime.now();
_startDateController.text =
_formatDate(now.subtract(const Duration(days: 1)));
_endDateController.text = _formatDate(now);
}
String _formatDate(DateTime date) {
return '${date.year}-${date.month.toString().padLeft(2, '0')}-${date.day.toString().padLeft(2, '0')}';
}
String _formatDuration(int seconds) {
final hours = seconds ~/ 3600;
final minutes = (seconds % 3600) ~/ 60;
return '${hours}小时${minutes}分钟';
}
Future<void> _fetchData() async {
final authService = Provider.of<AuthService>(context, listen: false);
final token = authService.credentials['wakatime_token'];
if (token == null || token.isEmpty) {
setState(() => _error = '请在设置中配置Wakatime API Token');
return;
}
setState(() {
_isLoading = true;
_error = null;
});
try {
final summary = await _wakatimeService.getSummary(
token,
_startDateController.text,
_endDateController.text,
);
setState(() {
_summary = summary;
_isLoading = false;
});
} catch (e) {
setState(() {
_error = e.toString();
_isLoading = false;
});
}
}
Widget _buildLegend(List<dynamic> data, String Function(dynamic) getName) {
return Wrap(
spacing: 16,
runSpacing: 8,
children: data.asMap().entries.map((entry) {
final index = entry.key;
final item = entry.value;
final isTouched = index == _touchedIndex;
return Row(
mainAxisSize: MainAxisSize.min,
children: [
Container(
width: 16,
height: 16,
color: _colors[index % _colors.length],
),
const SizedBox(width: 8),
Text(
'${getName(item)}${isTouched ? ' (${_formatDuration(item.totalSeconds)})' : ''}',
style: TextStyle(
fontSize: 12,
fontWeight: isTouched ? FontWeight.bold : FontWeight.normal,
),
),
],
);
}).toList(),
);
}
Widget _buildPieChart(
List<dynamic> data, String title, String Function(dynamic) getName) {
return Column(
children: [
Text(
title,
style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
),
const SizedBox(height: 16),
_buildLegend(data, getName),
const SizedBox(height: 16),
SizedBox(
height: 300,
child: PieChart(
PieChartData(
pieTouchData: PieTouchData(
touchCallback: (FlTouchEvent event, pieTouchResponse) {
setState(() {
if (!event.isInterestedForInteractions ||
pieTouchResponse == null ||
pieTouchResponse.touchedSection == null) {
_touchedIndex = null;
return;
}
_touchedIndex =
pieTouchResponse.touchedSection!.touchedSectionIndex;
});
},
),
sections: data.asMap().entries.map((entry) {
final index = entry.key;
final item = entry.value;
final isTouched = index == _touchedIndex;
final percent = item.percent;
return PieChartSectionData(
value: item.totalSeconds.toDouble(),
title: percent > 5
? '${getName(item)}\n${percent.toStringAsFixed(1)}%'
: '',
radius: isTouched ? 110 : 100,
titleStyle: const TextStyle(
fontSize: 12,
fontWeight: FontWeight.bold,
color: Colors.white,
),
color: _colors[index % _colors.length],
);
}).toList(),
),
),
),
],
);
}
Widget _buildProjectList() {
final projects = _summary!.getAggregatedProjects();
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'项目时间分布',
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
),
const SizedBox(height: 16),
ListView.builder(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
itemCount: projects.length,
itemBuilder: (context, index) {
final project = projects[index];
return Card(
margin: const EdgeInsets.only(bottom: 8),
child: Padding(
padding: const EdgeInsets.all(16),
child: Row(
children: [
Container(
width: 16,
height: 16,
color: _colors[index % _colors.length],
),
const SizedBox(width: 16),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
project.name,
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 4),
Text(
_formatDuration(project.totalSeconds.toInt()),
style: TextStyle(
fontSize: 14,
color: Colors.grey[600],
),
),
],
),
),
Text(
'${project.percent.toStringAsFixed(1)}%',
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
),
),
],
),
),
);
},
),
],
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Wakatime 统计'),
),
body: SingleChildScrollView(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
//
Row(
children: [
Expanded(
child: TextField(
controller: _startDateController,
decoration: const InputDecoration(
labelText: '开始日期',
hintText: 'YYYY-MM-DD',
),
),
),
const SizedBox(width: 16),
Expanded(
child: TextField(
controller: _endDateController,
decoration: const InputDecoration(
labelText: '结束日期',
hintText: 'YYYY-MM-DD',
),
),
),
],
),
const SizedBox(height: 16),
//
ElevatedButton(
onPressed: _isLoading ? null : _fetchData,
child: _isLoading
? const CircularProgressIndicator()
: const Text('获取数据'),
),
const SizedBox(height: 16),
//
if (_error != null)
Text(
_error!,
style: const TextStyle(color: Colors.red),
),
//
if (_summary != null) ...[
const Text(
'总编码时间',
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
),
Text(_summary!.cumulativeTotal.text),
const SizedBox(height: 24),
// 使
_buildPieChart(
_summary!.getAggregatedLanguages(),
'编程语言使用情况',
(lang) => lang.name,
),
const SizedBox(height: 24),
// 使
_buildPieChart(
_summary!.getAggregatedEditors(),
'编辑器使用情况',
(editor) => editor.name,
),
const SizedBox(height: 24),
//
_buildProjectList(),
],
],
),
),
);
}
@override
void dispose() {
_startDateController.dispose();
_endDateController.dispose();
super.dispose();
}
}

View File

@ -24,6 +24,7 @@ class AuthService extends ChangeNotifier {
final kodboxToken = await _storage.read(key: 'kodbox_token');
final githubUserName = await _storage.read(key: 'github_username');
final githubToken = await _storage.read(key: 'github_token');
final wakatimeToken = await _storage.read(key: 'wakatime_token');
if (leetcodeCookie != null) {
_credentials['leetcode_cookie'] = leetcodeCookie;
@ -46,6 +47,9 @@ class AuthService extends ChangeNotifier {
if (githubToken != null) {
_credentials['github_token'] = githubToken;
}
if (wakatimeToken != null) {
_credentials['wakatime_token'] = wakatimeToken;
}
_isAuthenticated = true;
notifyListeners();
}

View File

@ -0,0 +1,27 @@
import 'dart:convert';
import 'package:http/http.dart' as http;
import '../models/wakatime_summary.dart';
class WakatimeService {
static const String baseUrl = 'https://wakatime.com/api/v1';
Future<WakatimeSummary> getSummary(
String token,
String startDate,
String endDate,
) async {
final url = Uri.parse(
'$baseUrl/users/current/summaries?start=$startDate&end=$endDate',
);
final response = await http.get(
url,
headers: {'Authorization': 'Basic ${base64Encode(utf8.encode(token))}'},
);
if (response.statusCode == 200) {
return WakatimeSummary.fromJson(json.decode(response.body));
} else {
throw Exception('Failed to load summary: ${response.statusCode}');
}
}
}

View File

@ -81,6 +81,14 @@ packages:
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
source: hosted
version: "1.0.8"
equatable:
dependency: transitive
description:
name: equatable
sha256: "567c64b3cb4cf82397aac55f4f0cbd3ca20d77c6c03bedbc4ceaddc08904aef7"
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
source: hosted
version: "2.0.7"
fake_async:
dependency: transitive
description:
@ -113,6 +121,14 @@ packages:
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
source: hosted
version: "1.1.1"
fl_chart:
dependency: "direct main"
description:
name: fl_chart
sha256: "5a74434cc83bf64346efb562f1a06eefaf1bcb530dc3d96a104f631a1eff8d79"
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
source: hosted
version: "0.65.0"
flutter:
dependency: "direct main"
description: flutter
@ -130,10 +146,10 @@ packages:
dependency: "direct dev"
description:
name: flutter_lints
sha256: "5398f14efa795ffb7a33e9b6a08798b26a180edac4ad7db3f231e40f82ce11e1"
sha256: a25a15ebbdfc33ab1cd26c63a6ee519df92338a9c10f122adda92938253bef04
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
source: hosted
version: "5.0.0"
version: "2.0.3"
flutter_secure_storage:
dependency: "direct main"
description:
@ -252,10 +268,10 @@ packages:
dependency: transitive
description:
name: lints
sha256: c35bb79562d980e9a453fc715854e1ed39e24e7d0297a880ef54e17f9874a9d7
sha256: "0a217c6c989d21039f1498c3ed9f3ed71b354e69873f13a8dfc3c9fe76f1b452"
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
source: hosted
version: "5.1.1"
version: "2.1.1"
matcher:
dependency: transitive
description:

View File

@ -19,7 +19,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev
version: 1.0.0+1
environment:
sdk: ^3.7.0
sdk: '>=3.2.3 <4.0.0'
# Dependencies specify other packages that your package needs in order to work.
# To automatically upgrade your package dependencies to the latest versions
@ -33,14 +33,15 @@ dependencies:
# The following adds the Cupertino Icons font to your application.
# Use with the CupertinoIcons class for iOS style icons.
cupertino_icons: ^1.0.8
http: ^1.2.0
cupertino_icons: ^1.0.2
http: ^1.1.0
provider: ^6.1.1
shared_preferences: ^2.2.2
intl: ^0.19.0
flutter_secure_storage: ^9.0.0
cached_network_image: ^3.3.1
timeago: ^3.7.1
fl_chart: ^0.65.0
dev_dependencies:
flutter_test:
@ -51,7 +52,7 @@ dev_dependencies:
# activated in the `analysis_options.yaml` file located at the root of your
# package. See that file for information about deactivating specific lint
# rules and activating additional ones.
flutter_lints: ^5.0.0
flutter_lints: ^2.0.0
# For information on the generic Dart part of this file, see the
# following page: https://dart.dev/tools/pub/pubspec