Heatmap

This commit is contained in:
2025-10-12 18:47:01 +08:00
parent 5327f04ec0
commit 6124dbfd79
6 changed files with 854 additions and 9 deletions

View File

@@ -117,10 +117,7 @@ return $default(_that);case _:
final _that = this; final _that = this;
switch (_that) { switch (_that) {
case _AutocompleteSuggestion(): case _AutocompleteSuggestion():
return $default(_that);case _: return $default(_that);}
throw StateError('Unexpected subclass');
}
} }
/// A variant of `map` that fallback to returning `null`. /// A variant of `map` that fallback to returning `null`.
/// ///
@@ -179,10 +176,7 @@ return $default(_that.type,_that.keyword,_that.data);case _:
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String type, String keyword, dynamic data) $default,) {final _that = this; @optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String type, String keyword, dynamic data) $default,) {final _that = this;
switch (_that) { switch (_that) {
case _AutocompleteSuggestion(): case _AutocompleteSuggestion():
return $default(_that.type,_that.keyword,_that.data);case _: return $default(_that.type,_that.keyword,_that.data);}
throw StateError('Unexpected subclass');
}
} }
/// A variant of `when` that fallback to returning `null` /// A variant of `when` that fallback to returning `null`
/// ///

28
lib/models/heatmap.dart Normal file
View File

@@ -0,0 +1,28 @@
import 'package:freezed_annotation/freezed_annotation.dart';
part 'heatmap.freezed.dart';
part 'heatmap.g.dart';
@freezed
sealed class SnPublisherHeatmap with _$SnPublisherHeatmap {
const factory SnPublisherHeatmap({
required String unit,
@JsonKey(name: 'period_start') required DateTime periodStart,
@JsonKey(name: 'period_end') required DateTime periodEnd,
required List<SnPublisherHeatmapItem> items,
}) = _SnPublisherHeatmap;
factory SnPublisherHeatmap.fromJson(Map<String, dynamic> json) =>
_$SnPublisherHeatmapFromJson(json);
}
@freezed
sealed class SnPublisherHeatmapItem with _$SnPublisherHeatmapItem {
const factory SnPublisherHeatmapItem({
required DateTime date,
required int count,
}) = _SnPublisherHeatmapItem;
factory SnPublisherHeatmapItem.fromJson(Map<String, dynamic> json) =>
_$SnPublisherHeatmapItemFromJson(json);
}

View File

