From 83e92e2eed0f4d4c91a9979ce76077aad909b8cb Mon Sep 17 00:00:00 2001 From: LittleSheep Date: Wed, 15 Oct 2025 00:27:32 +0800 Subject: [PATCH] :sparkles: Show heatmap on pub profile --- lib/screens/posts/pub_profile.dart | 43 ++++++++++ lib/screens/posts/pub_profile.g.dart | 121 +++++++++++++++++++++++++++ lib/widgets/activity_heatmap.dart | 9 +- lib/widgets/post/post_list.dart | 2 + lib/widgets/post/post_list.g.dart | 29 ++++++- 5 files changed, 198 insertions(+), 6 deletions(-) diff --git a/lib/screens/posts/pub_profile.dart b/lib/screens/posts/pub_profile.dart index a005287d..b0cab2a1 100644 --- a/lib/screens/posts/pub_profile.dart +++ b/lib/screens/posts/pub_profile.dart @@ -8,6 +8,7 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:island/models/post.dart'; import 'package:island/models/publisher.dart'; import 'package:island/models/account.dart'; +import 'package:island/models/heatmap.dart'; import 'package:island/pods/config.dart'; import 'package:island/pods/network.dart'; import 'package:island/services/color.dart'; @@ -20,6 +21,7 @@ import 'package:island/widgets/app_scaffold.dart'; import 'package:island/widgets/content/cloud_files.dart'; import 'package:island/widgets/content/markdown.dart'; import 'package:island/widgets/post/post_list.dart'; +import 'package:island/widgets/activity_heatmap.dart'; import 'package:material_symbols_icons/symbols.dart'; import 'package:island/services/color_extraction.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; @@ -222,6 +224,32 @@ class _PublisherBioWidget extends StatelessWidget { } } +class _PublisherHeatmapWidget extends StatelessWidget { + final AsyncValue heatmap; + final bool forceDense; + + const _PublisherHeatmapWidget({ + required this.heatmap, + this.forceDense = false, + }); + + @override + Widget build(BuildContext context) { + return heatmap.when( + data: + (data) => + data != null + ? ActivityHeatmapWidget( + heatmap: data, + forceDense: forceDense, + ).padding(horizontal: 8) + : const SizedBox.shrink(), + loading: () => const SizedBox.shrink(), + error: (_, _) => const SizedBox.shrink(), + ); + } +} + class _PublisherCategoryTabWidget extends StatelessWidget { final TabController categoryTabController; @@ -292,6 +320,13 @@ Future publisherAppbarForcegroundColor(Ref ref, String pubName) async { } } +@riverpod +Future publisherHeatmap(Ref ref, String uname) async { + final apiClient = ref.watch(apiClientProvider); + final resp = await apiClient.get('/sphere/publishers/$uname/heatmap'); + return SnHeatmap.fromJson(resp.data); +} + class PublisherProfileScreen extends HookConsumerWidget { final String name; const PublisherProfileScreen({super.key, required this.name}); @@ -301,6 +336,7 @@ class PublisherProfileScreen extends HookConsumerWidget { final publisher = ref.watch(publisherProvider(name)); final badges = ref.watch(publisherBadgesProvider(name)); final subStatus = ref.watch(publisherSubscriptionStatusProvider(name)); + final heatmap = ref.watch(publisherHeatmapProvider(name)); final appbarColor = ref.watch( publisherAppbarForcegroundColorProvider(name), ); @@ -446,6 +482,10 @@ class PublisherProfileScreen extends HookConsumerWidget { ), _PublisherVerificationWidget(data: data), _PublisherBioWidget(data: data), + _PublisherHeatmapWidget( + heatmap: heatmap, + forceDense: true, + ), ], ), ), @@ -517,6 +557,9 @@ class PublisherProfileScreen extends HookConsumerWidget { SliverToBoxAdapter( child: _PublisherBioWidget(data: data), ), + SliverToBoxAdapter( + child: _PublisherHeatmapWidget(heatmap: heatmap), + ), SliverPostList(pubName: name, pinned: true), SliverToBoxAdapter( child: _PublisherCategoryTabWidget( diff --git a/lib/screens/posts/pub_profile.g.dart b/lib/screens/posts/pub_profile.g.dart index 3f3ed561..f340e956 100644 --- a/lib/screens/posts/pub_profile.g.dart +++ b/lib/screens/posts/pub_profile.g.dart @@ -530,5 +530,126 @@ class _PublisherAppbarForcegroundColorProviderElement (origin as PublisherAppbarForcegroundColorProvider).pubName; } +String _$publisherHeatmapHash() => r'86db275ce3861a2855b5ec35fbfef85fc47b23a6'; + +/// 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; +} + // ignore_for_file: type=lint // ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package diff --git a/lib/widgets/activity_heatmap.dart b/lib/widgets/activity_heatmap.dart index c7e03277..a0410c75 100644 --- a/lib/widgets/activity_heatmap.dart +++ b/lib/widgets/activity_heatmap.dart @@ -11,8 +11,13 @@ import '../services/responsive.dart'; /// Shows exactly 365 days (wide screen) or 90 days (non-wide screen) of data ending at the current date. class ActivityHeatmapWidget extends HookConsumerWidget { final SnHeatmap heatmap; + final bool forceDense; - const ActivityHeatmapWidget({super.key, required this.heatmap}); + const ActivityHeatmapWidget({ + super.key, + required this.heatmap, + this.forceDense = false, + }); @override Widget build(BuildContext context, WidgetRef ref) { @@ -21,7 +26,7 @@ class ActivityHeatmapWidget extends HookConsumerWidget { final now = DateTime.now(); final isWide = isWideScreen(context); - final days = isWide ? 365 : 90; + final days = (isWide && !forceDense) ? 365 : 90; // Start from exactly the selected days ago final startDate = now.subtract(Duration(days: days)); diff --git a/lib/widgets/post/post_list.dart b/lib/widgets/post/post_list.dart index 0252318c..d85674f5 100644 --- a/lib/widgets/post/post_list.dart +++ b/lib/widgets/post/post_list.dart @@ -23,6 +23,7 @@ class PostListNotifier extends _$PostListNotifier List? tags, bool? pinned, bool shuffle = false, + bool? includeReplies, }) { return fetch(cursor: null); } @@ -42,6 +43,7 @@ class PostListNotifier extends _$PostListNotifier if (categories != null) 'categories': categories, if (shuffle) 'shuffle': true, if (pinned != null) 'pinned': pinned, + if (includeReplies != null) 'includeReplies': includeReplies, }; final response = await client.get( diff --git a/lib/widgets/post/post_list.g.dart b/lib/widgets/post/post_list.g.dart index 8175bf5d..262da72d 100644 --- a/lib/widgets/post/post_list.g.dart +++ b/lib/widgets/post/post_list.g.dart @@ -6,7 +6,7 @@ part of 'post_list.dart'; // RiverpodGenerator // ************************************************************************** -String _$postListNotifierHash() => r'3c0a8154ded4bcd8f5456f7a4ea2e542f57efa85'; +String _$postListNotifierHash() => r'fc139ad4df0deb67bcbb949560319f2f7fbfb503'; /// Copied from Dart SDK class _SystemHash { @@ -38,6 +38,7 @@ abstract class _$PostListNotifier late final List? tags; late final bool? pinned; late final bool shuffle; + late final bool? includeReplies; FutureOr> build({ String? pubName, @@ -47,6 +48,7 @@ abstract class _$PostListNotifier List? tags, bool? pinned, bool shuffle = false, + bool? includeReplies, }); } @@ -69,6 +71,7 @@ class PostListNotifierFamily List? tags, bool? pinned, bool shuffle = false, + bool? includeReplies, }) { return PostListNotifierProvider( pubName: pubName, @@ -78,6 +81,7 @@ class PostListNotifierFamily tags: tags, pinned: pinned, shuffle: shuffle, + includeReplies: includeReplies, ); } @@ -93,6 +97,7 @@ class PostListNotifierFamily tags: provider.tags, pinned: provider.pinned, shuffle: provider.shuffle, + includeReplies: provider.includeReplies, ); } @@ -127,6 +132,7 @@ class PostListNotifierProvider List? tags, bool? pinned, bool shuffle = false, + bool? includeReplies, }) : this._internal( () => PostListNotifier() @@ -136,7 +142,8 @@ class PostListNotifierProvider ..categories = categories ..tags = tags ..pinned = pinned - ..shuffle = shuffle, + ..shuffle = shuffle + ..includeReplies = includeReplies, from: postListNotifierProvider, name: r'postListNotifierProvider', debugGetCreateSourceHash: @@ -153,6 +160,7 @@ class PostListNotifierProvider tags: tags, pinned: pinned, shuffle: shuffle, + includeReplies: includeReplies, ); PostListNotifierProvider._internal( @@ -169,6 +177,7 @@ class PostListNotifierProvider required this.tags, required this.pinned, required this.shuffle, + required this.includeReplies, }) : super.internal(); final String? pubName; @@ -178,6 +187,7 @@ class PostListNotifierProvider final List? tags; final bool? pinned; final bool shuffle; + final bool? includeReplies; @override FutureOr> runNotifierBuild( @@ -191,6 +201,7 @@ class PostListNotifierProvider tags: tags, pinned: pinned, shuffle: shuffle, + includeReplies: includeReplies, ); } @@ -207,7 +218,8 @@ class PostListNotifierProvider ..categories = categories ..tags = tags ..pinned = pinned - ..shuffle = shuffle, + ..shuffle = shuffle + ..includeReplies = includeReplies, from: from, name: null, dependencies: null, @@ -220,6 +232,7 @@ class PostListNotifierProvider tags: tags, pinned: pinned, shuffle: shuffle, + includeReplies: includeReplies, ), ); } @@ -242,7 +255,8 @@ class PostListNotifierProvider other.categories == categories && other.tags == tags && other.pinned == pinned && - other.shuffle == shuffle; + other.shuffle == shuffle && + other.includeReplies == includeReplies; } @override @@ -255,6 +269,7 @@ class PostListNotifierProvider hash = _SystemHash.combine(hash, tags.hashCode); hash = _SystemHash.combine(hash, pinned.hashCode); hash = _SystemHash.combine(hash, shuffle.hashCode); + hash = _SystemHash.combine(hash, includeReplies.hashCode); return _SystemHash.finish(hash); } @@ -284,6 +299,9 @@ mixin PostListNotifierRef /// The parameter `shuffle` of this provider. bool get shuffle; + + /// The parameter `includeReplies` of this provider. + bool? get includeReplies; } class _PostListNotifierProviderElement @@ -310,6 +328,9 @@ class _PostListNotifierProviderElement bool? get pinned => (origin as PostListNotifierProvider).pinned; @override bool get shuffle => (origin as PostListNotifierProvider).shuffle; + @override + bool? get includeReplies => + (origin as PostListNotifierProvider).includeReplies; } // ignore_for_file: type=lint