From 4c0ad5ef32b556a30c24d7bd3cec34416f98cc09 Mon Sep 17 00:00:00 2001 From: LittleSheep Date: Sun, 22 Jun 2025 21:03:02 +0800 Subject: [PATCH] :sparkles: Stellar program status showing --- lib/models/user.dart | 2 +- lib/models/user.freezed.dart | 24 ++-- lib/models/user.g.dart | 2 +- lib/models/wallet.dart | 16 +++ lib/models/wallet.freezed.dart | 151 ++++++++++++++++++++++++++ lib/models/wallet.g.dart | 27 +++++ lib/screens/account/leveling.dart | 77 ++++++++++++- lib/screens/account/leveling.g.dart | 31 ++++++ lib/screens/account/profile.dart | 5 +- lib/widgets/account/account_name.dart | 71 ++++++++++++ 10 files changed, 385 insertions(+), 21 deletions(-) create mode 100644 lib/screens/account/leveling.g.dart diff --git a/lib/models/user.dart b/lib/models/user.dart index 4fee4be..452ce97 100644 --- a/lib/models/user.dart +++ b/lib/models/user.dart @@ -45,7 +45,7 @@ sealed class SnAccountProfile with _$SnAccountProfile { required SnCloudFile? picture, required SnCloudFile? background, required SnVerificationMark? verification, - required SnWalletSubscription? stellarMembership, + required SnWalletSubscriptionRef? stellarMembership, required DateTime createdAt, required DateTime updatedAt, required DateTime? deletedAt, diff --git a/lib/models/user.freezed.dart b/lib/models/user.freezed.dart index 11f2502..9696eb7 100644 --- a/lib/models/user.freezed.dart +++ b/lib/models/user.freezed.dart @@ -200,7 +200,7 @@ $SnAccountProfileCopyWith<$Res> get profile { /// @nodoc mixin _$SnAccountProfile { - String get id; String get firstName; String get middleName; String get lastName; String get bio; String get gender; String get pronouns; String get location; String get timeZone; DateTime? get birthday; DateTime? get lastSeenAt; SnAccountBadge? get activeBadge; int get experience; int get level; double get levelingProgress; SnCloudFile? get picture; SnCloudFile? get background; SnVerificationMark? get verification; SnWalletSubscription? get stellarMembership; DateTime get createdAt; DateTime get updatedAt; DateTime? get deletedAt; + String get id; String get firstName; String get middleName; String get lastName; String get bio; String get gender; String get pronouns; String get location; String get timeZone; DateTime? get birthday; DateTime? get lastSeenAt; SnAccountBadge? get activeBadge; int get experience; int get level; double get levelingProgress; SnCloudFile? get picture; SnCloudFile? get background; SnVerificationMark? get verification; SnWalletSubscriptionRef? get stellarMembership; DateTime get createdAt; DateTime get updatedAt; DateTime? get deletedAt; /// Create a copy of SnAccountProfile /// with the given fields replaced by the non-null parameter values. @JsonKey(includeFromJson: false, includeToJson: false) @@ -233,11 +233,11 @@ abstract mixin class $SnAccountProfileCopyWith<$Res> { factory $SnAccountProfileCopyWith(SnAccountProfile value, $Res Function(SnAccountProfile) _then) = _$SnAccountProfileCopyWithImpl; @useResult $Res call({ - String id, String firstName, String middleName, String lastName, String bio, String gender, String pronouns, String location, String timeZone, DateTime? birthday, DateTime? lastSeenAt, SnAccountBadge? activeBadge, int experience, int level, double levelingProgress, SnCloudFile? picture, SnCloudFile? background, SnVerificationMark? verification, SnWalletSubscription? stellarMembership, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt + String id, String firstName, String middleName, String lastName, String bio, String gender, String pronouns, String location, String timeZone, DateTime? birthday, DateTime? lastSeenAt, SnAccountBadge? activeBadge, int experience, int level, double levelingProgress, SnCloudFile? picture, SnCloudFile? background, SnVerificationMark? verification, SnWalletSubscriptionRef? stellarMembership, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt }); -$SnAccountBadgeCopyWith<$Res>? get activeBadge;$SnCloudFileCopyWith<$Res>? get picture;$SnCloudFileCopyWith<$Res>? get background;$SnVerificationMarkCopyWith<$Res>? get verification;$SnWalletSubscriptionCopyWith<$Res>? get stellarMembership; +$SnAccountBadgeCopyWith<$Res>? get activeBadge;$SnCloudFileCopyWith<$Res>? get picture;$SnCloudFileCopyWith<$Res>? get background;$SnVerificationMarkCopyWith<$Res>? get verification;$SnWalletSubscriptionRefCopyWith<$Res>? get stellarMembership; } /// @nodoc @@ -271,7 +271,7 @@ as double,picture: freezed == picture ? _self.picture : picture // ignore: cast_ as SnCloudFile?,background: freezed == background ? _self.background : background // ignore: cast_nullable_to_non_nullable as SnCloudFile?,verification: freezed == verification ? _self.verification : verification // ignore: cast_nullable_to_non_nullable as SnVerificationMark?,stellarMembership: freezed == stellarMembership ? _self.stellarMembership : stellarMembership // ignore: cast_nullable_to_non_nullable -as SnWalletSubscription?,createdAt: null == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable +as SnWalletSubscriptionRef?,createdAt: null == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable as DateTime,updatedAt: null == updatedAt ? _self.updatedAt : updatedAt // ignore: cast_nullable_to_non_nullable as DateTime,deletedAt: freezed == deletedAt ? _self.deletedAt : deletedAt // ignore: cast_nullable_to_non_nullable as DateTime?, @@ -329,12 +329,12 @@ $SnVerificationMarkCopyWith<$Res>? get verification { /// with the given fields replaced by the non-null parameter values. @override @pragma('vm:prefer-inline') -$SnWalletSubscriptionCopyWith<$Res>? get stellarMembership { +$SnWalletSubscriptionRefCopyWith<$Res>? get stellarMembership { if (_self.stellarMembership == null) { return null; } - return $SnWalletSubscriptionCopyWith<$Res>(_self.stellarMembership!, (value) { + return $SnWalletSubscriptionRefCopyWith<$Res>(_self.stellarMembership!, (value) { return _then(_self.copyWith(stellarMembership: value)); }); } @@ -366,7 +366,7 @@ class _SnAccountProfile implements SnAccountProfile { @override final SnCloudFile? picture; @override final SnCloudFile? background; @override final SnVerificationMark? verification; -@override final SnWalletSubscription? stellarMembership; +@override final SnWalletSubscriptionRef? stellarMembership; @override final DateTime createdAt; @override final DateTime updatedAt; @override final DateTime? deletedAt; @@ -404,11 +404,11 @@ abstract mixin class _$SnAccountProfileCopyWith<$Res> implements $SnAccountProfi factory _$SnAccountProfileCopyWith(_SnAccountProfile value, $Res Function(_SnAccountProfile) _then) = __$SnAccountProfileCopyWithImpl; @override @useResult $Res call({ - String id, String firstName, String middleName, String lastName, String bio, String gender, String pronouns, String location, String timeZone, DateTime? birthday, DateTime? lastSeenAt, SnAccountBadge? activeBadge, int experience, int level, double levelingProgress, SnCloudFile? picture, SnCloudFile? background, SnVerificationMark? verification, SnWalletSubscription? stellarMembership, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt + String id, String firstName, String middleName, String lastName, String bio, String gender, String pronouns, String location, String timeZone, DateTime? birthday, DateTime? lastSeenAt, SnAccountBadge? activeBadge, int experience, int level, double levelingProgress, SnCloudFile? picture, SnCloudFile? background, SnVerificationMark? verification, SnWalletSubscriptionRef? stellarMembership, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt }); -@override $SnAccountBadgeCopyWith<$Res>? get activeBadge;@override $SnCloudFileCopyWith<$Res>? get picture;@override $SnCloudFileCopyWith<$Res>? get background;@override $SnVerificationMarkCopyWith<$Res>? get verification;@override $SnWalletSubscriptionCopyWith<$Res>? get stellarMembership; +@override $SnAccountBadgeCopyWith<$Res>? get activeBadge;@override $SnCloudFileCopyWith<$Res>? get picture;@override $SnCloudFileCopyWith<$Res>? get background;@override $SnVerificationMarkCopyWith<$Res>? get verification;@override $SnWalletSubscriptionRefCopyWith<$Res>? get stellarMembership; } /// @nodoc @@ -442,7 +442,7 @@ as double,picture: freezed == picture ? _self.picture : picture // ignore: cast_ as SnCloudFile?,background: freezed == background ? _self.background : background // ignore: cast_nullable_to_non_nullable as SnCloudFile?,verification: freezed == verification ? _self.verification : verification // ignore: cast_nullable_to_non_nullable as SnVerificationMark?,stellarMembership: freezed == stellarMembership ? _self.stellarMembership : stellarMembership // ignore: cast_nullable_to_non_nullable -as SnWalletSubscription?,createdAt: null == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable +as SnWalletSubscriptionRef?,createdAt: null == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable as DateTime,updatedAt: null == updatedAt ? _self.updatedAt : updatedAt // ignore: cast_nullable_to_non_nullable as DateTime,deletedAt: freezed == deletedAt ? _self.deletedAt : deletedAt // ignore: cast_nullable_to_non_nullable as DateTime?, @@ -501,12 +501,12 @@ $SnVerificationMarkCopyWith<$Res>? get verification { /// with the given fields replaced by the non-null parameter values. @override @pragma('vm:prefer-inline') -$SnWalletSubscriptionCopyWith<$Res>? get stellarMembership { +$SnWalletSubscriptionRefCopyWith<$Res>? get stellarMembership { if (_self.stellarMembership == null) { return null; } - return $SnWalletSubscriptionCopyWith<$Res>(_self.stellarMembership!, (value) { + return $SnWalletSubscriptionRefCopyWith<$Res>(_self.stellarMembership!, (value) { return _then(_self.copyWith(stellarMembership: value)); }); } diff --git a/lib/models/user.g.dart b/lib/models/user.g.dart index 5fd7f65..21ba4c0 100644 --- a/lib/models/user.g.dart +++ b/lib/models/user.g.dart @@ -87,7 +87,7 @@ _SnAccountProfile _$SnAccountProfileFromJson(Map json) => stellarMembership: json['stellar_membership'] == null ? null - : SnWalletSubscription.fromJson( + : SnWalletSubscriptionRef.fromJson( json['stellar_membership'] as Map, ), createdAt: DateTime.parse(json['created_at'] as String), diff --git a/lib/models/wallet.dart b/lib/models/wallet.dart index 9083640..39c3646 100644 --- a/lib/models/wallet.dart +++ b/lib/models/wallet.dart @@ -86,6 +86,22 @@ sealed class SnWalletSubscription with _$SnWalletSubscription { _$SnWalletSubscriptionFromJson(json); } +@freezed +sealed class SnWalletSubscriptionRef with _$SnWalletSubscriptionRef { + const factory SnWalletSubscriptionRef({ + required String id, + required bool isActive, + required String accountId, + required DateTime createdAt, + required DateTime? deletedAt, + required DateTime updatedAt, + required String identifier, + }) = _SnWalletSubscriptionRef; + + factory SnWalletSubscriptionRef.fromJson(Map json) => + _$SnWalletSubscriptionRefFromJson(json); +} + @freezed sealed class SnWalletOrder with _$SnWalletOrder { const factory SnWalletOrder({ diff --git a/lib/models/wallet.freezed.dart b/lib/models/wallet.freezed.dart index 6f17011..470e5fb 100644 --- a/lib/models/wallet.freezed.dart +++ b/lib/models/wallet.freezed.dart @@ -781,6 +781,157 @@ $SnAccountCopyWith<$Res>? get account { } +/// @nodoc +mixin _$SnWalletSubscriptionRef { + + String get id; bool get isActive; String get accountId; DateTime get createdAt; DateTime? get deletedAt; DateTime get updatedAt; String get identifier; +/// Create a copy of SnWalletSubscriptionRef +/// with the given fields replaced by the non-null parameter values. +@JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +$SnWalletSubscriptionRefCopyWith get copyWith => _$SnWalletSubscriptionRefCopyWithImpl(this as SnWalletSubscriptionRef, _$identity); + + /// Serializes this SnWalletSubscriptionRef to a JSON map. + Map toJson(); + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is SnWalletSubscriptionRef&&(identical(other.id, id) || other.id == id)&&(identical(other.isActive, isActive) || other.isActive == isActive)&&(identical(other.accountId, accountId) || other.accountId == accountId)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.identifier, identifier) || other.identifier == identifier)); +} + +@JsonKey(includeFromJson: false, includeToJson: false) +@override +int get hashCode => Object.hash(runtimeType,id,isActive,accountId,createdAt,deletedAt,updatedAt,identifier); + +@override +String toString() { + return 'SnWalletSubscriptionRef(id: $id, isActive: $isActive, accountId: $accountId, createdAt: $createdAt, deletedAt: $deletedAt, updatedAt: $updatedAt, identifier: $identifier)'; +} + + +} + +/// @nodoc +abstract mixin class $SnWalletSubscriptionRefCopyWith<$Res> { + factory $SnWalletSubscriptionRefCopyWith(SnWalletSubscriptionRef value, $Res Function(SnWalletSubscriptionRef) _then) = _$SnWalletSubscriptionRefCopyWithImpl; +@useResult +$Res call({ + String id, bool isActive, String accountId, DateTime createdAt, DateTime? deletedAt, DateTime updatedAt, String identifier +}); + + + + +} +/// @nodoc +class _$SnWalletSubscriptionRefCopyWithImpl<$Res> + implements $SnWalletSubscriptionRefCopyWith<$Res> { + _$SnWalletSubscriptionRefCopyWithImpl(this._self, this._then); + + final SnWalletSubscriptionRef _self; + final $Res Function(SnWalletSubscriptionRef) _then; + +/// Create a copy of SnWalletSubscriptionRef +/// with the given fields replaced by the non-null parameter values. +@pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? isActive = null,Object? accountId = null,Object? createdAt = null,Object? deletedAt = freezed,Object? updatedAt = null,Object? identifier = null,}) { + return _then(_self.copyWith( +id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable +as String,isActive: null == isActive ? _self.isActive : isActive // ignore: cast_nullable_to_non_nullable +as bool,accountId: null == accountId ? _self.accountId : accountId // ignore: cast_nullable_to_non_nullable +as String,createdAt: null == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable +as DateTime,deletedAt: freezed == deletedAt ? _self.deletedAt : deletedAt // ignore: cast_nullable_to_non_nullable +as DateTime?,updatedAt: null == updatedAt ? _self.updatedAt : updatedAt // ignore: cast_nullable_to_non_nullable +as DateTime,identifier: null == identifier ? _self.identifier : identifier // ignore: cast_nullable_to_non_nullable +as String, + )); +} + +} + + +/// @nodoc +@JsonSerializable() + +class _SnWalletSubscriptionRef implements SnWalletSubscriptionRef { + const _SnWalletSubscriptionRef({required this.id, required this.isActive, required this.accountId, required this.createdAt, required this.deletedAt, required this.updatedAt, required this.identifier}); + factory _SnWalletSubscriptionRef.fromJson(Map json) => _$SnWalletSubscriptionRefFromJson(json); + +@override final String id; +@override final bool isActive; +@override final String accountId; +@override final DateTime createdAt; +@override final DateTime? deletedAt; +@override final DateTime updatedAt; +@override final String identifier; + +/// Create a copy of SnWalletSubscriptionRef +/// with the given fields replaced by the non-null parameter values. +@override @JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +_$SnWalletSubscriptionRefCopyWith<_SnWalletSubscriptionRef> get copyWith => __$SnWalletSubscriptionRefCopyWithImpl<_SnWalletSubscriptionRef>(this, _$identity); + +@override +Map toJson() { + return _$SnWalletSubscriptionRefToJson(this, ); +} + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is _SnWalletSubscriptionRef&&(identical(other.id, id) || other.id == id)&&(identical(other.isActive, isActive) || other.isActive == isActive)&&(identical(other.accountId, accountId) || other.accountId == accountId)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.identifier, identifier) || other.identifier == identifier)); +} + +@JsonKey(includeFromJson: false, includeToJson: false) +@override +int get hashCode => Object.hash(runtimeType,id,isActive,accountId,createdAt,deletedAt,updatedAt,identifier); + +@override +String toString() { + return 'SnWalletSubscriptionRef(id: $id, isActive: $isActive, accountId: $accountId, createdAt: $createdAt, deletedAt: $deletedAt, updatedAt: $updatedAt, identifier: $identifier)'; +} + + +} + +/// @nodoc +abstract mixin class _$SnWalletSubscriptionRefCopyWith<$Res> implements $SnWalletSubscriptionRefCopyWith<$Res> { + factory _$SnWalletSubscriptionRefCopyWith(_SnWalletSubscriptionRef value, $Res Function(_SnWalletSubscriptionRef) _then) = __$SnWalletSubscriptionRefCopyWithImpl; +@override @useResult +$Res call({ + String id, bool isActive, String accountId, DateTime createdAt, DateTime? deletedAt, DateTime updatedAt, String identifier +}); + + + + +} +/// @nodoc +class __$SnWalletSubscriptionRefCopyWithImpl<$Res> + implements _$SnWalletSubscriptionRefCopyWith<$Res> { + __$SnWalletSubscriptionRefCopyWithImpl(this._self, this._then); + + final _SnWalletSubscriptionRef _self; + final $Res Function(_SnWalletSubscriptionRef) _then; + +/// Create a copy of SnWalletSubscriptionRef +/// with the given fields replaced by the non-null parameter values. +@override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? isActive = null,Object? accountId = null,Object? createdAt = null,Object? deletedAt = freezed,Object? updatedAt = null,Object? identifier = null,}) { + return _then(_SnWalletSubscriptionRef( +id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable +as String,isActive: null == isActive ? _self.isActive : isActive // ignore: cast_nullable_to_non_nullable +as bool,accountId: null == accountId ? _self.accountId : accountId // ignore: cast_nullable_to_non_nullable +as String,createdAt: null == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable +as DateTime,deletedAt: freezed == deletedAt ? _self.deletedAt : deletedAt // ignore: cast_nullable_to_non_nullable +as DateTime?,updatedAt: null == updatedAt ? _self.updatedAt : updatedAt // ignore: cast_nullable_to_non_nullable +as DateTime,identifier: null == identifier ? _self.identifier : identifier // ignore: cast_nullable_to_non_nullable +as String, + )); +} + + +} + + /// @nodoc mixin _$SnWalletOrder { diff --git a/lib/models/wallet.g.dart b/lib/models/wallet.g.dart index a94d00c..c4647b5 100644 --- a/lib/models/wallet.g.dart +++ b/lib/models/wallet.g.dart @@ -163,6 +163,33 @@ Map _$SnWalletSubscriptionToJson( 'deleted_at': instance.deletedAt?.toIso8601String(), }; +_SnWalletSubscriptionRef _$SnWalletSubscriptionRefFromJson( + Map json, +) => _SnWalletSubscriptionRef( + id: json['id'] as String, + isActive: json['is_active'] as bool, + accountId: json['account_id'] as String, + createdAt: DateTime.parse(json['created_at'] as String), + deletedAt: + json['deleted_at'] == null + ? null + : DateTime.parse(json['deleted_at'] as String), + updatedAt: DateTime.parse(json['updated_at'] as String), + identifier: json['identifier'] as String, +); + +Map _$SnWalletSubscriptionRefToJson( + _SnWalletSubscriptionRef instance, +) => { + 'id': instance.id, + 'is_active': instance.isActive, + 'account_id': instance.accountId, + 'created_at': instance.createdAt.toIso8601String(), + 'deleted_at': instance.deletedAt?.toIso8601String(), + 'updated_at': instance.updatedAt.toIso8601String(), + 'identifier': instance.identifier, +}; + _SnWalletOrder _$SnWalletOrderFromJson(Map json) => _SnWalletOrder( id: json['id'] as String, diff --git a/lib/screens/account/leveling.dart b/lib/screens/account/leveling.dart index 030d883..9e68ae4 100644 --- a/lib/screens/account/leveling.dart +++ b/lib/screens/account/leveling.dart @@ -4,7 +4,6 @@ import 'package:flutter/material.dart'; import 'package:gap/gap.dart'; import 'package:google_fonts/google_fonts.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:island/models/user.dart'; import 'package:island/models/wallet.dart'; import 'package:island/pods/network.dart'; import 'package:island/pods/userinfo.dart'; @@ -15,6 +14,21 @@ import 'package:island/widgets/alert.dart'; import 'package:island/widgets/app_scaffold.dart'; import 'package:island/widgets/payment/payment_overlay.dart'; import 'package:easy_localization/easy_localization.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'leveling.g.dart'; + +@riverpod +Future accountStellarSubscription(Ref ref) async { + try { + final client = ref.watch(apiClientProvider); + final resp = await client.get('/subscriptions/fuzzy/solian.stellar'); + return SnWalletSubscription.fromJson(resp.data); + } catch (err) { + if (err is DioException && err.response?.statusCode == 404) return null; + rethrow; + } +} @RoutePage() class LevelingScreen extends HookConsumerWidget { @@ -23,6 +37,7 @@ class LevelingScreen extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { final user = ref.watch(userInfoProvider); + final stellarSubscription = ref.watch(accountStellarSubscriptionProvider); if (user.value == null) { return AppScaffold( @@ -65,7 +80,7 @@ class LevelingScreen extends HookConsumerWidget { const Gap(24), // Membership section - _buildMembershipSection(context, ref, user.value!), + _buildMembershipSection(context, ref, stellarSubscription), const Gap(16), // Unlocked features section @@ -222,9 +237,52 @@ class LevelingScreen extends HookConsumerWidget { Widget _buildMembershipSection( BuildContext context, WidgetRef ref, - SnAccount user, + AsyncValue stellarSubscriptionAsync, + ) { + return stellarSubscriptionAsync.when( + data: (membership) => _buildMembershipContent(context, ref, membership), + loading: + () => Container( + width: double.infinity, + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + gradient: LinearGradient( + colors: [ + Theme.of(context).colorScheme.primaryContainer, + Theme.of(context).colorScheme.secondaryContainer, + ], + begin: Alignment.topLeft, + end: Alignment.bottomRight, + ), + borderRadius: BorderRadius.circular(12), + ), + child: const Center(child: CircularProgressIndicator()), + ), + error: + (error, stack) => Container( + width: double.infinity, + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + gradient: LinearGradient( + colors: [ + Theme.of(context).colorScheme.primaryContainer, + Theme.of(context).colorScheme.secondaryContainer, + ], + begin: Alignment.topLeft, + end: Alignment.bottomRight, + ), + borderRadius: BorderRadius.circular(12), + ), + child: Text('Error loading membership: $error'), + ), + ); + } + + Widget _buildMembershipContent( + BuildContext context, + WidgetRef ref, + SnWalletSubscription? membership, ) { - final membership = user.profile.stellarMembership; final isActive = membership?.isActive ?? false; return Container( @@ -309,7 +367,9 @@ class LevelingScreen extends HookConsumerWidget { ), if (membership.endedAt != null) Text( - 'membershipExpires'.tr(args: [membership.endedAt!.formatSystem()]), + 'membershipExpires'.tr( + args: [membership.endedAt!.formatSystem()], + ), style: Theme.of(context).textTheme.bodySmall?.copyWith( color: Theme.of(context).colorScheme.onSurfaceVariant, ), @@ -354,7 +414,11 @@ class LevelingScreen extends HookConsumerWidget { 'id': 'solian.stellar.supernova', 'name': 'membershipTierSupernova'.tr(), 'price': 'membershipPriceSupernova'.tr(), - 'features': ['membershipFeatureAllNova'.tr(), 'membershipFeatureExclusiveContent'.tr(), 'membershipFeatureVipSupport'.tr()], + 'features': [ + 'membershipFeatureAllNova'.tr(), + 'membershipFeatureExclusiveContent'.tr(), + 'membershipFeatureVipSupport'.tr(), + ], 'color': Colors.orange, }, ]; @@ -544,6 +608,7 @@ class LevelingScreen extends HookConsumerWidget { data: {'order_id': paidOrder.id}, ); + ref.invalidate(accountStellarSubscriptionProvider); ref.read(userInfoProvider.notifier).fetchUser(); if (context.mounted) { showSnackBar(context, 'membershipPurchaseSuccess'.tr()); diff --git a/lib/screens/account/leveling.g.dart b/lib/screens/account/leveling.g.dart new file mode 100644 index 0000000..810cc48 --- /dev/null +++ b/lib/screens/account/leveling.g.dart @@ -0,0 +1,31 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'leveling.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$accountStellarSubscriptionHash() => + r'37fb821460e3ac50b5cf777c933b6779f732daee'; + +/// See also [accountStellarSubscription]. +@ProviderFor(accountStellarSubscription) +final accountStellarSubscriptionProvider = + AutoDisposeFutureProvider.internal( + accountStellarSubscription, + name: r'accountStellarSubscriptionProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') + ? null + : _$accountStellarSubscriptionHash, + dependencies: null, + allTransitiveDependencies: null, + ); + +@Deprecated('Will be removed in 3.0. Use Ref instead') +// ignore: unused_element +typedef AccountStellarSubscriptionRef = + AutoDisposeFutureProviderRef; +// 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/screens/account/profile.dart b/lib/screens/account/profile.dart index e5202d6..f97d8c0 100644 --- a/lib/screens/account/profile.dart +++ b/lib/screens/account/profile.dart @@ -278,7 +278,10 @@ class AccountProfileScreen extends HookConsumerWidget { children: [ Row( children: [ - Text(data.nick).fontSize(20), + AccountName( + account: data, + style: TextStyle(fontSize: 20), + ), const Gap(6), Text( '@${data.name}', diff --git a/lib/widgets/account/account_name.dart b/lib/widgets/account/account_name.dart index ac06d03..9b4cb21 100644 --- a/lib/widgets/account/account_name.dart +++ b/lib/widgets/account/account_name.dart @@ -2,6 +2,7 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:gap/gap.dart'; import 'package:island/models/user.dart'; +import 'package:island/models/wallet.dart'; import 'package:material_symbols_icons/symbols.dart'; import 'package:styled_widget/styled_widget.dart'; @@ -25,6 +26,8 @@ class AccountName extends StatelessWidget { spacing: 4, children: [ Flexible(child: Text(account.nick, style: style)), + if (account.profile.stellarMembership != null) + StellarMembershipMark(membership: account.profile.stellarMembership!), if (account.profile.verification != null) VerificationMark(mark: account.profile.verification!), ], @@ -64,6 +67,74 @@ class VerificationMark extends StatelessWidget { } } +class StellarMembershipMark extends StatelessWidget { + final SnWalletSubscriptionRef membership; + const StellarMembershipMark({super.key, required this.membership}); + + String _getMembershipTierName(String identifier) { + switch (identifier) { + case 'solian.stellar.primary': + return 'membershipTierStellar'.tr(); + case 'solian.stellar.nova': + return 'membershipTierNova'.tr(); + case 'solian.stellar.supernova': + return 'membershipTierSupernova'.tr(); + default: + return 'membershipTierUnknown'.tr(); + } + } + + Color _getMembershipTierColor(String identifier) { + switch (identifier) { + case 'solian.stellar.primary': + return Colors.amber; + case 'solian.stellar.nova': + return Colors.blue; + case 'solian.stellar.supernova': + return Colors.purple; + default: + return Colors.grey; + } + } + + IconData _getMembershipTierIcon(String identifier) { + switch (identifier) { + case 'solian.stellar.primary': + return Symbols.star; + case 'solian.stellar.nova': + return Symbols.auto_awesome; + case 'solian.stellar.supernova': + return Symbols.diamond; + default: + return Symbols.workspace_premium; + } + } + + @override + Widget build(BuildContext context) { + if (!membership.isActive) return const SizedBox.shrink(); + + final tierName = _getMembershipTierName(membership.identifier); + final tierColor = _getMembershipTierColor(membership.identifier); + final tierIcon = _getMembershipTierIcon(membership.identifier); + + return Tooltip( + richMessage: TextSpan( + text: 'stellarMembership'.tr(), + children: [ + TextSpan(text: '\n'), + TextSpan( + text: 'currentMembership'.tr(args: [tierName]), + style: TextStyle(fontWeight: FontWeight.normal), + ), + ], + style: TextStyle(fontWeight: FontWeight.bold), + ), + child: Icon(tierIcon, size: 16, color: tierColor, fill: 1), + ); + } +} + class VerificationStatusCard extends StatelessWidget { final SnVerificationMark mark; const VerificationStatusCard({super.key, required this.mark});