feat(gt): add contribution heatmap

closes #104
This commit is contained in:
Rongjian Zhang 2020-10-05 16:07:07 +08:00
parent 8a490abb10
commit d2bb3c4a5c
5 changed files with 147 additions and 33 deletions

View File

@ -111,3 +111,12 @@ class GiteaIssue {
factory GiteaIssue.fromJson(Map<String, dynamic> json) =>
_$GiteaIssueFromJson(json);
}
@JsonSerializable(fieldRename: FieldRename.snake)
class GiteaHeatmapItem {
int timestamp;
int contributions;
GiteaHeatmapItem();
factory GiteaHeatmapItem.fromJson(Map<String, dynamic> json) =>
_$GiteaHeatmapItemFromJson(json);
}

View File

@ -196,3 +196,15 @@ Map<String, dynamic> _$GiteaIssueToJson(GiteaIssue instance) =>
'updated_at': instance.updatedAt?.toIso8601String(),
'html_url': instance.htmlUrl,
};
GiteaHeatmapItem _$GiteaHeatmapItemFromJson(Map<String, dynamic> json) {
return GiteaHeatmapItem()
..timestamp = json['timestamp'] as int
..contributions = json['contributions'] as int;
}
Map<String, dynamic> _$GiteaHeatmapItemToJson(GiteaHeatmapItem instance) =>
<String, dynamic>{
'timestamp': instance.timestamp,
'contributions': instance.contributions,
};

View File