@@ -0,0 +1,546 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
// coverage:ignore-file
// ignore_for_file: type=lint
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
part of 'heatmap.dart';
// **************************************************************************
// FreezedGenerator
// **************************************************************************
// dart format off
T _$identity<T>(T value) => value;
/// @nodoc
mixin _$SnPublisherHeatmap {
String get unit;@JsonKey(name: 'period_start') DateTime get periodStart;@JsonKey(name: 'period_end') DateTime get periodEnd; List<SnPublisherHeatmapItem> get items;
/// Create a copy of SnPublisherHeatmap
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
$SnPublisherHeatmapCopyWith<SnPublisherHeatmap> get copyWith => _$SnPublisherHeatmapCopyWithImpl<SnPublisherHeatmap>(this as SnPublisherHeatmap, _$identity);
/// Serializes this SnPublisherHeatmap to a JSON map.
Map<String, dynamic> toJson();
@override
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));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType,unit,periodStart,periodEnd,const DeepCollectionEquality().hash(items));
@override
String toString() {
return 'SnPublisherHeatmap(unit: $unit, periodStart: $periodStart, periodEnd: $periodEnd, items: $items)';
}
}
/// @nodoc
abstract mixin class $SnPublisherHeatmapCopyWith<$Res> {
factory $SnPublisherHeatmapCopyWith(SnPublisherHeatmap value, $Res Function(SnPublisherHeatmap) _then) = _$SnPublisherHeatmapCopyWithImpl;
@useResult
$Res call({
String unit,@JsonKey(name: 'period_start') DateTime periodStart,@JsonKey(name: 'period_end') DateTime periodEnd, List<SnPublisherHeatmapItem> items
});
}
/// @nodoc
class _$SnPublisherHeatmapCopyWithImpl<$Res>
implements $SnPublisherHeatmapCopyWith<$Res> {
_$SnPublisherHeatmapCopyWithImpl(this._self, this._then);
final SnPublisherHeatmap _self;
final $Res Function(SnPublisherHeatmap) _then;
/// Create a copy of SnPublisherHeatmap
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline') @override $Res call({Object? unit = null,Object? periodStart = null,Object? periodEnd = null,Object? items = null,}) {
return _then(_self.copyWith(
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 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 List<SnPublisherHeatmapItem>,
));
}
}
/// Adds pattern-matching-related methods to [SnPublisherHeatmap].
extension SnPublisherHeatmapPatterns on SnPublisherHeatmap {
/// A variant of `map` that fallback to returning `orElse`.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case _:
/// return orElse();
/// }
/// ```
@optionalTypeArgs TResult maybeMap<TResult extends Object?>(TResult Function( _SnPublisherHeatmap value)? $default,{required TResult orElse(),}){
final _that = this;
switch (_that) {
case _SnPublisherHeatmap() when $default != null:
return $default(_that);case _:
return orElse();
}
}
/// A `switch`-like method, using callbacks.
///
/// Callbacks receives the raw object, upcasted.
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case final Subclass2 value:
/// return ...;
/// }
/// ```
@optionalTypeArgs TResult map<TResult extends Object?>(TResult Function( _SnPublisherHeatmap value) $default,){
final _that = this;
switch (_that) {
case _SnPublisherHeatmap():
return $default(_that);}
}
/// A variant of `map` that fallback to returning `null`.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case _:
/// return null;
/// }
/// ```
@optionalTypeArgs TResult? mapOrNull<TResult extends Object?>(TResult? Function( _SnPublisherHeatmap value)? $default,){
final _that = this;
switch (_that) {
case _SnPublisherHeatmap() when $default != null:
return $default(_that);case _:
return null;
}
}
/// A variant of `when` that fallback to an `orElse` callback.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case _:
/// return orElse();
/// }
/// ```
@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;
switch (_that) {
case _SnPublisherHeatmap() when $default != null:
return $default(_that.unit,_that.periodStart,_that.periodEnd,_that.items);case _:
return orElse();
}
}
/// A `switch`-like method, using callbacks.
///
/// As opposed to `map`, this offers destructuring.
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case Subclass2(:final field2):
/// return ...;
/// }
/// ```
@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;
switch (_that) {
case _SnPublisherHeatmap():
return $default(_that.unit,_that.periodStart,_that.periodEnd,_that.items);}
}
/// A variant of `when` that fallback to returning `null`
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case _:
/// return null;
/// }
/// ```
@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;
switch (_that) {
case _SnPublisherHeatmap() when $default != null:
return $default(_that.unit,_that.periodStart,_that.periodEnd,_that.items);case _:
return null;
}
}
}
/// @nodoc
@JsonSerializable()
class _SnPublisherHeatmap implements SnPublisherHeatmap {
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;
factory _SnPublisherHeatmap.fromJson(Map<String, dynamic> json) => _$SnPublisherHeatmapFromJson(json);
@override final String unit;
@override@JsonKey(name: 'period_start') final DateTime periodStart;
@override@JsonKey(name: 'period_end') final DateTime periodEnd;
final List<SnPublisherHeatmapItem> _items;
@override List<SnPublisherHeatmapItem> get items {
if (_items is EqualUnmodifiableListView) return _items;
// ignore: implicit_dynamic_type
return EqualUnmodifiableListView(_items);
}
/// Create a copy of SnPublisherHeatmap
/// with the given fields replaced by the non-null parameter values.
@override @JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
_$SnPublisherHeatmapCopyWith<_SnPublisherHeatmap> get copyWith => __$SnPublisherHeatmapCopyWithImpl<_SnPublisherHeatmap>(this, _$identity);
@override
Map<String, dynamic> toJson() {
return _$SnPublisherHeatmapToJson(this, );
}
@override
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));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType,unit,periodStart,periodEnd,const DeepCollectionEquality().hash(_items));
@override
String toString() {
return 'SnPublisherHeatmap(unit: $unit, periodStart: $periodStart, periodEnd: $periodEnd, items: $items)';
}
}
/// @nodoc
abstract mixin class _$SnPublisherHeatmapCopyWith<$Res> implements $SnPublisherHeatmapCopyWith<$Res> {
factory _$SnPublisherHeatmapCopyWith(_SnPublisherHeatmap value, $Res Function(_SnPublisherHeatmap) _then) = __$SnPublisherHeatmapCopyWithImpl;
@override @useResult
$Res call({
String unit,@JsonKey(name: 'period_start') DateTime periodStart,@JsonKey(name: 'period_end') DateTime periodEnd, List<SnPublisherHeatmapItem> items
});
}
/// @nodoc
class __$SnPublisherHeatmapCopyWithImpl<$Res>
implements _$SnPublisherHeatmapCopyWith<$Res> {
__$SnPublisherHeatmapCopyWithImpl(this._self, this._then);
final _SnPublisherHeatmap _self;
final $Res Function(_SnPublisherHeatmap) _then;
/// Create a copy of SnPublisherHeatmap
/// with the given fields replaced by the non-null parameter values.
@override @pragma('vm:prefer-inline') $Res call({Object? unit = null,Object? periodStart = null,Object? periodEnd = null,Object? items = null,}) {
return _then(_SnPublisherHeatmap(
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 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 List<SnPublisherHeatmapItem>,
));
}
}
/// @nodoc
mixin _$SnPublisherHeatmapItem {
DateTime get date; int get count;
/// Create a copy of SnPublisherHeatmapItem
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
$SnPublisherHeatmapItemCopyWith<SnPublisherHeatmapItem> get copyWith => _$SnPublisherHeatmapItemCopyWithImpl<SnPublisherHeatmapItem>(this as SnPublisherHeatmapItem, _$identity);
/// Serializes this SnPublisherHeatmapItem to a JSON map.
Map<String, dynamic> toJson();
@override
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));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType,date,count);
@override
String toString() {
return 'SnPublisherHeatmapItem(date: $date, count: $count)';
}
}
/// @nodoc
abstract mixin class $SnPublisherHeatmapItemCopyWith<$Res> {
factory $SnPublisherHeatmapItemCopyWith(SnPublisherHeatmapItem value, $Res Function(SnPublisherHeatmapItem) _then) = _$SnPublisherHeatmapItemCopyWithImpl;
@useResult
$Res call({
DateTime date, int count
});
}
/// @nodoc
class _$SnPublisherHeatmapItemCopyWithImpl<$Res>
implements $SnPublisherHeatmapItemCopyWith<$Res> {
_$SnPublisherHeatmapItemCopyWithImpl(this._self, this._then);
final SnPublisherHeatmapItem _self;
final $Res Function(SnPublisherHeatmapItem) _then;
/// Create a copy of SnPublisherHeatmapItem
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline') @override $Res call({Object? date = null,Object? count = null,}) {
return _then(_self.copyWith(
date: null == date ? _self.date : date // ignore: cast_nullable_to_non_nullable
as DateTime,count: null == count ? _self.count : count // ignore: cast_nullable_to_non_nullable
as int,
));
}
}
/// Adds pattern-matching-related methods to [SnPublisherHeatmapItem].
extension SnPublisherHeatmapItemPatterns on SnPublisherHeatmapItem {
/// A variant of `map` that fallback to returning `orElse`.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case _:
/// return orElse();
/// }
/// ```
@optionalTypeArgs TResult maybeMap<TResult extends Object?>(TResult Function( _SnPublisherHeatmapItem value)? $default,{required TResult orElse(),}){
final _that = this;
switch (_that) {
case _SnPublisherHeatmapItem() when $default != null:
return $default(_that);case _:
return orElse();
}
}
/// A `switch`-like method, using callbacks.
///
/// Callbacks receives the raw object, upcasted.
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case final Subclass2 value:
/// return ...;
/// }
/// ```
@optionalTypeArgs TResult map<TResult extends Object?>(TResult Function( _SnPublisherHeatmapItem value) $default,){
final _that = this;
switch (_that) {
case _SnPublisherHeatmapItem():
return $default(_that);}
}
/// A variant of `map` that fallback to returning `null`.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case _:
/// return null;
/// }
/// ```
@optionalTypeArgs TResult? mapOrNull<TResult extends Object?>(TResult? Function( _SnPublisherHeatmapItem value)? $default,){
final _that = this;
switch (_that) {
case _SnPublisherHeatmapItem() when $default != null:
return $default(_that);case _:
return null;
}
}
/// A variant of `when` that fallback to an `orElse` callback.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case _:
/// return orElse();
/// }
/// ```
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( DateTime date, int count)? $default,{required TResult orElse(),}) {final _that = this;
switch (_that) {
case _SnPublisherHeatmapItem() when $default != null:
return $default(_that.date,_that.count);case _:
return orElse();
}
}
/// A `switch`-like method, using callbacks.
///
/// As opposed to `map`, this offers destructuring.
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case Subclass2(:final field2):
/// return ...;
/// }
/// ```
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( DateTime date, int count) $default,) {final _that = this;
switch (_that) {
case _SnPublisherHeatmapItem():
return $default(_that.date,_that.count);}
}
/// A variant of `when` that fallback to returning `null`
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case _:
/// return null;
/// }
/// ```
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( DateTime date, int count)? $default,) {final _that = this;
switch (_that) {
case _SnPublisherHeatmapItem() when $default != null:
return $default(_that.date,_that.count);case _:
return null;
}
}
}
/// @nodoc
@JsonSerializable()
class _SnPublisherHeatmapItem implements SnPublisherHeatmapItem {
const _SnPublisherHeatmapItem({required this.date, required this.count});
factory _SnPublisherHeatmapItem.fromJson(Map<String, dynamic> json) => _$SnPublisherHeatmapItemFromJson(json);
@override final DateTime date;
@override final int count;
/// Create a copy of SnPublisherHeatmapItem
/// with the given fields replaced by the non-null parameter values.
@override @JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
_$SnPublisherHeatmapItemCopyWith<_SnPublisherHeatmapItem> get copyWith => __$SnPublisherHeatmapItemCopyWithImpl<_SnPublisherHeatmapItem>(this, _$identity);
@override
Map<String, dynamic> toJson() {
return _$SnPublisherHeatmapItemToJson(this, );
}
@override
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));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType,date,count);
@override
String toString() {
return 'SnPublisherHeatmapItem(date: $date, count: $count)';
}
}
/// @nodoc
abstract mixin class _$SnPublisherHeatmapItemCopyWith<$Res> implements $SnPublisherHeatmapItemCopyWith<$Res> {
factory _$SnPublisherHeatmapItemCopyWith(_SnPublisherHeatmapItem value, $Res Function(_SnPublisherHeatmapItem) _then) = __$SnPublisherHeatmapItemCopyWithImpl;
@override @useResult
$Res call({
DateTime date, int count
});
}
/// @nodoc
class __$SnPublisherHeatmapItemCopyWithImpl<$Res>
implements _$SnPublisherHeatmapItemCopyWith<$Res> {
__$SnPublisherHeatmapItemCopyWithImpl(this._self, this._then);
final _SnPublisherHeatmapItem _self;
final $Res Function(_SnPublisherHeatmapItem) _then;
/// Create a copy of SnPublisherHeatmapItem
/// with the given fields replaced by the non-null parameter values.
@override @pragma('vm:prefer-inline') $Res call({Object? date = null,Object? count = null,}) {
return _then(_SnPublisherHeatmapItem(
date: null == date ? _self.date : date // ignore: cast_nullable_to_non_nullable
as DateTime,count: null == count ? _self.count : count // ignore: cast_nullable_to_non_nullable
as int,
));
}
}
// dart format on

