♻️ Refactored heatmap
This commit is contained in:
@@ -4,25 +4,23 @@ part 'heatmap.freezed.dart';
|
|||||||
part 'heatmap.g.dart';
|
part 'heatmap.g.dart';
|
||||||
|
|
||||||
@freezed
|
@freezed
|
||||||
sealed class SnPublisherHeatmap with _$SnPublisherHeatmap {
|
sealed class SnHeatmap with _$SnPublisherHeatmap {
|
||||||
const factory SnPublisherHeatmap({
|
const factory SnHeatmap({
|
||||||
required String unit,
|
required String unit,
|
||||||
@JsonKey(name: 'period_start') required DateTime periodStart,
|
@JsonKey(name: 'period_start') required DateTime periodStart,
|
||||||
@JsonKey(name: 'period_end') required DateTime periodEnd,
|
@JsonKey(name: 'period_end') required DateTime periodEnd,
|
||||||
required List<SnPublisherHeatmapItem> items,
|
required List<SnHeatmapItem> items,
|
||||||
}) = _SnPublisherHeatmap;
|
}) = _SnPublisherHeatmap;
|
||||||
|
|
||||||
factory SnPublisherHeatmap.fromJson(Map<String, dynamic> json) =>
|
factory SnHeatmap.fromJson(Map<String, dynamic> json) =>
|
||||||
_$SnPublisherHeatmapFromJson(json);
|
_$SnPublisherHeatmapFromJson(json);
|
||||||
}
|
}
|
||||||
|
|
||||||
@freezed
|
@freezed
|
||||||
sealed class SnPublisherHeatmapItem with _$SnPublisherHeatmapItem {
|
sealed class SnHeatmapItem with _$SnPublisherHeatmapItem {
|
||||||
const factory SnPublisherHeatmapItem({
|
const factory SnHeatmapItem({required DateTime date, required int count}) =
|
||||||
required DateTime date,
|
_SnPublisherHeatmapItem;
|
||||||
required int count,
|
|
||||||
}) = _SnPublisherHeatmapItem;
|
|
||||||
|
|
||||||
factory SnPublisherHeatmapItem.fromJson(Map<String, dynamic> json) =>
|
factory SnHeatmapItem.fromJson(Map<String, dynamic> json) =>
|
||||||
_$SnPublisherHeatmapItemFromJson(json);
|
_$SnPublisherHeatmapItemFromJson(json);
|
||||||
}
|
}
|
||||||
|
@@ -15,12 +15,12 @@ T _$identity<T>(T value) => value;
|
|||||||
/// @nodoc
|
/// @nodoc
|
||||||
mixin _$SnPublisherHeatmap {
|
mixin _$SnPublisherHeatmap {
|
||||||
|
|
||||||
String get unit;@JsonKey(name: 'period_start') DateTime get periodStart;@JsonKey(name: 'period_end') DateTime get periodEnd; List<SnPublisherHeatmapItem> get items;
|
String get unit;@JsonKey(name: 'period_start') DateTime get periodStart;@JsonKey(name: 'period_end') DateTime get periodEnd; List<SnHeatmapItem> get items;
|
||||||
/// Create a copy of SnPublisherHeatmap
|
/// Create a copy of SnPublisherHeatmap
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
@pragma('vm:prefer-inline')
|
@pragma('vm:prefer-inline')
|
||||||
$SnPublisherHeatmapCopyWith<SnPublisherHeatmap> get copyWith => _$SnPublisherHeatmapCopyWithImpl<SnPublisherHeatmap>(this as SnPublisherHeatmap, _$identity);
|
$SnPublisherHeatmapCopyWith<SnHeatmap> get copyWith => _$SnPublisherHeatmapCopyWithImpl<SnHeatmap>(this as SnHeatmap, _$identity);
|
||||||
|
|
||||||
/// Serializes this SnPublisherHeatmap to a JSON map.
|
/// Serializes this SnPublisherHeatmap to a JSON map.
|
||||||
Map<String, dynamic> toJson();
|
Map<String, dynamic> toJson();
|
||||||
@@ -28,7 +28,7 @@ $SnPublisherHeatmapCopyWith<SnPublisherHeatmap> get copyWith => _$SnPublisherHea
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
bool operator ==(Object other) {
|
bool operator ==(Object other) {
|
||||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is SnPublisherHeatmap&&(identical(other.unit, unit) || other.unit == unit)&&(identical(other.periodStart, periodStart) || other.periodStart == periodStart)&&(identical(other.periodEnd, periodEnd) || other.periodEnd == periodEnd)&&const DeepCollectionEquality().equals(other.items, items));
|
return identical(this, other) || (other.runtimeType == runtimeType&&other is SnHeatmap&&(identical(other.unit, unit) || other.unit == unit)&&(identical(other.periodStart, periodStart) || other.periodStart == periodStart)&&(identical(other.periodEnd, periodEnd) || other.periodEnd == periodEnd)&&const DeepCollectionEquality().equals(other.items, items));
|
||||||
}
|
}
|
||||||
|
|
||||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
@@ -45,10 +45,10 @@ String toString() {
|
|||||||
|
|
||||||
/// @nodoc
|
/// @nodoc
|
||||||
abstract mixin class $SnPublisherHeatmapCopyWith<$Res> {
|
abstract mixin class $SnPublisherHeatmapCopyWith<$Res> {
|
||||||
factory $SnPublisherHeatmapCopyWith(SnPublisherHeatmap value, $Res Function(SnPublisherHeatmap) _then) = _$SnPublisherHeatmapCopyWithImpl;
|
factory $SnPublisherHeatmapCopyWith(SnHeatmap value, $Res Function(SnHeatmap) _then) = _$SnPublisherHeatmapCopyWithImpl;
|
||||||
@useResult
|
@useResult
|
||||||
$Res call({
|
$Res call({
|
||||||
String unit,@JsonKey(name: 'period_start') DateTime periodStart,@JsonKey(name: 'period_end') DateTime periodEnd, List<SnPublisherHeatmapItem> items
|
String unit,@JsonKey(name: 'period_start') DateTime periodStart,@JsonKey(name: 'period_end') DateTime periodEnd, List<SnHeatmapItem> items
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
@@ -60,8 +60,8 @@ class _$SnPublisherHeatmapCopyWithImpl<$Res>
|
|||||||
implements $SnPublisherHeatmapCopyWith<$Res> {
|
implements $SnPublisherHeatmapCopyWith<$Res> {
|
||||||
_$SnPublisherHeatmapCopyWithImpl(this._self, this._then);
|
_$SnPublisherHeatmapCopyWithImpl(this._self, this._then);
|
||||||
|
|
||||||
final SnPublisherHeatmap _self;
|
final SnHeatmap _self;
|
||||||
final $Res Function(SnPublisherHeatmap) _then;
|
final $Res Function(SnHeatmap) _then;
|
||||||
|
|
||||||
/// Create a copy of SnPublisherHeatmap
|
/// Create a copy of SnPublisherHeatmap
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
@@ -71,15 +71,15 @@ unit: null == unit ? _self.unit : unit // ignore: cast_nullable_to_non_nullable
|
|||||||
as String,periodStart: null == periodStart ? _self.periodStart : periodStart // ignore: cast_nullable_to_non_nullable
|
as String,periodStart: null == periodStart ? _self.periodStart : periodStart // ignore: cast_nullable_to_non_nullable
|
||||||
as DateTime,periodEnd: null == periodEnd ? _self.periodEnd : periodEnd // ignore: cast_nullable_to_non_nullable
|
as DateTime,periodEnd: null == periodEnd ? _self.periodEnd : periodEnd // ignore: cast_nullable_to_non_nullable
|
||||||
as DateTime,items: null == items ? _self.items : items // ignore: cast_nullable_to_non_nullable
|
as DateTime,items: null == items ? _self.items : items // ignore: cast_nullable_to_non_nullable
|
||||||
as List<SnPublisherHeatmapItem>,
|
as List<SnHeatmapItem>,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// Adds pattern-matching-related methods to [SnPublisherHeatmap].
|
/// Adds pattern-matching-related methods to [SnHeatmap].
|
||||||
extension SnPublisherHeatmapPatterns on SnPublisherHeatmap {
|
extension SnPublisherHeatmapPatterns on SnHeatmap {
|
||||||
/// A variant of `map` that fallback to returning `orElse`.
|
/// A variant of `map` that fallback to returning `orElse`.
|
||||||
///
|
///
|
||||||
/// It is equivalent to doing:
|
/// It is equivalent to doing:
|
||||||
@@ -153,7 +153,7 @@ return $default(_that);case _:
|
|||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
|
|
||||||
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String unit, @JsonKey(name: 'period_start') DateTime periodStart, @JsonKey(name: 'period_end') DateTime periodEnd, List<SnPublisherHeatmapItem> items)? $default,{required TResult orElse(),}) {final _that = this;
|
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String unit, @JsonKey(name: 'period_start') DateTime periodStart, @JsonKey(name: 'period_end') DateTime periodEnd, List<SnHeatmapItem> items)? $default,{required TResult orElse(),}) {final _that = this;
|
||||||
switch (_that) {
|
switch (_that) {
|
||||||
case _SnPublisherHeatmap() when $default != null:
|
case _SnPublisherHeatmap() when $default != null:
|
||||||
return $default(_that.unit,_that.periodStart,_that.periodEnd,_that.items);case _:
|
return $default(_that.unit,_that.periodStart,_that.periodEnd,_that.items);case _:
|
||||||
@@ -174,7 +174,7 @@ return $default(_that.unit,_that.periodStart,_that.periodEnd,_that.items);case _
|
|||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
|
|
||||||
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String unit, @JsonKey(name: 'period_start') DateTime periodStart, @JsonKey(name: 'period_end') DateTime periodEnd, List<SnPublisherHeatmapItem> items) $default,) {final _that = this;
|
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String unit, @JsonKey(name: 'period_start') DateTime periodStart, @JsonKey(name: 'period_end') DateTime periodEnd, List<SnHeatmapItem> items) $default,) {final _that = this;
|
||||||
switch (_that) {
|
switch (_that) {
|
||||||
case _SnPublisherHeatmap():
|
case _SnPublisherHeatmap():
|
||||||
return $default(_that.unit,_that.periodStart,_that.periodEnd,_that.items);}
|
return $default(_that.unit,_that.periodStart,_that.periodEnd,_that.items);}
|
||||||
@@ -191,7 +191,7 @@ return $default(_that.unit,_that.periodStart,_that.periodEnd,_that.items);}
|
|||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
|
|
||||||
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String unit, @JsonKey(name: 'period_start') DateTime periodStart, @JsonKey(name: 'period_end') DateTime periodEnd, List<SnPublisherHeatmapItem> items)? $default,) {final _that = this;
|
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String unit, @JsonKey(name: 'period_start') DateTime periodStart, @JsonKey(name: 'period_end') DateTime periodEnd, List<SnHeatmapItem> items)? $default,) {final _that = this;
|
||||||
switch (_that) {
|
switch (_that) {
|
||||||
case _SnPublisherHeatmap() when $default != null:
|
case _SnPublisherHeatmap() when $default != null:
|
||||||
return $default(_that.unit,_that.periodStart,_that.periodEnd,_that.items);case _:
|
return $default(_that.unit,_that.periodStart,_that.periodEnd,_that.items);case _:
|
||||||
@@ -205,15 +205,15 @@ return $default(_that.unit,_that.periodStart,_that.periodEnd,_that.items);case _
|
|||||||
/// @nodoc
|
/// @nodoc
|
||||||
@JsonSerializable()
|
@JsonSerializable()
|
||||||
|
|
||||||
class _SnPublisherHeatmap implements SnPublisherHeatmap {
|
class _SnPublisherHeatmap implements SnHeatmap {
|
||||||
const _SnPublisherHeatmap({required this.unit, @JsonKey(name: 'period_start') required this.periodStart, @JsonKey(name: 'period_end') required this.periodEnd, required final List<SnPublisherHeatmapItem> items}): _items = items;
|
const _SnPublisherHeatmap({required this.unit, @JsonKey(name: 'period_start') required this.periodStart, @JsonKey(name: 'period_end') required this.periodEnd, required final List<SnHeatmapItem> items}): _items = items;
|
||||||
factory _SnPublisherHeatmap.fromJson(Map<String, dynamic> json) => _$SnPublisherHeatmapFromJson(json);
|
factory _SnPublisherHeatmap.fromJson(Map<String, dynamic> json) => _$SnPublisherHeatmapFromJson(json);
|
||||||
|
|
||||||
@override final String unit;
|
@override final String unit;
|
||||||
@override@JsonKey(name: 'period_start') final DateTime periodStart;
|
@override@JsonKey(name: 'period_start') final DateTime periodStart;
|
||||||
@override@JsonKey(name: 'period_end') final DateTime periodEnd;
|
@override@JsonKey(name: 'period_end') final DateTime periodEnd;
|
||||||
final List<SnPublisherHeatmapItem> _items;
|
final List<SnHeatmapItem> _items;
|
||||||
@override List<SnPublisherHeatmapItem> get items {
|
@override List<SnHeatmapItem> get items {
|
||||||
if (_items is EqualUnmodifiableListView) return _items;
|
if (_items is EqualUnmodifiableListView) return _items;
|
||||||
// ignore: implicit_dynamic_type
|
// ignore: implicit_dynamic_type
|
||||||
return EqualUnmodifiableListView(_items);
|
return EqualUnmodifiableListView(_items);
|
||||||
@@ -253,7 +253,7 @@ abstract mixin class _$SnPublisherHeatmapCopyWith<$Res> implements $SnPublisherH
|
|||||||
factory _$SnPublisherHeatmapCopyWith(_SnPublisherHeatmap value, $Res Function(_SnPublisherHeatmap) _then) = __$SnPublisherHeatmapCopyWithImpl;
|
factory _$SnPublisherHeatmapCopyWith(_SnPublisherHeatmap value, $Res Function(_SnPublisherHeatmap) _then) = __$SnPublisherHeatmapCopyWithImpl;
|
||||||
@override @useResult
|
@override @useResult
|
||||||
$Res call({
|
$Res call({
|
||||||
String unit,@JsonKey(name: 'period_start') DateTime periodStart,@JsonKey(name: 'period_end') DateTime periodEnd, List<SnPublisherHeatmapItem> items
|
String unit,@JsonKey(name: 'period_start') DateTime periodStart,@JsonKey(name: 'period_end') DateTime periodEnd, List<SnHeatmapItem> items
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
@@ -276,7 +276,7 @@ unit: null == unit ? _self.unit : unit // ignore: cast_nullable_to_non_nullable
|
|||||||
as String,periodStart: null == periodStart ? _self.periodStart : periodStart // ignore: cast_nullable_to_non_nullable
|
as String,periodStart: null == periodStart ? _self.periodStart : periodStart // ignore: cast_nullable_to_non_nullable
|
||||||
as DateTime,periodEnd: null == periodEnd ? _self.periodEnd : periodEnd // ignore: cast_nullable_to_non_nullable
|
as DateTime,periodEnd: null == periodEnd ? _self.periodEnd : periodEnd // ignore: cast_nullable_to_non_nullable
|
||||||
as DateTime,items: null == items ? _self._items : items // ignore: cast_nullable_to_non_nullable
|
as DateTime,items: null == items ? _self._items : items // ignore: cast_nullable_to_non_nullable
|
||||||
as List<SnPublisherHeatmapItem>,
|
as List<SnHeatmapItem>,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -292,7 +292,7 @@ mixin _$SnPublisherHeatmapItem {
|
|||||||
/// with the given fields replaced by the non-null parameter values.
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
@pragma('vm:prefer-inline')
|
@pragma('vm:prefer-inline')
|
||||||
$SnPublisherHeatmapItemCopyWith<SnPublisherHeatmapItem> get copyWith => _$SnPublisherHeatmapItemCopyWithImpl<SnPublisherHeatmapItem>(this as SnPublisherHeatmapItem, _$identity);
|
$SnPublisherHeatmapItemCopyWith<SnHeatmapItem> get copyWith => _$SnPublisherHeatmapItemCopyWithImpl<SnHeatmapItem>(this as SnHeatmapItem, _$identity);
|
||||||
|
|
||||||
/// Serializes this SnPublisherHeatmapItem to a JSON map.
|
/// Serializes this SnPublisherHeatmapItem to a JSON map.
|
||||||
Map<String, dynamic> toJson();
|
Map<String, dynamic> toJson();
|
||||||
@@ -300,7 +300,7 @@ $SnPublisherHeatmapItemCopyWith<SnPublisherHeatmapItem> get copyWith => _$SnPubl
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
bool operator ==(Object other) {
|
bool operator ==(Object other) {
|
||||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is SnPublisherHeatmapItem&&(identical(other.date, date) || other.date == date)&&(identical(other.count, count) || other.count == count));
|
return identical(this, other) || (other.runtimeType == runtimeType&&other is SnHeatmapItem&&(identical(other.date, date) || other.date == date)&&(identical(other.count, count) || other.count == count));
|
||||||
}
|
}
|
||||||
|
|
||||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
@@ -317,7 +317,7 @@ String toString() {
|
|||||||
|
|
||||||
/// @nodoc
|
/// @nodoc
|
||||||
abstract mixin class $SnPublisherHeatmapItemCopyWith<$Res> {
|
abstract mixin class $SnPublisherHeatmapItemCopyWith<$Res> {
|
||||||
factory $SnPublisherHeatmapItemCopyWith(SnPublisherHeatmapItem value, $Res Function(SnPublisherHeatmapItem) _then) = _$SnPublisherHeatmapItemCopyWithImpl;
|
factory $SnPublisherHeatmapItemCopyWith(SnHeatmapItem value, $Res Function(SnHeatmapItem) _then) = _$SnPublisherHeatmapItemCopyWithImpl;
|
||||||
@useResult
|
@useResult
|
||||||
$Res call({
|
$Res call({
|
||||||
DateTime date, int count
|
DateTime date, int count
|
||||||
@@ -332,8 +332,8 @@ class _$SnPublisherHeatmapItemCopyWithImpl<$Res>
|
|||||||
implements $SnPublisherHeatmapItemCopyWith<$Res> {
|
implements $SnPublisherHeatmapItemCopyWith<$Res> {
|
||||||
_$SnPublisherHeatmapItemCopyWithImpl(this._self, this._then);
|
_$SnPublisherHeatmapItemCopyWithImpl(this._self, this._then);
|
||||||
|
|
||||||
final SnPublisherHeatmapItem _self;
|
final SnHeatmapItem _self;
|
||||||
final $Res Function(SnPublisherHeatmapItem) _then;
|
final $Res Function(SnHeatmapItem) _then;
|
||||||
|
|
||||||
/// Create a copy of SnPublisherHeatmapItem
|
/// Create a copy of SnPublisherHeatmapItem
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
@@ -348,8 +348,8 @@ as int,
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// Adds pattern-matching-related methods to [SnPublisherHeatmapItem].
|
/// Adds pattern-matching-related methods to [SnHeatmapItem].
|
||||||
extension SnPublisherHeatmapItemPatterns on SnPublisherHeatmapItem {
|
extension SnPublisherHeatmapItemPatterns on SnHeatmapItem {
|
||||||
/// A variant of `map` that fallback to returning `orElse`.
|
/// A variant of `map` that fallback to returning `orElse`.
|
||||||
///
|
///
|
||||||
/// It is equivalent to doing:
|
/// It is equivalent to doing:
|
||||||
@@ -475,7 +475,7 @@ return $default(_that.date,_that.count);case _:
|
|||||||
/// @nodoc
|
/// @nodoc
|
||||||
@JsonSerializable()
|
@JsonSerializable()
|
||||||
|
|
||||||
class _SnPublisherHeatmapItem implements SnPublisherHeatmapItem {
|
class _SnPublisherHeatmapItem implements SnHeatmapItem {
|
||||||
const _SnPublisherHeatmapItem({required this.date, required this.count});
|
const _SnPublisherHeatmapItem({required this.date, required this.count});
|
||||||
factory _SnPublisherHeatmapItem.fromJson(Map<String, dynamic> json) => _$SnPublisherHeatmapItemFromJson(json);
|
factory _SnPublisherHeatmapItem.fromJson(Map<String, dynamic> json) => _$SnPublisherHeatmapItemFromJson(json);
|
||||||
|
|
||||||
|
@@ -13,10 +13,7 @@ _SnPublisherHeatmap _$SnPublisherHeatmapFromJson(Map<String, dynamic> json) =>
|
|||||||
periodEnd: DateTime.parse(json['period_end'] as String),
|
periodEnd: DateTime.parse(json['period_end'] as String),
|
||||||
items:
|
items:
|
||||||
(json['items'] as List<dynamic>)
|
(json['items'] as List<dynamic>)
|
||||||
.map(
|
.map((e) => SnHeatmapItem.fromJson(e as Map<String, dynamic>))
|
||||||
(e) =>
|
|
||||||
SnPublisherHeatmapItem.fromJson(e as Map<String, dynamic>),
|
|
||||||
)
|
|
||||||
.toList(),
|
.toList(),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@@ -1,7 +1,6 @@
|
|||||||
import 'package:dio/dio.dart';
|
import 'package:dio/dio.dart';
|
||||||
import 'package:dropdown_button2/dropdown_button2.dart';
|
import 'package:dropdown_button2/dropdown_button2.dart';
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:fl_heatmap/fl_heatmap.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
@@ -20,6 +19,7 @@ import 'package:island/widgets/app_scaffold.dart';
|
|||||||
import 'package:island/widgets/content/cloud_files.dart';
|
import 'package:island/widgets/content/cloud_files.dart';
|
||||||
import 'package:island/widgets/content/sheet.dart';
|
import 'package:island/widgets/content/sheet.dart';
|
||||||
import 'package:island/widgets/response.dart';
|
import 'package:island/widgets/response.dart';
|
||||||
|
import 'package:island/widgets/activity_heatmap.dart';
|
||||||
import 'package:material_symbols_icons/symbols.dart';
|
import 'package:material_symbols_icons/symbols.dart';
|
||||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||||
import 'package:riverpod_paging_utils/riverpod_paging_utils.dart';
|
import 'package:riverpod_paging_utils/riverpod_paging_utils.dart';
|
||||||
@@ -36,11 +36,11 @@ Future<SnPublisherStats?> publisherStats(Ref ref, String? uname) async {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@riverpod
|
@riverpod
|
||||||
Future<SnPublisherHeatmap?> publisherHeatmap(Ref ref, String? uname) async {
|
Future<SnHeatmap?> publisherHeatmap(Ref ref, String? uname) async {
|
||||||
if (uname == null) return null;
|
if (uname == null) return null;
|
||||||
final apiClient = ref.watch(apiClientProvider);
|
final apiClient = ref.watch(apiClientProvider);
|
||||||
final resp = await apiClient.get('/sphere/publishers/$uname/heatmap');
|
final resp = await apiClient.get('/sphere/publishers/$uname/heatmap');
|
||||||
return SnPublisherHeatmap.fromJson(resp.data);
|
return SnHeatmap.fromJson(resp.data);
|
||||||
}
|
}
|
||||||
|
|
||||||
@riverpod
|
@riverpod
|
||||||
@@ -601,7 +601,7 @@ class CreatorHubScreen extends HookConsumerWidget {
|
|||||||
|
|
||||||
class _PublisherStatsWidget extends StatelessWidget {
|
class _PublisherStatsWidget extends StatelessWidget {
|
||||||
final SnPublisherStats stats;
|
final SnPublisherStats stats;
|
||||||
final SnPublisherHeatmap? heatmap;
|
final SnHeatmap? heatmap;
|
||||||
const _PublisherStatsWidget({required this.stats, this.heatmap});
|
const _PublisherStatsWidget({required this.stats, this.heatmap});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -655,7 +655,7 @@ class _PublisherStatsWidget extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
if (heatmap != null) _PublisherHeatmapWidget(heatmap: heatmap!),
|
if (heatmap != null) ActivityHeatmapWidget(heatmap: heatmap!),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -1197,231 +1197,3 @@ class _PublisherInviteSheet extends HookConsumerWidget {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class _PublisherHeatmapWidget extends StatelessWidget {
|
|
||||||
final SnPublisherHeatmap heatmap;
|
|
||||||
const _PublisherHeatmapWidget({required this.heatmap});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
// Generate exactly 365 days ending at current date
|
|
||||||
final now = DateTime.now();
|
|
||||||
|
|
||||||
// Start from exactly 365 days ago
|
|
||||||
final startDate = now.subtract(const Duration(days: 365));
|
|
||||||
// End at current date
|
|
||||||
final endDate = now;
|
|
||||||
|
|
||||||
// Find monday of the week containing start date
|
|
||||||
final startMonday = startDate.subtract(
|
|
||||||
Duration(days: startDate.weekday - 1),
|
|
||||||
);
|
|
||||||
// Find sunday of the week containing end date
|
|
||||||
final endSunday = endDate.add(Duration(days: 7 - endDate.weekday));
|
|
||||||
|
|
||||||
// Generate weeks to cover exactly 365 days
|
|
||||||
final weeks = <DateTime>[];
|
|
||||||
var current = startMonday;
|
|
||||||
while (current.isBefore(endSunday) || current.isAtSameMomentAs(endSunday)) {
|
|
||||||
weeks.add(current);
|
|
||||||
current = current.add(const Duration(days: 7));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create data map for all dates in the range
|
|
||||||
final dataMap = <DateTime, double>{};
|
|
||||||
for (final week in weeks) {
|
|
||||||
for (var i = 0; i < 7; i++) {
|
|
||||||
final date = week.add(Duration(days: i));
|
|
||||||
// Only include dates within our 365-day range
|
|
||||||
if (date.isAfter(startDate.subtract(const Duration(days: 1))) &&
|
|
||||||
date.isBefore(endDate.add(const Duration(days: 1)))) {
|
|
||||||
final item = heatmap.items.firstWhere(
|
|
||||||
(e) =>
|
|
||||||
e.date.year == date.year &&
|
|
||||||
e.date.month == date.month &&
|
|
||||||
e.date.day == date.day,
|
|
||||||
orElse: () => SnPublisherHeatmapItem(date: date, count: 0),
|
|
||||||
);
|
|
||||||
dataMap[date] = item.count.toDouble();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Generate month labels for the top
|
|
||||||
final monthLabels = <String>[];
|
|
||||||
final monthPositions = <int>[];
|
|
||||||
final processedMonths =
|
|
||||||
<String>{}; // Track processed months to avoid duplicates
|
|
||||||
|
|
||||||
for (final week in weeks) {
|
|
||||||
final monthKey = '${week.year}-${week.month.toString().padLeft(2, '0')}';
|
|
||||||
|
|
||||||
// Only process each month once
|
|
||||||
if (!processedMonths.contains(monthKey)) {
|
|
||||||
processedMonths.add(monthKey);
|
|
||||||
|
|
||||||
// Find which week this month starts in
|
|
||||||
final firstDayOfMonth = DateTime(week.year, week.month, 1);
|
|
||||||
final monthStartMonday = firstDayOfMonth.subtract(
|
|
||||||
Duration(days: firstDayOfMonth.weekday - 1),
|
|
||||||
);
|
|
||||||
|
|
||||||
final monthStartWeekIndex = weeks.indexWhere(
|
|
||||||
(w) =>
|
|
||||||
w.year == monthStartMonday.year &&
|
|
||||||
w.month == monthStartMonday.month &&
|
|
||||||
w.day == monthStartMonday.day,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (monthStartWeekIndex != -1) {
|
|
||||||
monthLabels.add(_getMonthAbbreviation(week.month));
|
|
||||||
monthPositions.add(monthStartWeekIndex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
final heatmapData = HeatmapData(
|
|
||||||
rows: [
|
|
||||||
'Mon',
|
|
||||||
'Tue',
|
|
||||||
'Wed',
|
|
||||||
'Thu',
|
|
||||||
'Fri',
|
|
||||||
'Sat',
|
|
||||||
'Sun',
|
|
||||||
], // Days of week vertically
|
|
||||||
columns:
|
|
||||||
weeks
|
|
||||||
.map(
|
|
||||||
(w) =>
|
|
||||||
'${w.year}-${w.month.toString().padLeft(2, '0')}-${w.day.toString().padLeft(2, '0')}',
|
|
||||||
)
|
|
||||||
.toList(), // Weeks horizontally
|
|
||||||
items: [
|
|
||||||
for (int day = 0; day < 7; day++) // For each day of week (Mon-Sun)
|
|
||||||
for (final week in weeks) // For each week
|
|
||||||
HeatmapItem(
|
|
||||||
value: dataMap[week.add(Duration(days: day))] ?? 0.0,
|
|
||||||
unit: heatmap.unit,
|
|
||||||
xAxisLabel:
|
|
||||||
'${week.year}-${week.month.toString().padLeft(2, '0')}-${week.day.toString().padLeft(2, '0')}',
|
|
||||||
yAxisLabel:
|
|
||||||
day == 0
|
|
||||||
? 'Mon'
|
|
||||||
: day == 1
|
|
||||||
? 'Tue'
|
|
||||||
: day == 2
|
|
||||||
? 'Wed'
|
|
||||||
: day == 3
|
|
||||||
? 'Thu'
|
|
||||||
: day == 4
|
|
||||||
? 'Fri'
|
|
||||||
: day == 5
|
|
||||||
? 'Sat'
|
|
||||||
: 'Sun',
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
|
|
||||||
return Card(
|
|
||||||
margin: EdgeInsets.zero,
|
|
||||||
child: Padding(
|
|
||||||
padding: const EdgeInsets.all(16),
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
'activityHeatmap',
|
|
||||||
style: Theme.of(context).textTheme.titleMedium,
|
|
||||||
).tr(),
|
|
||||||
const Gap(8),
|
|
||||||
// Month labels row
|
|
||||||
Row(
|
|
||||||
children: [
|
|
||||||
const SizedBox(width: 30), // Space for day labels
|
|
||||||
...monthLabels.asMap().entries.map((entry) {
|
|
||||||
final monthIndex = entry.key;
|
|
||||||
final month = entry.value;
|
|
||||||
|
|
||||||
return Expanded(
|
|
||||||
child: Container(
|
|
||||||
alignment: Alignment.center,
|
|
||||||
child: Text(
|
|
||||||
month,
|
|
||||||
style: Theme.of(context).textTheme.bodySmall,
|
|
||||||
textAlign: TextAlign.center,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
const Gap(4),
|
|
||||||
Heatmap(
|
|
||||||
heatmapData: heatmapData,
|
|
||||||
rowsVisible: 7,
|
|
||||||
showXAxisLabels: false,
|
|
||||||
),
|
|
||||||
const Gap(8),
|
|
||||||
// Legend
|
|
||||||
Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.end,
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
'Less',
|
|
||||||
style: Theme.of(context).textTheme.bodySmall?.copyWith(
|
|
||||||
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const Gap(4),
|
|
||||||
// Color indicators (light to dark green)
|
|
||||||
...[
|
|
||||||
Colors.green.withOpacity(0.2),
|
|
||||||
Colors.green.withOpacity(0.4),
|
|
||||||
Colors.green.withOpacity(0.6),
|
|
||||||
Colors.green.withOpacity(0.8),
|
|
||||||
Colors.green,
|
|
||||||
].map(
|
|
||||||
(color) => Container(
|
|
||||||
width: 8,
|
|
||||||
height: 8,
|
|
||||||
margin: const EdgeInsets.symmetric(horizontal: 1),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: color,
|
|
||||||
borderRadius: BorderRadius.circular(2),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const Gap(4),
|
|
||||||
Text(
|
|
||||||
'More',
|
|
||||||
style: Theme.of(context).textTheme.bodySmall?.copyWith(
|
|
||||||
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
String _getMonthAbbreviation(int month) {
|
|
||||||
const monthNames = [
|
|
||||||
'Jan',
|
|
||||||
'Feb',
|
|
||||||
'Mar',
|
|
||||||
'Apr',
|
|
||||||
'May',
|
|
||||||
'Jun',
|
|
||||||
'Jul',
|
|
||||||
'Aug',
|
|
||||||
'Sep',
|
|
||||||
'Oct',
|
|
||||||
'Nov',
|
|
||||||
'Dec',
|
|
||||||
];
|
|
||||||
return monthNames[month - 1];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@@ -156,7 +156,7 @@ String _$publisherHeatmapHash() => r'780dfb05b8610a37cfcd937fd04cf5bbe9b298c9';
|
|||||||
const publisherHeatmapProvider = PublisherHeatmapFamily();
|
const publisherHeatmapProvider = PublisherHeatmapFamily();
|
||||||
|
|
||||||
/// See also [publisherHeatmap].
|
/// See also [publisherHeatmap].
|
||||||
class PublisherHeatmapFamily extends Family<AsyncValue<SnPublisherHeatmap?>> {
|
class PublisherHeatmapFamily extends Family<AsyncValue<SnHeatmap?>> {
|
||||||
/// See also [publisherHeatmap].
|
/// See also [publisherHeatmap].
|
||||||
const PublisherHeatmapFamily();
|
const PublisherHeatmapFamily();
|
||||||
|
|
||||||
@@ -188,8 +188,7 @@ class PublisherHeatmapFamily extends Family<AsyncValue<SnPublisherHeatmap?>> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// See also [publisherHeatmap].
|
/// See also [publisherHeatmap].
|
||||||
class PublisherHeatmapProvider
|
class PublisherHeatmapProvider extends AutoDisposeFutureProvider<SnHeatmap?> {
|
||||||
extends AutoDisposeFutureProvider<SnPublisherHeatmap?> {
|
|
||||||
/// See also [publisherHeatmap].
|
/// See also [publisherHeatmap].
|
||||||
PublisherHeatmapProvider(String? uname)
|
PublisherHeatmapProvider(String? uname)
|
||||||
: this._internal(
|
: this._internal(
|
||||||
@@ -220,7 +219,7 @@ class PublisherHeatmapProvider
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Override overrideWith(
|
Override overrideWith(
|
||||||
FutureOr<SnPublisherHeatmap?> Function(PublisherHeatmapRef provider) create,
|
FutureOr<SnHeatmap?> Function(PublisherHeatmapRef provider) create,
|
||||||
) {
|
) {
|
||||||
return ProviderOverride(
|
return ProviderOverride(
|
||||||
origin: this,
|
origin: this,
|
||||||
@@ -237,7 +236,7 @@ class PublisherHeatmapProvider
|
|||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
AutoDisposeFutureProviderElement<SnPublisherHeatmap?> createElement() {
|
AutoDisposeFutureProviderElement<SnHeatmap?> createElement() {
|
||||||
return _PublisherHeatmapProviderElement(this);
|
return _PublisherHeatmapProviderElement(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -257,13 +256,13 @@ class PublisherHeatmapProvider
|
|||||||
|
|
||||||
@Deprecated('Will be removed in 3.0. Use Ref instead')
|
@Deprecated('Will be removed in 3.0. Use Ref instead')
|
||||||
// ignore: unused_element
|
// ignore: unused_element
|
||||||
mixin PublisherHeatmapRef on AutoDisposeFutureProviderRef<SnPublisherHeatmap?> {
|
mixin PublisherHeatmapRef on AutoDisposeFutureProviderRef<SnHeatmap?> {
|
||||||
/// The parameter `uname` of this provider.
|
/// The parameter `uname` of this provider.
|
||||||
String? get uname;
|
String? get uname;
|
||||||
}
|
}
|
||||||
|
|
||||||
class _PublisherHeatmapProviderElement
|
class _PublisherHeatmapProviderElement
|
||||||
extends AutoDisposeFutureProviderElement<SnPublisherHeatmap?>
|
extends AutoDisposeFutureProviderElement<SnHeatmap?>
|
||||||
with PublisherHeatmapRef {
|
with PublisherHeatmapRef {
|
||||||
_PublisherHeatmapProviderElement(super.provider);
|
_PublisherHeatmapProviderElement(super.provider);
|
||||||
|
|
||||||
|
274
lib/widgets/activity_heatmap.dart
Normal file
274
lib/widgets/activity_heatmap.dart
Normal file
@@ -0,0 +1,274 @@
|
|||||||
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
|
import 'package:fl_heatmap/fl_heatmap.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
|
import 'package:gap/gap.dart';
|
||||||
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
|
import 'package:island/models/heatmap.dart';
|
||||||
|
|
||||||
|
/// A reusable heatmap widget for displaying activity data in GitHub-style layout.
|
||||||
|
/// Shows exactly 365 days of data ending at the current date.
|
||||||
|
class ActivityHeatmapWidget extends HookConsumerWidget {
|
||||||
|
final SnHeatmap heatmap;
|
||||||
|
|
||||||
|
const ActivityHeatmapWidget({super.key, required this.heatmap});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
|
final selectedItem = useState<HeatmapItem?>(null);
|
||||||
|
|
||||||
|
// Generate exactly 365 days ending at current date
|
||||||
|
final now = DateTime.now();
|
||||||
|
|
||||||
|
// Start from exactly 365 days ago
|
||||||
|
final startDate = now.subtract(const Duration(days: 365));
|
||||||
|
// End at current date
|
||||||
|
final endDate = now;
|
||||||
|
|
||||||
|
// Find monday of the week containing start date
|
||||||
|
final startMonday = startDate.subtract(
|
||||||
|
Duration(days: startDate.weekday - 1),
|
||||||
|
);
|
||||||
|
// Find sunday of the week containing end date
|
||||||
|
final endSunday = endDate.add(Duration(days: 7 - endDate.weekday));
|
||||||
|
|
||||||
|
// Generate weeks to cover exactly 365 days
|
||||||
|
final weeks = <DateTime>[];
|
||||||
|
var current = startMonday;
|
||||||
|
while (current.isBefore(endSunday) || current.isAtSameMomentAs(endSunday)) {
|
||||||
|
weeks.add(current);
|
||||||
|
current = current.add(const Duration(days: 7));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create data map for all dates in the range
|
||||||
|
final dataMap = <DateTime, double>{};
|
||||||
|
for (final week in weeks) {
|
||||||
|
for (var i = 0; i < 7; i++) {
|
||||||
|
final date = week.add(Duration(days: i));
|
||||||
|
// Only include dates within our 365-day range
|
||||||
|
if (date.isAfter(startDate.subtract(const Duration(days: 1))) &&
|
||||||
|
date.isBefore(endDate.add(const Duration(days: 1)))) {
|
||||||
|
final item = heatmap.items.firstWhere(
|
||||||
|
(e) =>
|
||||||
|
e.date.year == date.year &&
|
||||||
|
e.date.month == date.month &&
|
||||||
|
e.date.day == date.day,
|
||||||
|
orElse: () => SnHeatmapItem(date: date, count: 0),
|
||||||
|
);
|
||||||
|
dataMap[date] = item.count.toDouble();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate month labels for the top
|
||||||
|
final monthLabels = <String>[];
|
||||||
|
final monthPositions = <int>[];
|
||||||
|
final processedMonths =
|
||||||
|
<String>{}; // Track processed months to avoid duplicates
|
||||||
|
|
||||||
|
for (final week in weeks) {
|
||||||
|
final monthKey = '${week.year}-${week.month.toString().padLeft(2, '0')}';
|
||||||
|
|
||||||
|
// Only process each month once
|
||||||
|
if (!processedMonths.contains(monthKey)) {
|
||||||
|
processedMonths.add(monthKey);
|
||||||
|
|
||||||
|
// Find which week this month starts in
|
||||||
|
final firstDayOfMonth = DateTime(week.year, week.month, 1);
|
||||||
|
final monthStartMonday = firstDayOfMonth.subtract(
|
||||||
|
Duration(days: firstDayOfMonth.weekday - 1),
|
||||||
|
);
|
||||||
|
|
||||||
|
final monthStartWeekIndex = weeks.indexWhere(
|
||||||
|
(w) =>
|
||||||
|
w.year == monthStartMonday.year &&
|
||||||
|
w.month == monthStartMonday.month &&
|
||||||
|
w.day == monthStartMonday.day,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (monthStartWeekIndex != -1) {
|
||||||
|
monthLabels.add(_getMonthAbbreviation(week.month));
|
||||||
|
monthPositions.add(monthStartWeekIndex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final heatmapData = HeatmapData(
|
||||||
|
rows: [
|
||||||
|
'Mon',
|
||||||
|
'Tue',
|
||||||
|
'Wed',
|
||||||
|
'Thu',
|
||||||
|
'Fri',
|
||||||
|
'Sat',
|
||||||
|
'Sun',
|
||||||
|
], // Days of week vertically
|
||||||
|
columns:
|
||||||
|
weeks
|
||||||
|
.map(
|
||||||
|
(w) =>
|
||||||
|
'${w.year}-${w.month.toString().padLeft(2, '0')}-${w.day.toString().padLeft(2, '0')}',
|
||||||
|
)
|
||||||
|
.toList(), // Weeks horizontally
|
||||||
|
items: [
|
||||||
|
for (int day = 0; day < 7; day++) // For each day of week (Mon-Sun)
|
||||||
|
for (final week in weeks) // For each week
|
||||||
|
HeatmapItem(
|
||||||
|
value: dataMap[week.add(Duration(days: day))] ?? 0.0,
|
||||||
|
unit: heatmap.unit,
|
||||||
|
xAxisLabel:
|
||||||
|
'${week.year}-${week.month.toString().padLeft(2, '0')}-${week.day.toString().padLeft(2, '0')}',
|
||||||
|
yAxisLabel:
|
||||||
|
day == 0
|
||||||
|
? 'Mon'
|
||||||
|
: day == 1
|
||||||
|
? 'Tue'
|
||||||
|
: day == 2
|
||||||
|
? 'Wed'
|
||||||
|
: day == 3
|
||||||
|
? 'Thu'
|
||||||
|
: day == 4
|
||||||
|
? 'Fri'
|
||||||
|
: day == 5
|
||||||
|
? 'Sat'
|
||||||
|
: 'Sun',
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
return Card(
|
||||||
|
margin: EdgeInsets.zero,
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(16),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
'activityHeatmap',
|
||||||
|
style: Theme.of(context).textTheme.titleMedium,
|
||||||
|
).tr(),
|
||||||
|
const Gap(8),
|
||||||
|
// Month labels row
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
const SizedBox(width: 30), // Space for day labels
|
||||||
|
...monthLabels.asMap().entries.map((entry) {
|
||||||
|
final month = entry.value;
|
||||||
|
|
||||||
|
return Expanded(
|
||||||
|
child: Container(
|
||||||
|
alignment: Alignment.center,
|
||||||
|
child: Text(
|
||||||
|
month,
|
||||||
|
style: Theme.of(context).textTheme.bodySmall,
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const Gap(4),
|
||||||
|
Heatmap(
|
||||||
|
heatmapData: heatmapData,
|
||||||
|
rowsVisible: 7,
|
||||||
|
showXAxisLabels: false,
|
||||||
|
onItemSelectedListener: (item) {
|
||||||
|
selectedItem.value = item;
|
||||||
|
},
|
||||||
|
),
|
||||||
|
const Gap(8),
|
||||||
|
// Legend
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
if (selectedItem.value != null)
|
||||||
|
RichText(
|
||||||
|
text: TextSpan(
|
||||||
|
children: [
|
||||||
|
TextSpan(
|
||||||
|
text: selectedItem.value!.value.toInt().toString(),
|
||||||
|
style: Theme.of(context).textTheme.bodySmall
|
||||||
|
?.copyWith(fontWeight: FontWeight.bold),
|
||||||
|
),
|
||||||
|
TextSpan(
|
||||||
|
text: ' activities on ',
|
||||||
|
style: Theme.of(context).textTheme.bodySmall,
|
||||||
|
),
|
||||||
|
TextSpan(
|
||||||
|
text: _formatDate(
|
||||||
|
selectedItem.value!.xAxisLabel ?? '',
|
||||||
|
),
|
||||||
|
style: Theme.of(context).textTheme.bodySmall,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const Spacer(),
|
||||||
|
Text(
|
||||||
|
'Less',
|
||||||
|
style: Theme.of(context).textTheme.bodySmall?.copyWith(
|
||||||
|
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const Gap(4),
|
||||||
|
// Color indicators (light to dark green)
|
||||||
|
...[
|
||||||
|
Colors.green.withOpacity(0.2),
|
||||||
|
Colors.green.withOpacity(0.4),
|
||||||
|
Colors.green.withOpacity(0.6),
|
||||||
|
Colors.green.withOpacity(0.8),
|
||||||
|
Colors.green,
|
||||||
|
].map(
|
||||||
|
(color) => Container(
|
||||||
|
width: 8,
|
||||||
|
height: 8,
|
||||||
|
margin: const EdgeInsets.symmetric(horizontal: 1),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: color,
|
||||||
|
borderRadius: BorderRadius.circular(2),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const Gap(4),
|
||||||
|
Text(
|
||||||
|
'More',
|
||||||
|
style: Theme.of(context).textTheme.bodySmall?.copyWith(
|
||||||
|
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
String _getMonthAbbreviation(int month) {
|
||||||
|
const monthNames = [
|
||||||
|
'Jan',
|
||||||
|
'Feb',
|
||||||
|
'Mar',
|
||||||
|
'Apr',
|
||||||
|
'May',
|
||||||
|
'Jun',
|
||||||
|
'Jul',
|
||||||
|
'Aug',
|
||||||
|
'Sep',
|
||||||
|
'Oct',
|
||||||
|
'Nov',
|
||||||
|
'Dec',
|
||||||
|
];
|
||||||
|
return monthNames[month - 1];
|
||||||
|
}
|
||||||
|
|
||||||
|
String _formatDate(String dateString) {
|
||||||
|
try {
|
||||||
|
final date = DateTime.parse(dateString);
|
||||||
|
final monthAbbrev = _getMonthAbbreviation(date.month);
|
||||||
|
return '$monthAbbrev ${date.day}, ${date.year}';
|
||||||
|
} catch (e) {
|
||||||
|
return dateString; // Fallback to original string if parsing fails
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Reference in New Issue
Block a user