Show heatmap on pub profile

This commit is contained in:
2025-10-15 00:27:32 +08:00
parent a83b929d42
commit 83e92e2eed
5 changed files with 198 additions and 6 deletions

View File

@@ -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<SnHeatmap?> 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<Color?> publisherAppbarForcegroundColor(Ref ref, String pubName) async {
}
}
@riverpod
Future<SnHeatmap?> 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(

View File

@@ -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<AsyncValue<SnHeatmap?>> {
/// 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<SnHeatmap?> {
/// 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<SnHeatmap?> 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<SnHeatmap?> 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<SnHeatmap?> {
/// The parameter `uname` of this provider.
String get uname;
}
class _PublisherHeatmapProviderElement
extends AutoDisposeFutureProviderElement<SnHeatmap?>
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

View File

@@ -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));

View File

@@ -23,6 +23,7 @@ class PostListNotifier extends _$PostListNotifier
List<String>? 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(

View File

@@ -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<String>? tags;
late final bool? pinned;
late final bool shuffle;
late final bool? includeReplies;
FutureOr<CursorPagingData<SnPost>> build({
String? pubName,
@@ -47,6 +48,7 @@ abstract class _$PostListNotifier
List<String>? tags,
bool? pinned,
bool shuffle = false,
bool? includeReplies,
});
}
@@ -69,6 +71,7 @@ class PostListNotifierFamily
List<String>? 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<String>? 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<String>? tags;
final bool? pinned;
final bool shuffle;
final bool? includeReplies;
@override
FutureOr<CursorPagingData<SnPost>> 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