43
lib/models/heatmap.g.dart Normal file
View File

@@ -0,0 +1,43 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'heatmap.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
_SnPublisherHeatmap _$SnPublisherHeatmapFromJson(Map<String, dynamic> json) =>
_SnPublisherHeatmap(
unit: json['unit'] as String,
periodStart: DateTime.parse(json['period_start'] as String),
periodEnd: DateTime.parse(json['period_end'] as String),
items:
(json['items'] as List<dynamic>)
.map(
(e) =>
SnPublisherHeatmapItem.fromJson(e as Map<String, dynamic>),
)
.toList(),
);
Map<String, dynamic> _$SnPublisherHeatmapToJson(_SnPublisherHeatmap instance) =>
<String, dynamic>{
'unit': instance.unit,
'period_start': instance.periodStart.toIso8601String(),
'period_end': instance.periodEnd.toIso8601String(),
'items': instance.items.map((e) => e.toJson()).toList(),
};
_SnPublisherHeatmapItem _$SnPublisherHeatmapItemFromJson(
Map<String, dynamic> json,
) => _SnPublisherHeatmapItem(
date: DateTime.parse(json['date'] as String),
count: (json['count'] as num).toInt(),
);
Map<String, dynamic> _$SnPublisherHeatmapItemToJson(
_SnPublisherHeatmapItem instance,
) => <String, dynamic>{
'date': instance.date.toIso8601String(),
'count': instance.count,
};

