flutter_dashboard/lib/models/wakatime_summary.dart
jdysya 569b4f4498 feat(wakatime): 添加 Wakatime 平台支持
- 在首页添加 Wakatime 卡片和相关功能
- 在设置页面添加 Wakatime API Token 配置
- 更新 auth_service 以支持 Wakatime 认证
- 添加 fl_chart 依赖用于 Wakatime 数据图表展示
- 更新 flutter_lints 版本到 2.0.0
2025-06-10 12:07:33 +08:00

542 lines
15 KiB
Dart

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'],
);
}
}