@ -6,6 +6,7 @@ import 'package:git_touch/scaffolds/refresh_stateful.dart';
import 'package:git_touch/utils/utils.dart';
import 'package:git_touch/widgets/action_entry.dart';
import 'package:git_touch/widgets/app_bar_title.dart';
import 'package:git_touch/widgets/contribution.dart';
import 'package:git_touch/widgets/mutation_button.dart';
import 'package:git_touch/widgets/entry_item.dart';
import 'package:git_touch/widgets/repository_item.dart';
@ -112,37 +113,12 @@ class GhUserScreen extends StatelessWidget {
),
]),
CommonStyle.border,
Container(
alignment: Alignment.center,
padding: CommonStyle.padding,
child: SingleChildScrollView(
scrollDirection: Axis.horizontal,
reverse: true,
child: Wrap(
spacing: 3,
children: p.contributionsCollection.contributionCalendar.weeks
.map((week) {
return Wrap(
direction: Axis.vertical,
spacing: 3,
children: week.contributionDays.map((day) {
var color = convertColor(day.color);
if (theme.brightness == Brightness.dark) {
color = Color.fromRGBO(0xff - color.red,
0xff - color.green, 0xff - color.blue, 1);
}
return SizedBox(
width: 10,
height: 10,
child: DecoratedBox(
decoration: BoxDecoration(color: color),
),
);
}).toList(),
);
}).toList(),
),
),
ContributionWidget(
weeks: p.contributionsCollection.contributionCalendar.weeks.map((e) {
return e.contributionDays.map((d) {
return ContributionDay(hexColor: d.color);
});
}),
),
CommonStyle.border,
TableView(

View File

@ -1,3 +1,4 @@
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
import 'package:git_touch/models/auth.dart';
@ -5,6 +6,7 @@ import 'package:git_touch/models/gitea.dart';
import 'package:git_touch/scaffolds/refresh_stateful.dart';
import 'package:git_touch/utils/utils.dart';
import 'package:git_touch/widgets/action_entry.dart';
import 'package:git_touch/widgets/contribution.dart';
import 'package:git_touch/widgets/repository_item.dart';
import 'package:git_touch/widgets/user_header.dart';
import 'package:provider/provider.dart';
@ -18,17 +20,42 @@ class GtUserScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return RefreshStatefulScaffold<Tuple2<GiteaUser, List<GiteaRepository>>>(
return RefreshStatefulScaffold<
Tuple3<GiteaUser, List<GiteaRepository>, List<List<ContributionDay>>>>(
title: Text(isViewer ? 'Me' : 'User'),
fetchData: () async {
final auth = context.read<AuthModel>();
final res = await Future.wait([
auth.fetchGitea(isViewer ? '/user' : '/users/$login'),
auth.fetchGitea(isViewer ? '/user/repos' : '/users/$login/repos'),
auth.fetchGitea(
'/users/${login ?? auth.activeAccount.login}/heatmap'),
]);
return Tuple2(
final heatmapItems = [
for (final v in res[2]) GiteaHeatmapItem.fromJson(v)
];
List<List<ContributionDay>> heatmapWeeks = [[]];
for (var i = 0; i < heatmapItems.length; i++) {
if (i > 0 &&
heatmapItems[i].timestamp - heatmapItems[i - 1].timestamp >
86400) {
if (heatmapWeeks.last.length == 7) {
heatmapWeeks.add([]);
}
heatmapWeeks.last.add(ContributionDay(count: 0));
} else {
if (heatmapWeeks.last.length == 7) {
heatmapWeeks.add([]);
}
heatmapWeeks.last
.add(ContributionDay(count: heatmapItems[i].contributions));
}
}
return Tuple3(
GiteaUser.fromJson(res[0]),
[for (var v in res[1]) GiteaRepository.fromJson(v)],
heatmapWeeks,
);
},
action: isViewer
@ -40,6 +67,7 @@ class GtUserScreen extends StatelessWidget {
bodyBuilder: (data, _) {
final user = data.item1;
final repos = data.item2;
final heatmapWeeks = data.item3;
return Column(
children: <Widget>[
@ -51,6 +79,8 @@ class GtUserScreen extends StatelessWidget {
bio: '',
),
CommonStyle.border,
ContributionWidget(weeks: heatmapWeeks),
CommonStyle.border,
Column(
children: <Widget>[
for (var v in repos)

View File

@ -0,0 +1,87 @@
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:git_touch/models/theme.dart';
import 'package:git_touch/utils/utils.dart';
import 'package:provider/provider.dart';
class ContributionDay {
String hexColor;
int count;
Color color;
ContributionDay({this.hexColor, this.count, this.color})
: assert(hexColor != null || count != null || color != null);
}
class ContributionWidget extends StatelessWidget {
final Iterable<Iterable<ContributionDay>> weeks;
ContributionWidget({@required this.weeks}) {
int maxCount;
for (var week in weeks) {
for (var day in week) {
if (day.count != null) {
if (maxCount == null) {
for (var week in weeks) {
for (var day in week) {
maxCount = max(day.count, maxCount ?? 0);
}
}
}
if (day.count == 0) {
day.hexColor = emptyColor;
} else {
final level = (day.count * 4) ~/ (maxCount + 1);
day.hexColor = colors[level];
}
}
if (day.hexColor != null) {
day.color = convertColor(day.hexColor);
}
}
}
}
static const emptyColor = '#ebedf0';
static const colors = ['#9be9a8', '#40c463', '#30a14e', '#216e39'];
static Color _revert(Color color) {
return Color.fromRGBO(
0xff - color.red, 0xff - color.green, 0xff - color.blue, 1);
}
@override
Widget build(BuildContext context) {
final theme = context.watch<ThemeModel>();
return Container(
alignment: Alignment.center,
padding: CommonStyle.padding,
child: SingleChildScrollView(
scrollDirection: Axis.horizontal,
reverse: true,
child: Wrap(
spacing: 3,
children: [
for (final week in weeks)
Wrap(
direction: Axis.vertical,
spacing: 3,
children: [
for (final day in week)
SizedBox(
width: 10,
height: 10,
child: DecoratedBox(
decoration: BoxDecoration(
color: theme.brightness == Brightness.dark
? _revert(day.color)
: day.color),
),
)
],
)
],
),
),
);
}
}