View File

@@ -1,6 +1,7 @@
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';
@@ -8,6 +9,7 @@ import 'package:gap/gap.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:island/models/post.dart'; import 'package:island/models/post.dart';
import 'package:island/models/publisher.dart'; import 'package:island/models/publisher.dart';
import 'package:island/models/heatmap.dart';
import 'package:island/pods/network.dart'; import 'package:island/pods/network.dart';
import 'package:island/screens/creators/publishers_form.dart'; import 'package:island/screens/creators/publishers_form.dart';
import 'package:island/services/responsive.dart'; import 'package:island/services/responsive.dart';
@@ -33,6 +35,14 @@ Future<SnPublisherStats?> publisherStats(Ref ref, String? uname) async {
return SnPublisherStats.fromJson(resp.data); return SnPublisherStats.fromJson(resp.data);
} }
@riverpod
Future<SnPublisherHeatmap?> publisherHeatmap(Ref ref, String? uname) async {
if (uname == null) return null;
final apiClient = ref.watch(apiClientProvider);
final resp = await apiClient.get('/sphere/publishers/$uname/heatmap');
return SnPublisherHeatmap.fromJson(resp.data);
}
@riverpod @riverpod
Future<SnPublisherMember?> publisherIdentity(Ref ref, String uname) async { Future<SnPublisherMember?> publisherIdentity(Ref ref, String uname) async {
try { try {
@@ -329,6 +339,10 @@ class CreatorHubScreen extends HookConsumerWidget {
publisherStatsProvider(currentPublisher.value?.name), publisherStatsProvider(currentPublisher.value?.name),
); );
final publisherHeatmap = ref.watch(
publisherHeatmapProvider(currentPublisher.value?.name),
);
final publisherFeatures = ref.watch( final publisherFeatures = ref.watch(
publisherFeaturesProvider(currentPublisher.value?.name), publisherFeaturesProvider(currentPublisher.value?.name),
); );
@@ -557,6 +571,7 @@ class CreatorHubScreen extends HookConsumerWidget {
if (stats != null) if (stats != null)
_PublisherStatsWidget( _PublisherStatsWidget(
stats: stats, stats: stats,
heatmap: publisherHeatmap.value,
).padding(horizontal: 12), ).padding(horizontal: 12),
buildNavigationWidget(true), buildNavigationWidget(true),
], ],
@@ -567,6 +582,7 @@ class CreatorHubScreen extends HookConsumerWidget {
if (stats != null) if (stats != null)
_PublisherStatsWidget( _PublisherStatsWidget(
stats: stats, stats: stats,
heatmap: publisherHeatmap.value,
).padding(horizontal: 16), ).padding(horizontal: 16),
buildNavigationWidget(false), buildNavigationWidget(false),
], ],
@@ -585,7 +601,8 @@ class CreatorHubScreen extends HookConsumerWidget {
class _PublisherStatsWidget extends StatelessWidget { class _PublisherStatsWidget extends StatelessWidget {
final SnPublisherStats stats; final SnPublisherStats stats;
const _PublisherStatsWidget({required this.stats}); final SnPublisherHeatmap? heatmap;
const _PublisherStatsWidget({required this.stats, this.heatmap});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@@ -638,6 +655,7 @@ class _PublisherStatsWidget extends StatelessWidget {
), ),
], ],
), ),
if (heatmap != null) _PublisherHeatmapWidget(heatmap: heatmap!),
], ],
), ),
); );
@@ -1179,3 +1197,97 @@ class _PublisherInviteSheet extends HookConsumerWidget {
); );
} }
} }
class _PublisherHeatmapWidget extends StatelessWidget {
final SnPublisherHeatmap heatmap;
const _PublisherHeatmapWidget({required this.heatmap});
@override
Widget build(BuildContext context) {
// Find min and max dates
final dates = heatmap.items.map((e) => e.date).toList();
if (dates.isEmpty) return const SizedBox.shrink();
final minDate = dates.reduce((a, b) => a.isBefore(b) ? a : b);
final maxDate = dates.reduce((a, b) => a.isAfter(b) ? a : b);
// Find monday of the week containing minDate
final startMonday = minDate.subtract(Duration(days: minDate.weekday - 1));
// Find sunday of the week containing maxDate
final endSunday = maxDate.add(Duration(days: 7 - maxDate.weekday));
// Generate all weeks
final weeks = <DateTime>[];
var current = startMonday;
while (current.isBefore(endSunday) || current.isAtSameMomentAs(endSunday)) {
weeks.add(current);
current = current.add(const Duration(days: 7));
}
// Columns: Mon to Sun
const columns = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'];
// Create data map
final dataMap = <String, Map<String, double>>{};
for (final week in weeks) {
final weekKey =
'${week.year}-${week.month.toString().padLeft(2, '0')}-${week.day.toString().padLeft(2, '0')}';
dataMap[weekKey] = {};
for (var i = 0; i < 7; i++) {
final date = week.add(Duration(days: i));
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[weekKey]![columns[i]] = item.count.toDouble();
}
}
final heatmapData = HeatmapData(
rows:
weeks
.map(
(w) =>
'${w.year}-${w.month.toString().padLeft(2, '0')}-${w.day.toString().padLeft(2, '0')}',
)
.toList(),
columns: columns,
items: [
for (final row in dataMap.entries)
for (final col in row.value.entries)
HeatmapItem(
value: col.value,
unit: heatmap.unit,
xAxisLabel: col.key,
yAxisLabel: row.key,
),
],
);
return Card(
margin: EdgeInsets.zero,
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Activity Heatmap',
style: Theme.of(context).textTheme.titleMedium,
),
const Gap(8),
Heatmap(
showXAxisLabels: false,
showYAxisLabels: false,
heatmapData: heatmapData,
rowsVisible: 5,
),
],
),
),
);
}
}

