Subscriptions

This commit is contained in:
2025-05-14 01:38:47 +08:00
parent 70f58259de
commit 661d07716b
8 changed files with 653 additions and 9 deletions

View File

@ -21,6 +21,15 @@ Future<SnAccount> account(Ref ref, String uname) async {
return SnAccount.fromJson(resp.data);
}
@riverpod
Future<List<SnAccountBadge>> accountBadges(Ref ref, String uname) async {
final apiClient = ref.watch(apiClientProvider);
final resp = await apiClient.get("/accounts/$uname/badges");
return List<SnAccountBadge>.from(
resp.data.map((x) => SnAccountBadge.fromJson(x)),
);
}
@RoutePage()
class AccountProfileScreen extends HookConsumerWidget {
final String name;

View File

@ -145,5 +145,127 @@ class _AccountProviderElement
String get uname => (origin as AccountProvider).uname;
}
String _$accountBadgesHash() => r'4bfe5fb0d6ac0d4cde4563460bde289289188f6d';
/// See also [accountBadges].
@ProviderFor(accountBadges)
const accountBadgesProvider = AccountBadgesFamily();
/// See also [accountBadges].
class AccountBadgesFamily extends Family<AsyncValue<List<SnAccountBadge>>> {
/// See also [accountBadges].
const AccountBadgesFamily();
/// See also [accountBadges].
AccountBadgesProvider call(String uname) {
return AccountBadgesProvider(uname);
}
@override
AccountBadgesProvider getProviderOverride(
covariant AccountBadgesProvider 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'accountBadgesProvider';
}
/// See also [accountBadges].
class AccountBadgesProvider
extends AutoDisposeFutureProvider<List<SnAccountBadge>> {
/// See also [accountBadges].
AccountBadgesProvider(String uname)
: this._internal(
(ref) => accountBadges(ref as AccountBadgesRef, uname),
from: accountBadgesProvider,
name: r'accountBadgesProvider',
debugGetCreateSourceHash:
const bool.fromEnvironment('dart.vm.product')
? null
: _$accountBadgesHash,
dependencies: AccountBadgesFamily._dependencies,
allTransitiveDependencies:
AccountBadgesFamily._allTransitiveDependencies,
uname: uname,
);
AccountBadgesProvider._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<List<SnAccountBadge>> Function(AccountBadgesRef provider) create,
) {
return ProviderOverride(
origin: this,
override: AccountBadgesProvider._internal(
(ref) => create(ref as AccountBadgesRef),
from: from,
name: null,
dependencies: null,
allTransitiveDependencies: null,
debugGetCreateSourceHash: null,
uname: uname,
),
);
}
@override
AutoDisposeFutureProviderElement<List<SnAccountBadge>> createElement() {
return _AccountBadgesProviderElement(this);
}
@override
bool operator ==(Object other) {
return other is AccountBadgesProvider && 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 AccountBadgesRef on AutoDisposeFutureProviderRef<List<SnAccountBadge>> {
/// The parameter `uname` of this provider.
String get uname;
}
class _AccountBadgesProviderElement
extends AutoDisposeFutureProviderElement<List<SnAccountBadge>>
with AccountBadgesRef {
_AccountBadgesProviderElement(super.provider);
@override
String get uname => (origin as AccountBadgesProvider).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

@ -1,11 +1,16 @@
import 'package:auto_route/annotations.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:gap/gap.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:island/models/post.dart';
import 'package:island/models/user.dart';
import 'package:island/pods/network.dart';
import 'package:island/widgets/account/badge.dart';
import 'package:island/widgets/account/status.dart';
import 'package:island/widgets/alert.dart';
import 'package:island/widgets/app_scaffold.dart';
import 'package:island/widgets/content/cloud_files.dart';
import 'package:island/widgets/post/post_list.dart';
@ -21,6 +26,27 @@ Future<SnPublisher> publisher(Ref ref, String uname) async {
return SnPublisher.fromJson(resp.data);
}
@riverpod
Future<List<SnAccountBadge>> publisherBadges(Ref ref, String pubName) async {
final pub = await ref.watch(publisherProvider(pubName).future);
if (pub.publisherType != 0) return [];
final apiClient = ref.watch(apiClientProvider);
final resp = await apiClient.get("/accounts/${pub.name}/badges");
return List<SnAccountBadge>.from(
resp.data.map((x) => SnAccountBadge.fromJson(x)),
);
}
@riverpod
Future<SnSubscriptionStatus> publisherSubscriptionStatus(
Ref ref,
String pubName,
) async {
final apiClient = ref.watch(apiClientProvider);
final resp = await apiClient.get("/publishers/$pubName/subscription");
return SnSubscriptionStatus.fromJson(resp.data);
}
@RoutePage()
class PublisherProfileScreen extends HookConsumerWidget {
final String name;
@ -32,6 +58,38 @@ class PublisherProfileScreen extends HookConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final publisher = ref.watch(publisherProvider(name));
final badges = ref.watch(publisherBadgesProvider(name));
final subStatus = ref.watch(publisherSubscriptionStatusProvider(name));
final subscribing = useState(false);
Future<void> subscribe() async {
final apiClient = ref.watch(apiClientProvider);
subscribing.value = true;
try {
await apiClient.post("/publishers/$name/subscribe", data: {'tier': 0});
ref.invalidate(publisherSubscriptionStatusProvider(name));
HapticFeedback.heavyImpact();
} catch (err) {
showErrorAlert(err);
} finally {
subscribing.value = false;
}
}
Future<void> unsubscribe() async {
final apiClient = ref.watch(apiClientProvider);
subscribing.value = true;
try {
await apiClient.post("/publishers/$name/unsubscribe");
ref.invalidate(publisherSubscriptionStatusProvider(name));
HapticFeedback.heavyImpact();
} catch (err) {
showErrorAlert(err);
} finally {
subscribing.value = false;
}
}
final iconShadow = Shadow(
color: Colors.black54,
@ -64,6 +122,41 @@ class PublisherProfileScreen extends HookConsumerWidget {
),
),
),
actions: [
subStatus.when(
data:
(status) => IconButton(
onPressed:
subscribing.value
? null
: (status.isSubscribed
? unsubscribe
: subscribe),
icon: Icon(
status.isSubscribed
? Icons.remove_circle
: Icons.add_circle,
shadows: [iconShadow],
),
),
error: (_, __) => const SizedBox(),
loading:
() => const SizedBox(
width: 48,
height: 48,
child: Center(
child: SizedBox(
width: 20,
height: 20,
child: CircularProgressIndicator(
strokeWidth: 2,
),
),
),
),
),
const Gap(8),
],
),
SliverToBoxAdapter(
child: Row(
@ -95,14 +188,14 @@ class PublisherProfileScreen extends HookConsumerWidget {
],
).padding(horizontal: 24, top: 24, bottom: 24),
),
// if (data.badges.isNotEmpty)
// SliverToBoxAdapter(
// child: BadgeList(
// badges: data.badges,
// ).padding(horizontal: 24, bottom: 24),
// )
// else
// const Gap(16),
if (badges.value?.isNotEmpty ?? false)
SliverToBoxAdapter(
child: BadgeList(
badges: badges.value!,
).padding(horizontal: 24, bottom: 24),
)
else
const SliverGap(16),
SliverToBoxAdapter(child: const Divider(height: 1)),
if (data.bio.isNotEmpty)
SliverToBoxAdapter(

View File

@ -145,5 +145,259 @@ class _PublisherProviderElement
String get uname => (origin as PublisherProvider).uname;
}
String _$publisherBadgesHash() => r'69a5bbc9e1528da65ae8b1e5e6c4f57c3dcf4071';
/// See also [publisherBadges].
@ProviderFor(publisherBadges)
const publisherBadgesProvider = PublisherBadgesFamily();
/// See also [publisherBadges].
class PublisherBadgesFamily extends Family<AsyncValue<List<SnAccountBadge>>> {
/// See also [publisherBadges].
const PublisherBadgesFamily();
/// See also [publisherBadges].
PublisherBadgesProvider call(String pubName) {
return PublisherBadgesProvider(pubName);
}
@override
PublisherBadgesProvider getProviderOverride(
covariant PublisherBadgesProvider provider,
) {
return call(provider.pubName);
}
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'publisherBadgesProvider';
}
/// See also [publisherBadges].
class PublisherBadgesProvider
extends AutoDisposeFutureProvider<List<SnAccountBadge>> {
/// See also [publisherBadges].
PublisherBadgesProvider(String pubName)
: this._internal(
(ref) => publisherBadges(ref as PublisherBadgesRef, pubName),
from: publisherBadgesProvider,
name: r'publisherBadgesProvider',
debugGetCreateSourceHash:
const bool.fromEnvironment('dart.vm.product')
? null
: _$publisherBadgesHash,
dependencies: PublisherBadgesFamily._dependencies,
allTransitiveDependencies:
PublisherBadgesFamily._allTransitiveDependencies,
pubName: pubName,
);
PublisherBadgesProvider._internal(
super._createNotifier, {
required super.name,
required super.dependencies,
required super.allTransitiveDependencies,
required super.debugGetCreateSourceHash,
required super.from,
required this.pubName,
}) : super.internal();
final String pubName;
@override
Override overrideWith(
FutureOr<List<SnAccountBadge>> Function(PublisherBadgesRef provider) create,
) {
return ProviderOverride(
origin: this,
override: PublisherBadgesProvider._internal(
(ref) => create(ref as PublisherBadgesRef),
from: from,
name: null,
dependencies: null,
allTransitiveDependencies: null,
debugGetCreateSourceHash: null,
pubName: pubName,
),
);
}
@override
AutoDisposeFutureProviderElement<List<SnAccountBadge>> createElement() {
return _PublisherBadgesProviderElement(this);
}
@override
bool operator ==(Object other) {
return other is PublisherBadgesProvider && other.pubName == pubName;
}
@override
int get hashCode {
var hash = _SystemHash.combine(0, runtimeType.hashCode);
hash = _SystemHash.combine(hash, pubName.hashCode);
return _SystemHash.finish(hash);
}
}
@Deprecated('Will be removed in 3.0. Use Ref instead')
// ignore: unused_element
mixin PublisherBadgesRef on AutoDisposeFutureProviderRef<List<SnAccountBadge>> {
/// The parameter `pubName` of this provider.
String get pubName;
}
class _PublisherBadgesProviderElement
extends AutoDisposeFutureProviderElement<List<SnAccountBadge>>
with PublisherBadgesRef {
_PublisherBadgesProviderElement(super.provider);
@override
String get pubName => (origin as PublisherBadgesProvider).pubName;
}
String _$publisherSubscriptionStatusHash() =>
r'4eb6741c40775c814e71b6a98b8f1e2d84bf7e30';
/// See also [publisherSubscriptionStatus].
@ProviderFor(publisherSubscriptionStatus)
const publisherSubscriptionStatusProvider = PublisherSubscriptionStatusFamily();
/// See also [publisherSubscriptionStatus].
class PublisherSubscriptionStatusFamily
extends Family<AsyncValue<SnSubscriptionStatus>> {
/// See also [publisherSubscriptionStatus].
const PublisherSubscriptionStatusFamily();
/// See also [publisherSubscriptionStatus].
PublisherSubscriptionStatusProvider call(String pubName) {
return PublisherSubscriptionStatusProvider(pubName);
}
@override
PublisherSubscriptionStatusProvider getProviderOverride(
covariant PublisherSubscriptionStatusProvider provider,
) {
return call(provider.pubName);
}
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'publisherSubscriptionStatusProvider';
}
/// See also [publisherSubscriptionStatus].
class PublisherSubscriptionStatusProvider
extends AutoDisposeFutureProvider<SnSubscriptionStatus> {
/// See also [publisherSubscriptionStatus].
PublisherSubscriptionStatusProvider(String pubName)
: this._internal(
(ref) => publisherSubscriptionStatus(
ref as PublisherSubscriptionStatusRef,
pubName,
),
from: publisherSubscriptionStatusProvider,
name: r'publisherSubscriptionStatusProvider',
debugGetCreateSourceHash:
const bool.fromEnvironment('dart.vm.product')
? null
: _$publisherSubscriptionStatusHash,
dependencies: PublisherSubscriptionStatusFamily._dependencies,
allTransitiveDependencies:
PublisherSubscriptionStatusFamily._allTransitiveDependencies,
pubName: pubName,
);
PublisherSubscriptionStatusProvider._internal(
super._createNotifier, {
required super.name,
required super.dependencies,
required super.allTransitiveDependencies,
required super.debugGetCreateSourceHash,
required super.from,
required this.pubName,
}) : super.internal();
final String pubName;
@override
Override overrideWith(
FutureOr<SnSubscriptionStatus> Function(
PublisherSubscriptionStatusRef provider,
)
create,
) {
return ProviderOverride(
origin: this,
override: PublisherSubscriptionStatusProvider._internal(
(ref) => create(ref as PublisherSubscriptionStatusRef),
from: from,
name: null,
dependencies: null,
allTransitiveDependencies: null,
debugGetCreateSourceHash: null,
pubName: pubName,
),
);
}
@override
AutoDisposeFutureProviderElement<SnSubscriptionStatus> createElement() {
return _PublisherSubscriptionStatusProviderElement(this);
}
@override
bool operator ==(Object other) {
return other is PublisherSubscriptionStatusProvider &&
other.pubName == pubName;
}
@override
int get hashCode {
var hash = _SystemHash.combine(0, runtimeType.hashCode);
hash = _SystemHash.combine(hash, pubName.hashCode);
return _SystemHash.finish(hash);
}
}
@Deprecated('Will be removed in 3.0. Use Ref instead')
// ignore: unused_element
mixin PublisherSubscriptionStatusRef
on AutoDisposeFutureProviderRef<SnSubscriptionStatus> {
/// The parameter `pubName` of this provider.
String get pubName;
}
class _PublisherSubscriptionStatusProviderElement
extends AutoDisposeFutureProviderElement<SnSubscriptionStatus>
with PublisherSubscriptionStatusRef {
_PublisherSubscriptionStatusProviderElement(super.provider);
@override
String get pubName => (origin as PublisherSubscriptionStatusProvider).pubName;
}
// 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