diff --git a/lib/models/autocomplete_response.freezed.dart b/lib/models/autocomplete_response.freezed.dart index fd0d69e4..55beedbc 100644 --- a/lib/models/autocomplete_response.freezed.dart +++ b/lib/models/autocomplete_response.freezed.dart @@ -117,10 +117,7 @@ return $default(_that);case _: final _that = this; switch (_that) { case _AutocompleteSuggestion(): -return $default(_that);case _: - throw StateError('Unexpected subclass'); - -} +return $default(_that);} } /// 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 Function( String type, String keyword, dynamic data) $default,) {final _that = this; switch (_that) { case _AutocompleteSuggestion(): -return $default(_that.type,_that.keyword,_that.data);case _: - throw StateError('Unexpected subclass'); - -} +return $default(_that.type,_that.keyword,_that.data);} } /// A variant of `when` that fallback to returning `null` /// diff --git a/lib/models/heatmap.dart b/lib/models/heatmap.dart new file mode 100644 index 00000000..cf10f58d --- /dev/null +++ b/lib/models/heatmap.dart @@ -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 items, + }) = _SnPublisherHeatmap; + + factory SnPublisherHeatmap.fromJson(Map json) => + _$SnPublisherHeatmapFromJson(json); +} + +@freezed +sealed class SnPublisherHeatmapItem with _$SnPublisherHeatmapItem { + const factory SnPublisherHeatmapItem({ + required DateTime date, + required int count, + }) = _SnPublisherHeatmapItem; + + factory SnPublisherHeatmapItem.fromJson(Map json) => + _$SnPublisherHeatmapItemFromJson(json); +} diff --git a/lib/models/heatmap.freezed.dart b/lib/models/heatmap.freezed.dart new file mode 100644 index 00000000..748ba88e --- /dev/null +++ b/lib/models/heatmap.freezed.dart @@ -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 value) => value; + +/// @nodoc +mixin _$SnPublisherHeatmap { + + String get unit;@JsonKey(name: 'period_start') DateTime get periodStart;@JsonKey(name: 'period_end') DateTime get periodEnd; List 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 get copyWith => _$SnPublisherHeatmapCopyWithImpl(this as SnPublisherHeatmap, _$identity); + + /// Serializes this SnPublisherHeatmap to a JSON map. + Map 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 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, + )); +} + +} + + +/// 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 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 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? 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 Function( String unit, @JsonKey(name: 'period_start') DateTime periodStart, @JsonKey(name: 'period_end') DateTime periodEnd, List 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 Function( String unit, @JsonKey(name: 'period_start') DateTime periodStart, @JsonKey(name: 'period_end') DateTime periodEnd, List 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? Function( String unit, @JsonKey(name: 'period_start') DateTime periodStart, @JsonKey(name: 'period_end') DateTime periodEnd, List 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 items}): _items = items; + factory _SnPublisherHeatmap.fromJson(Map 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 _items; +@override List 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 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 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, + )); +} + + +} + + +/// @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 get copyWith => _$SnPublisherHeatmapItemCopyWithImpl(this as SnPublisherHeatmapItem, _$identity); + + /// Serializes this SnPublisherHeatmapItem to a JSON map. + Map 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 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 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? 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 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 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? 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 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 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 diff --git a/lib/models/heatmap.g.dart b/lib/models/heatmap.g.dart new file mode 100644 index 00000000..ab8e3fd6 --- /dev/null +++ b/lib/models/heatmap.g.dart @@ -0,0 +1,43 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'heatmap.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +_SnPublisherHeatmap _$SnPublisherHeatmapFromJson(Map 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) + .map( + (e) => + SnPublisherHeatmapItem.fromJson(e as Map), + ) + .toList(), + ); + +Map _$SnPublisherHeatmapToJson(_SnPublisherHeatmap instance) => + { + 'unit': instance.unit, + 'period_start': instance.periodStart.toIso8601String(), + 'period_end': instance.periodEnd.toIso8601String(), + 'items': instance.items.map((e) => e.toJson()).toList(), + }; + +_SnPublisherHeatmapItem _$SnPublisherHeatmapItemFromJson( + Map json, +) => _SnPublisherHeatmapItem( + date: DateTime.parse(json['date'] as String), + count: (json['count'] as num).toInt(), +); + +Map _$SnPublisherHeatmapItemToJson( + _SnPublisherHeatmapItem instance, +) => { + 'date': instance.date.toIso8601String(), + 'count': instance.count, +}; diff --git a/lib/screens/creators/hub.dart b/lib/screens/creators/hub.dart index 700034c5..552f40c9 100644 --- a/lib/screens/creators/hub.dart +++ b/lib/screens/creators/hub.dart @@ -1,6 +1,7 @@ import 'package:dio/dio.dart'; import 'package:dropdown_button2/dropdown_button2.dart'; import 'package:easy_localization/easy_localization.dart'; +import 'package:fl_heatmap/fl_heatmap.dart'; import 'package:flutter/material.dart'; import 'package:go_router/go_router.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:island/models/post.dart'; import 'package:island/models/publisher.dart'; +import 'package:island/models/heatmap.dart'; import 'package:island/pods/network.dart'; import 'package:island/screens/creators/publishers_form.dart'; import 'package:island/services/responsive.dart'; @@ -33,6 +35,14 @@ Future publisherStats(Ref ref, String? uname) async { return SnPublisherStats.fromJson(resp.data); } +@riverpod +Future 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 Future publisherIdentity(Ref ref, String uname) async { try { @@ -329,6 +339,10 @@ class CreatorHubScreen extends HookConsumerWidget { publisherStatsProvider(currentPublisher.value?.name), ); + final publisherHeatmap = ref.watch( + publisherHeatmapProvider(currentPublisher.value?.name), + ); + final publisherFeatures = ref.watch( publisherFeaturesProvider(currentPublisher.value?.name), ); @@ -557,6 +571,7 @@ class CreatorHubScreen extends HookConsumerWidget { if (stats != null) _PublisherStatsWidget( stats: stats, + heatmap: publisherHeatmap.value, ).padding(horizontal: 12), buildNavigationWidget(true), ], @@ -567,6 +582,7 @@ class CreatorHubScreen extends HookConsumerWidget { if (stats != null) _PublisherStatsWidget( stats: stats, + heatmap: publisherHeatmap.value, ).padding(horizontal: 16), buildNavigationWidget(false), ], @@ -585,7 +601,8 @@ class CreatorHubScreen extends HookConsumerWidget { class _PublisherStatsWidget extends StatelessWidget { final SnPublisherStats stats; - const _PublisherStatsWidget({required this.stats}); + final SnPublisherHeatmap? heatmap; + const _PublisherStatsWidget({required this.stats, this.heatmap}); @override 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 = []; + 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 = >{}; + 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, + ), + ], + ), + ), + ); + } +} diff --git a/lib/screens/creators/hub.g.dart b/lib/screens/creators/hub.g.dart index 42dd6fe1..a2291f72 100644 --- a/lib/screens/creators/hub.g.dart +++ b/lib/screens/creators/hub.g.dart @@ -149,6 +149,128 @@ class _PublisherStatsProviderElement 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> { + /// 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? _dependencies = null; + + @override + Iterable? get dependencies => _dependencies; + + static const Iterable? _allTransitiveDependencies = null; + + @override + Iterable? get allTransitiveDependencies => + _allTransitiveDependencies; + + @override + String? get name => r'publisherHeatmapProvider'; +} + +/// See also [publisherHeatmap]. +class PublisherHeatmapProvider + extends AutoDisposeFutureProvider { + /// 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 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 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 { + /// The parameter `uname` of this provider. + String? get uname; +} + +class _PublisherHeatmapProviderElement + extends AutoDisposeFutureProviderElement + with PublisherHeatmapRef { + _PublisherHeatmapProviderElement(super.provider); + + @override + String? get uname => (origin as PublisherHeatmapProvider).uname; +} + String _$publisherIdentityHash() => r'299372f25fa4b2bf8e11a8ba2d645100fc38e76f'; /// See also [publisherIdentity].