View File

@@ -149,6 +149,128 @@ class _PublisherStatsProviderElement
String? get uname => (origin as PublisherStatsProvider).uname; String? get uname => (origin as PublisherStatsProvider).uname;
} }
String _$publisherHeatmapHash() => r'780dfb05b8610a37cfcd937fd04cf5bbe9b298c9';
/// See also [publisherHeatmap].
@ProviderFor(publisherHeatmap)
const publisherHeatmapProvider = PublisherHeatmapFamily();
/// See also [publisherHeatmap].
class PublisherHeatmapFamily extends Family<AsyncValue<SnPublisherHeatmap?>> {
/// See also [publisherHeatmap].
const PublisherHeatmapFamily();
/// See also [publisherHeatmap].
PublisherHeatmapProvider call(String? uname) {
return PublisherHeatmapProvider(uname);
}
@override
PublisherHeatmapProvider getProviderOverride(
covariant PublisherHeatmapProvider provider,
) {
return call(provider.uname);
}
static const Iterable<ProviderOrFamily>? _dependencies = null;
@override
Iterable<ProviderOrFamily>? get dependencies => _dependencies;
static const Iterable<ProviderOrFamily>? _allTransitiveDependencies = null;
@override
Iterable<ProviderOrFamily>? get allTransitiveDependencies =>
_allTransitiveDependencies;
@override
String? get name => r'publisherHeatmapProvider';
}
/// See also [publisherHeatmap].
class PublisherHeatmapProvider
extends AutoDisposeFutureProvider<SnPublisherHeatmap?> {
/// See also [publisherHeatmap].
PublisherHeatmapProvider(String? uname)
: this._internal(
(ref) => publisherHeatmap(ref as PublisherHeatmapRef, uname),
from: publisherHeatmapProvider,
name: r'publisherHeatmapProvider',
debugGetCreateSourceHash:
const bool.fromEnvironment('dart.vm.product')
? null
: _$publisherHeatmapHash,
dependencies: PublisherHeatmapFamily._dependencies,
allTransitiveDependencies:
PublisherHeatmapFamily._allTransitiveDependencies,
uname: uname,
);
PublisherHeatmapProvider._internal(
super._createNotifier, {
required super.name,
required super.dependencies,
required super.allTransitiveDependencies,
required super.debugGetCreateSourceHash,
required super.from,
required this.uname,
}) : super.internal();
final String? uname;
@override
Override overrideWith(
FutureOr<SnPublisherHeatmap?> Function(PublisherHeatmapRef provider) create,
) {
return ProviderOverride(
origin: this,
override: PublisherHeatmapProvider._internal(
(ref) => create(ref as PublisherHeatmapRef),
from: from,
name: null,
dependencies: null,
allTransitiveDependencies: null,
debugGetCreateSourceHash: null,
uname: uname,
),
);
}
@override
AutoDisposeFutureProviderElement<SnPublisherHeatmap?> createElement() {
return _PublisherHeatmapProviderElement(this);
}
@override
bool operator ==(Object other) {
return other is PublisherHeatmapProvider && other.uname == uname;
}
@override
int get hashCode {
var hash = _SystemHash.combine(0, runtimeType.hashCode);
hash = _SystemHash.combine(hash, uname.hashCode);
return _SystemHash.finish(hash);
}
}
@Deprecated('Will be removed in 3.0. Use Ref instead')
// ignore: unused_element
mixin PublisherHeatmapRef on AutoDisposeFutureProviderRef<SnPublisherHeatmap?> {
/// The parameter `uname` of this provider.
String? get uname;
}
class _PublisherHeatmapProviderElement
extends AutoDisposeFutureProviderElement<SnPublisherHeatmap?>
with PublisherHeatmapRef {
_PublisherHeatmapProviderElement(super.provider);
@override
String? get uname => (origin as PublisherHeatmapProvider).uname;
}
String _$publisherIdentityHash() => r'299372f25fa4b2bf8e11a8ba2d645100fc38e76f'; String _$publisherIdentityHash() => r'299372f25fa4b2bf8e11a8ba2d645100fc38e76f';
/// See also [publisherIdentity]. /// See also [publisherIdentity].