diff --git a/assets/i18n/en-US.json b/assets/i18n/en-US.json index a30f643..eaa8293 100644 --- a/assets/i18n/en-US.json +++ b/assets/i18n/en-US.json @@ -255,7 +255,7 @@ "walletCurrencyPoints": "New Solar Points", "walletCurrencyShortPoints": "NSP", "walletCurrencyGolds": "The Solar Dollars", - "walletCurrencyShortGolds": "TSD", + "walletCurrencyShortGolds": "NSD", "retry": "Retry", "creatorHubUnselectedHint": "Pick / create a publisher to get started.", "relationships": "Relationships", @@ -487,5 +487,31 @@ "biometricAuthFailed": "Biometric authentication failed. Please try again.", "paymentSuccess": "Payment completed successfully!", "membershipPurchaseSuccess": "Membership purchased successfully!", - "paymentError": "Payment failed: {error}" + "paymentError": "Payment failed: {error}", + "usePinInstead": "Use PIN Code", + "levelProgress": "Level Progress", + "unlockedFeatures": "Unlocked Features", + "unlockedFeaturesDescription": "Features unlocked at your current level will be displayed here.", + "stellarMembership": "Stellar Membership", + "upgradeYourPlan": "Upgrade Your Plan", + "chooseYourPlan": "Choose Your Plan", + "currentMembership": "Current: {}", + "membershipExpires": "Expires: {}", + "membershipTierStellar": "Stellar", + "membershipTierNova": "Nova", + "membershipTierSupernova": "Supernova", + "membershipTierUnknown": "Unknown", + "membershipPriceStellar": "10 NS$ per month", + "membershipPriceNova": "20 NS$ per month", + "membershipPriceSupernova": "30 NS$ per month", + "membershipFeatureBasic": "Basic features", + "membershipFeaturePrioritySupport": "Priority support", + "membershipFeatureAdFree": "Ad-free experience", + "membershipFeatureAllPrimary": "All Primary features", + "membershipFeatureAdvancedCustomization": "Advanced customization", + "membershipFeatureEarlyAccess": "Early access", + "membershipFeatureAllNova": "All Nova features", + "membershipFeatureExclusiveContent": "Exclusive content", + "membershipFeatureVipSupport": "VIP support", + "membershipCurrentBadge": "CURRENT" } diff --git a/assets/i18n/zh-TW.json b/assets/i18n/zh-TW.json index 2f8e144..d74b8e4 100644 --- a/assets/i18n/zh-TW.json +++ b/assets/i18n/zh-TW.json @@ -307,5 +307,32 @@ "chatBreakCleared": "聊天暫停已清除。", "chatBreakCustom": "自訂時長", "chatBreakEnterMinutes": "輸入分鐘數", - "chatBreakNone": "無" + "chatBreakNone": "無", + "paymentError": "付款失敗:{error}", + "usePinInstead": "使用密碼", + "levelProgress": "等級進度", + "unlockedFeatures": "已解鎖功能", + "unlockedFeaturesDescription": "您目前等級解鎖的功能將會顯示在此。", + "stellarMembership": "星際會員", + "upgradeYourPlan": "升級您的方案", + "chooseYourPlan": "選擇您的方案", + "currentMembership": "目前:{}", + "membershipExpires": "到期:{}", + "membershipTierStellar": "星際", + "membershipTierNova": "新星", + "membershipTierSupernova": "超新星", + "membershipTierUnknown": "未知", + "membershipPriceStellar": "每月 10 星幣", + "membershipPriceNova": "每月 20 星幣", + "membershipPriceSupernova": "每月 30 星幣", + "membershipFeatureBasic": "基本功能", + "membershipFeaturePrioritySupport": "優先支援", + "membershipFeatureAdFree": "無廣告體驗", + "membershipFeatureAllPrimary": "所有主要功能", + "membershipFeatureAdvancedCustomization": "進階自訂", + "membershipFeatureEarlyAccess": "搶先體驗", + "membershipFeatureAllNova": "所有新星功能", + "membershipFeatureExclusiveContent": "獨家內容", + "membershipFeatureVipSupport": "VIP 支援", + "membershipCurrentBadge": "目前" } \ No newline at end of file diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist index 858ede3..caca9f0 100644 --- a/ios/Runner/Info.plist +++ b/ios/Runner/Info.plist @@ -74,6 +74,8 @@ <string>UIInterfaceOrientationLandscapeLeft</string> <string>UIInterfaceOrientationLandscapeRight</string> </array> + <key>NSFaceIDUsageDescription</key> + <string>Allow the Solar Network verify your ownership of the logged in account and continue your action quickly.</string> <key>NSUserActivityTypes</key> <array> <string>INStartCallIntent</string> diff --git a/lib/models/wallet.dart b/lib/models/wallet.dart index f2dd341..9083640 100644 --- a/lib/models/wallet.dart +++ b/lib/models/wallet.dart @@ -62,21 +62,21 @@ sealed class SnWalletSubscription with _$SnWalletSubscription { const factory SnWalletSubscription({ required String id, required DateTime begunAt, - required DateTime endedAt, + required DateTime? endedAt, required String identifier, - required bool isActive, - required bool isFreeTrial, - required int status, - required String paymentMethod, - required Map<String, dynamic> paymentDetails, - required double basePrice, + @Default(true) bool isActive, + @Default(false) bool isFreeTrial, + @Default(1) int status, + required String? paymentMethod, + required Map<String, dynamic>? paymentDetails, + required double? basePrice, required String? couponId, required dynamic coupon, - required DateTime renewalAt, + required DateTime? renewalAt, required String accountId, required SnAccount? account, - required bool isAvailable, - required double finalPrice, + @Default(true) bool isAvailable, + required double? finalPrice, required DateTime createdAt, required DateTime updatedAt, required DateTime? deletedAt, diff --git a/lib/models/wallet.freezed.dart b/lib/models/wallet.freezed.dart index 2782f55..6f17011 100644 --- a/lib/models/wallet.freezed.dart +++ b/lib/models/wallet.freezed.dart @@ -562,7 +562,7 @@ $SnWalletCopyWith<$Res>? get payeeWallet { /// @nodoc mixin _$SnWalletSubscription { - String get id; DateTime get begunAt; DateTime get endedAt; String get identifier; bool get isActive; bool get isFreeTrial; int get status; String get paymentMethod; Map<String, dynamic> get paymentDetails; double get basePrice; String? get couponId; dynamic get coupon; DateTime get renewalAt; String get accountId; SnAccount? get account; bool get isAvailable; double get finalPrice; DateTime get createdAt; DateTime get updatedAt; DateTime? get deletedAt; + String get id; DateTime get begunAt; DateTime? get endedAt; String get identifier; bool get isActive; bool get isFreeTrial; int get status; String? get paymentMethod; Map<String, dynamic>? get paymentDetails; double? get basePrice; String? get couponId; dynamic get coupon; DateTime? get renewalAt; String get accountId; SnAccount? get account; bool get isAvailable; double? get finalPrice; DateTime get createdAt; DateTime get updatedAt; DateTime? get deletedAt; /// Create a copy of SnWalletSubscription /// with the given fields replaced by the non-null parameter values. @JsonKey(includeFromJson: false, includeToJson: false) @@ -595,7 +595,7 @@ abstract mixin class $SnWalletSubscriptionCopyWith<$Res> { factory $SnWalletSubscriptionCopyWith(SnWalletSubscription value, $Res Function(SnWalletSubscription) _then) = _$SnWalletSubscriptionCopyWithImpl; @useResult $Res call({ - String id, DateTime begunAt, DateTime endedAt, String identifier, bool isActive, bool isFreeTrial, int status, String paymentMethod, Map<String, dynamic> paymentDetails, double basePrice, String? couponId, dynamic coupon, DateTime renewalAt, String accountId, SnAccount? account, bool isAvailable, double finalPrice, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt + String id, DateTime begunAt, DateTime? endedAt, String identifier, bool isActive, bool isFreeTrial, int status, String? paymentMethod, Map<String, dynamic>? paymentDetails, double? basePrice, String? couponId, dynamic coupon, DateTime? renewalAt, String accountId, SnAccount? account, bool isAvailable, double? finalPrice, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt }); @@ -612,26 +612,26 @@ class _$SnWalletSubscriptionCopyWithImpl<$Res> /// Create a copy of SnWalletSubscription /// with the given fields replaced by the non-null parameter values. -@pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? begunAt = null,Object? endedAt = null,Object? identifier = null,Object? isActive = null,Object? isFreeTrial = null,Object? status = null,Object? paymentMethod = null,Object? paymentDetails = null,Object? basePrice = null,Object? couponId = freezed,Object? coupon = freezed,Object? renewalAt = null,Object? accountId = null,Object? account = freezed,Object? isAvailable = null,Object? finalPrice = null,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,}) { +@pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? begunAt = null,Object? endedAt = freezed,Object? identifier = null,Object? isActive = null,Object? isFreeTrial = null,Object? status = null,Object? paymentMethod = freezed,Object? paymentDetails = freezed,Object? basePrice = freezed,Object? couponId = freezed,Object? coupon = freezed,Object? renewalAt = freezed,Object? accountId = null,Object? account = freezed,Object? isAvailable = null,Object? finalPrice = freezed,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,}) { return _then(_self.copyWith( id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable as String,begunAt: null == begunAt ? _self.begunAt : begunAt // ignore: cast_nullable_to_non_nullable -as DateTime,endedAt: null == endedAt ? _self.endedAt : endedAt // ignore: cast_nullable_to_non_nullable -as DateTime,identifier: null == identifier ? _self.identifier : identifier // ignore: cast_nullable_to_non_nullable +as DateTime,endedAt: freezed == endedAt ? _self.endedAt : endedAt // ignore: cast_nullable_to_non_nullable +as DateTime?,identifier: null == identifier ? _self.identifier : identifier // ignore: cast_nullable_to_non_nullable as String,isActive: null == isActive ? _self.isActive : isActive // ignore: cast_nullable_to_non_nullable as bool,isFreeTrial: null == isFreeTrial ? _self.isFreeTrial : isFreeTrial // ignore: cast_nullable_to_non_nullable as bool,status: null == status ? _self.status : status // ignore: cast_nullable_to_non_nullable -as int,paymentMethod: null == paymentMethod ? _self.paymentMethod : paymentMethod // ignore: cast_nullable_to_non_nullable -as String,paymentDetails: null == paymentDetails ? _self.paymentDetails : paymentDetails // ignore: cast_nullable_to_non_nullable -as Map<String, dynamic>,basePrice: null == basePrice ? _self.basePrice : basePrice // ignore: cast_nullable_to_non_nullable -as double,couponId: freezed == couponId ? _self.couponId : couponId // ignore: cast_nullable_to_non_nullable +as int,paymentMethod: freezed == paymentMethod ? _self.paymentMethod : paymentMethod // ignore: cast_nullable_to_non_nullable +as String?,paymentDetails: freezed == paymentDetails ? _self.paymentDetails : paymentDetails // ignore: cast_nullable_to_non_nullable +as Map<String, dynamic>?,basePrice: freezed == basePrice ? _self.basePrice : basePrice // ignore: cast_nullable_to_non_nullable +as double?,couponId: freezed == couponId ? _self.couponId : couponId // ignore: cast_nullable_to_non_nullable as String?,coupon: freezed == coupon ? _self.coupon : coupon // ignore: cast_nullable_to_non_nullable -as dynamic,renewalAt: null == renewalAt ? _self.renewalAt : renewalAt // ignore: cast_nullable_to_non_nullable -as DateTime,accountId: null == accountId ? _self.accountId : accountId // ignore: cast_nullable_to_non_nullable +as dynamic,renewalAt: freezed == renewalAt ? _self.renewalAt : renewalAt // ignore: cast_nullable_to_non_nullable +as DateTime?,accountId: null == accountId ? _self.accountId : accountId // ignore: cast_nullable_to_non_nullable as String,account: freezed == account ? _self.account : account // ignore: cast_nullable_to_non_nullable as SnAccount?,isAvailable: null == isAvailable ? _self.isAvailable : isAvailable // ignore: cast_nullable_to_non_nullable -as bool,finalPrice: null == finalPrice ? _self.finalPrice : finalPrice // ignore: cast_nullable_to_non_nullable -as double,createdAt: null == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable +as bool,finalPrice: freezed == finalPrice ? _self.finalPrice : finalPrice // ignore: cast_nullable_to_non_nullable +as double?,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?, @@ -657,32 +657,34 @@ $SnAccountCopyWith<$Res>? get account { @JsonSerializable() class _SnWalletSubscription implements SnWalletSubscription { - const _SnWalletSubscription({required this.id, required this.begunAt, required this.endedAt, required this.identifier, required this.isActive, required this.isFreeTrial, required this.status, required this.paymentMethod, required final Map<String, dynamic> paymentDetails, required this.basePrice, required this.couponId, required this.coupon, required this.renewalAt, required this.accountId, required this.account, required this.isAvailable, required this.finalPrice, required this.createdAt, required this.updatedAt, required this.deletedAt}): _paymentDetails = paymentDetails; + const _SnWalletSubscription({required this.id, required this.begunAt, required this.endedAt, required this.identifier, this.isActive = true, this.isFreeTrial = false, this.status = 1, required this.paymentMethod, required final Map<String, dynamic>? paymentDetails, required this.basePrice, required this.couponId, required this.coupon, required this.renewalAt, required this.accountId, required this.account, this.isAvailable = true, required this.finalPrice, required this.createdAt, required this.updatedAt, required this.deletedAt}): _paymentDetails = paymentDetails; factory _SnWalletSubscription.fromJson(Map<String, dynamic> json) => _$SnWalletSubscriptionFromJson(json); @override final String id; @override final DateTime begunAt; -@override final DateTime endedAt; +@override final DateTime? endedAt; @override final String identifier; -@override final bool isActive; -@override final bool isFreeTrial; -@override final int status; -@override final String paymentMethod; - final Map<String, dynamic> _paymentDetails; -@override Map<String, dynamic> get paymentDetails { +@override@JsonKey() final bool isActive; +@override@JsonKey() final bool isFreeTrial; +@override@JsonKey() final int status; +@override final String? paymentMethod; + final Map<String, dynamic>? _paymentDetails; +@override Map<String, dynamic>? get paymentDetails { + final value = _paymentDetails; + if (value == null) return null; if (_paymentDetails is EqualUnmodifiableMapView) return _paymentDetails; // ignore: implicit_dynamic_type - return EqualUnmodifiableMapView(_paymentDetails); + return EqualUnmodifiableMapView(value); } -@override final double basePrice; +@override final double? basePrice; @override final String? couponId; @override final dynamic coupon; -@override final DateTime renewalAt; +@override final DateTime? renewalAt; @override final String accountId; @override final SnAccount? account; -@override final bool isAvailable; -@override final double finalPrice; +@override@JsonKey() final bool isAvailable; +@override final double? finalPrice; @override final DateTime createdAt; @override final DateTime updatedAt; @override final DateTime? deletedAt; @@ -720,7 +722,7 @@ abstract mixin class _$SnWalletSubscriptionCopyWith<$Res> implements $SnWalletSu factory _$SnWalletSubscriptionCopyWith(_SnWalletSubscription value, $Res Function(_SnWalletSubscription) _then) = __$SnWalletSubscriptionCopyWithImpl; @override @useResult $Res call({ - String id, DateTime begunAt, DateTime endedAt, String identifier, bool isActive, bool isFreeTrial, int status, String paymentMethod, Map<String, dynamic> paymentDetails, double basePrice, String? couponId, dynamic coupon, DateTime renewalAt, String accountId, SnAccount? account, bool isAvailable, double finalPrice, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt + String id, DateTime begunAt, DateTime? endedAt, String identifier, bool isActive, bool isFreeTrial, int status, String? paymentMethod, Map<String, dynamic>? paymentDetails, double? basePrice, String? couponId, dynamic coupon, DateTime? renewalAt, String accountId, SnAccount? account, bool isAvailable, double? finalPrice, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt }); @@ -737,26 +739,26 @@ class __$SnWalletSubscriptionCopyWithImpl<$Res> /// Create a copy of SnWalletSubscription /// with the given fields replaced by the non-null parameter values. -@override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? begunAt = null,Object? endedAt = null,Object? identifier = null,Object? isActive = null,Object? isFreeTrial = null,Object? status = null,Object? paymentMethod = null,Object? paymentDetails = null,Object? basePrice = null,Object? couponId = freezed,Object? coupon = freezed,Object? renewalAt = null,Object? accountId = null,Object? account = freezed,Object? isAvailable = null,Object? finalPrice = null,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,}) { +@override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? begunAt = null,Object? endedAt = freezed,Object? identifier = null,Object? isActive = null,Object? isFreeTrial = null,Object? status = null,Object? paymentMethod = freezed,Object? paymentDetails = freezed,Object? basePrice = freezed,Object? couponId = freezed,Object? coupon = freezed,Object? renewalAt = freezed,Object? accountId = null,Object? account = freezed,Object? isAvailable = null,Object? finalPrice = freezed,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,}) { return _then(_SnWalletSubscription( id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable as String,begunAt: null == begunAt ? _self.begunAt : begunAt // ignore: cast_nullable_to_non_nullable -as DateTime,endedAt: null == endedAt ? _self.endedAt : endedAt // ignore: cast_nullable_to_non_nullable -as DateTime,identifier: null == identifier ? _self.identifier : identifier // ignore: cast_nullable_to_non_nullable +as DateTime,endedAt: freezed == endedAt ? _self.endedAt : endedAt // ignore: cast_nullable_to_non_nullable +as DateTime?,identifier: null == identifier ? _self.identifier : identifier // ignore: cast_nullable_to_non_nullable as String,isActive: null == isActive ? _self.isActive : isActive // ignore: cast_nullable_to_non_nullable as bool,isFreeTrial: null == isFreeTrial ? _self.isFreeTrial : isFreeTrial // ignore: cast_nullable_to_non_nullable as bool,status: null == status ? _self.status : status // ignore: cast_nullable_to_non_nullable -as int,paymentMethod: null == paymentMethod ? _self.paymentMethod : paymentMethod // ignore: cast_nullable_to_non_nullable -as String,paymentDetails: null == paymentDetails ? _self._paymentDetails : paymentDetails // ignore: cast_nullable_to_non_nullable -as Map<String, dynamic>,basePrice: null == basePrice ? _self.basePrice : basePrice // ignore: cast_nullable_to_non_nullable -as double,couponId: freezed == couponId ? _self.couponId : couponId // ignore: cast_nullable_to_non_nullable +as int,paymentMethod: freezed == paymentMethod ? _self.paymentMethod : paymentMethod // ignore: cast_nullable_to_non_nullable +as String?,paymentDetails: freezed == paymentDetails ? _self._paymentDetails : paymentDetails // ignore: cast_nullable_to_non_nullable +as Map<String, dynamic>?,basePrice: freezed == basePrice ? _self.basePrice : basePrice // ignore: cast_nullable_to_non_nullable +as double?,couponId: freezed == couponId ? _self.couponId : couponId // ignore: cast_nullable_to_non_nullable as String?,coupon: freezed == coupon ? _self.coupon : coupon // ignore: cast_nullable_to_non_nullable -as dynamic,renewalAt: null == renewalAt ? _self.renewalAt : renewalAt // ignore: cast_nullable_to_non_nullable -as DateTime,accountId: null == accountId ? _self.accountId : accountId // ignore: cast_nullable_to_non_nullable +as dynamic,renewalAt: freezed == renewalAt ? _self.renewalAt : renewalAt // ignore: cast_nullable_to_non_nullable +as DateTime?,accountId: null == accountId ? _self.accountId : accountId // ignore: cast_nullable_to_non_nullable as String,account: freezed == account ? _self.account : account // ignore: cast_nullable_to_non_nullable as SnAccount?,isAvailable: null == isAvailable ? _self.isAvailable : isAvailable // ignore: cast_nullable_to_non_nullable -as bool,finalPrice: null == finalPrice ? _self.finalPrice : finalPrice // ignore: cast_nullable_to_non_nullable -as double,createdAt: null == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable +as bool,finalPrice: freezed == finalPrice ? _self.finalPrice : finalPrice // ignore: cast_nullable_to_non_nullable +as double?,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?, diff --git a/lib/models/wallet.g.dart b/lib/models/wallet.g.dart index bba682c..a94d00c 100644 --- a/lib/models/wallet.g.dart +++ b/lib/models/wallet.g.dart @@ -106,24 +106,30 @@ _SnWalletSubscription _$SnWalletSubscriptionFromJson( ) => _SnWalletSubscription( id: json['id'] as String, begunAt: DateTime.parse(json['begun_at'] as String), - endedAt: DateTime.parse(json['ended_at'] as String), + endedAt: + json['ended_at'] == null + ? null + : DateTime.parse(json['ended_at'] as String), identifier: json['identifier'] as String, - isActive: json['is_active'] as bool, - isFreeTrial: json['is_free_trial'] as bool, - status: (json['status'] as num).toInt(), - paymentMethod: json['payment_method'] as String, - paymentDetails: json['payment_details'] as Map<String, dynamic>, - basePrice: (json['base_price'] as num).toDouble(), + isActive: json['is_active'] as bool? ?? true, + isFreeTrial: json['is_free_trial'] as bool? ?? false, + status: (json['status'] as num?)?.toInt() ?? 1, + paymentMethod: json['payment_method'] as String?, + paymentDetails: json['payment_details'] as Map<String, dynamic>?, + basePrice: (json['base_price'] as num?)?.toDouble(), couponId: json['coupon_id'] as String?, coupon: json['coupon'], - renewalAt: DateTime.parse(json['renewal_at'] as String), + renewalAt: + json['renewal_at'] == null + ? null + : DateTime.parse(json['renewal_at'] as String), accountId: json['account_id'] as String, account: json['account'] == null ? null : SnAccount.fromJson(json['account'] as Map<String, dynamic>), - isAvailable: json['is_available'] as bool, - finalPrice: (json['final_price'] as num).toDouble(), + isAvailable: json['is_available'] as bool? ?? true, + finalPrice: (json['final_price'] as num?)?.toDouble(), createdAt: DateTime.parse(json['created_at'] as String), updatedAt: DateTime.parse(json['updated_at'] as String), deletedAt: @@ -137,7 +143,7 @@ Map<String, dynamic> _$SnWalletSubscriptionToJson( ) => <String, dynamic>{ 'id': instance.id, 'begun_at': instance.begunAt.toIso8601String(), - 'ended_at': instance.endedAt.toIso8601String(), + 'ended_at': instance.endedAt?.toIso8601String(), 'identifier': instance.identifier, 'is_active': instance.isActive, 'is_free_trial': instance.isFreeTrial, @@ -147,7 +153,7 @@ Map<String, dynamic> _$SnWalletSubscriptionToJson( 'base_price': instance.basePrice, 'coupon_id': instance.couponId, 'coupon': instance.coupon, - 'renewal_at': instance.renewalAt.toIso8601String(), + 'renewal_at': instance.renewalAt?.toIso8601String(), 'account_id': instance.accountId, 'account': instance.account?.toJson(), 'is_available': instance.isAvailable, diff --git a/lib/screens/account/leveling.dart b/lib/screens/account/leveling.dart index 116e7dc..030d883 100644 --- a/lib/screens/account/leveling.dart +++ b/lib/screens/account/leveling.dart @@ -9,6 +9,7 @@ import 'package:island/models/wallet.dart'; import 'package:island/pods/network.dart'; import 'package:island/pods/userinfo.dart'; import 'package:island/services/responsive.dart'; +import 'package:island/services/time.dart'; import 'package:island/widgets/account/leveling_progress.dart'; import 'package:island/widgets/alert.dart'; import 'package:island/widgets/app_scaffold.dart'; @@ -51,7 +52,7 @@ class LevelingScreen extends HookConsumerWidget { // Level Stairs Graph Text( - 'Level Progress', + 'levelProgress'.tr(), style: Theme.of( context, ).textTheme.headlineSmall?.copyWith(fontWeight: FontWeight.bold), @@ -79,12 +80,12 @@ class LevelingScreen extends HookConsumerWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - 'Unlocked Features', + 'unlockedFeatures'.tr(), style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold), ), const Gap(8), Text( - 'Features unlocked at your current level will be displayed here.', + 'unlockedFeaturesDescription'.tr(), style: Theme.of(context).textTheme.bodyMedium?.copyWith( color: Theme.of(context).colorScheme.onSurfaceVariant, ), @@ -252,7 +253,7 @@ class LevelingScreen extends HookConsumerWidget { ), const Gap(8), Text( - 'Stellar Membership', + 'stellarMembership'.tr(), style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold), ), ], @@ -265,7 +266,7 @@ class LevelingScreen extends HookConsumerWidget { ], Text( - isActive ? 'Upgrade Your Plan' : 'Choose Your Plan', + isActive ? 'upgradeYourPlan'.tr() : 'chooseYourPlan'.tr(), style: TextStyle(fontSize: 16, fontWeight: FontWeight.w600), ), const Gap(12), @@ -299,19 +300,20 @@ class LevelingScreen extends HookConsumerWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - 'Current: $tierName', + 'currentMembership'.tr(args: [tierName]), style: TextStyle( fontSize: 14, fontWeight: FontWeight.w600, color: tierColor, ), ), - Text( - 'Expires: ${_formatDate(membership.endedAt)}', - style: Theme.of(context).textTheme.bodySmall?.copyWith( - color: Theme.of(context).colorScheme.onSurfaceVariant, + if (membership.endedAt != null) + Text( + 'membershipExpires'.tr(args: [membership.endedAt!.formatSystem()]), + style: Theme.of(context).textTheme.bodySmall?.copyWith( + color: Theme.of(context).colorScheme.onSurfaceVariant, + ), ), - ), ], ), ), @@ -328,31 +330,31 @@ class LevelingScreen extends HookConsumerWidget { final tiers = [ { 'id': 'solian.stellar.primary', - 'name': 'Stellar', - 'price': '10 NS\$ per month', + 'name': 'membershipTierStellar'.tr(), + 'price': 'membershipPriceStellar'.tr(), 'features': [ - 'Basic features', - 'Priority support', - 'Ad-free experience', + 'membershipFeatureBasic'.tr(), + 'membershipFeaturePrioritySupport'.tr(), + 'membershipFeatureAdFree'.tr(), ], 'color': Colors.blue, }, { 'id': 'solian.stellar.nova', - 'name': 'Nova', - 'price': '20 NS\$ per month', + 'name': 'membershipTierNova'.tr(), + 'price': 'membershipPriceNova'.tr(), 'features': [ - 'All Primary features', - 'Advanced customization', - 'Early access', + 'membershipFeatureAllPrimary'.tr(), + 'membershipFeatureAdvancedCustomization'.tr(), + 'membershipFeatureEarlyAccess'.tr(), ], 'color': Colors.purple, }, { 'id': 'solian.stellar.supernova', - 'name': 'Supernova', - 'price': '30 NS\$ per month', - 'features': ['All Nova features', 'Exclusive content', 'VIP support'], + 'name': 'membershipTierSupernova'.tr(), + 'price': 'membershipPriceSupernova'.tr(), + 'features': ['membershipFeatureAllNova'.tr(), 'membershipFeatureExclusiveContent'.tr(), 'membershipFeatureVipSupport'.tr()], 'color': Colors.orange, }, ]; @@ -432,7 +434,7 @@ class LevelingScreen extends HookConsumerWidget { borderRadius: BorderRadius.circular(4), ), child: Text( - 'CURRENT', + 'membershipCurrentBadge'.tr(), style: TextStyle( fontSize: 10, fontWeight: FontWeight.bold, @@ -476,13 +478,13 @@ class LevelingScreen extends HookConsumerWidget { String _getMembershipTierName(String identifier) { switch (identifier) { case 'solian.stellar.primary': - return 'Primary'; + return 'membershipTierStellar'.tr(); case 'solian.stellar.nova': - return 'Nova'; + return 'membershipTierNova'.tr(); case 'solian.stellar.supernova': - return 'Supernova'; + return 'membershipTierSupernova'.tr(); default: - return 'Unknown'; + return 'membershipTierUnknown'.tr(); } } @@ -499,10 +501,6 @@ class LevelingScreen extends HookConsumerWidget { } } - String _formatDate(DateTime date) { - return '${date.day}/${date.month}/${date.year}'; - } - Future<void> _purchaseMembership( BuildContext context, WidgetRef ref, @@ -538,21 +536,23 @@ class LevelingScreen extends HookConsumerWidget { enableBiometric: true, ); + if (context.mounted) showLoadingModal(context); + if (paidOrder != null) { - // Payment successful, refresh user info or show success message - ref.invalidate(userInfoProvider); + await client.post( + '/subscriptions/order/handle', + data: {'order_id': paidOrder.id}, + ); + + ref.read(userInfoProvider.notifier).fetchUser(); if (context.mounted) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text('membershipPurchaseSuccess'.tr()), - backgroundColor: Theme.of(context).colorScheme.primary, - ), - ); + showSnackBar(context, 'membershipPurchaseSuccess'.tr()); } } } catch (err) { - if (context.mounted) hideLoadingModal(context); showErrorAlert(err); + } finally { + if (context.mounted) hideLoadingModal(context); } } } diff --git a/lib/widgets/payment/payment_overlay.dart b/lib/widgets/payment/payment_overlay.dart index b6648c5..0958907 100644 --- a/lib/widgets/payment/payment_overlay.dart +++ b/lib/widgets/payment/payment_overlay.dart @@ -12,6 +12,7 @@ import 'package:dio/dio.dart'; import 'package:local_auth/local_auth.dart'; import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:flutter/services.dart'; +import 'package:styled_widget/styled_widget.dart'; class PaymentOverlay extends HookConsumerWidget { final SnWalletOrder order; @@ -278,12 +279,7 @@ class _PaymentContentState extends ConsumerState<_PaymentContent> { _isPinMode = true; }); if (message != null && message.isNotEmpty) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text(message), - backgroundColor: Theme.of(context).colorScheme.error, - ), - ); + showSnackBar(context, message); } } @@ -423,21 +419,10 @@ class _PaymentContentState extends ConsumerState<_PaymentContent> { Widget _buildBiometricAuth() { return Column( mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, children: [ - Container( - width: 120, - height: 120, - decoration: BoxDecoration( - color: Theme.of(context).colorScheme.primaryContainer, - shape: BoxShape.circle, - ), - child: Icon( - Symbols.fingerprint, - size: 64, - color: Theme.of(context).colorScheme.onPrimaryContainer, - ), - ), - const Gap(24), + Icon(Symbols.fingerprint, size: 48), + const Gap(16), Text( 'useBiometricToConfirm'.tr(), style: Theme.of( @@ -445,15 +430,15 @@ class _PaymentContentState extends ConsumerState<_PaymentContent> { ).textTheme.titleMedium?.copyWith(fontWeight: FontWeight.w500), textAlign: TextAlign.center, ), - const Gap(16), Text( - 'touchSensorToAuthenticate'.tr(), + 'The biometric data will only be processed on your device', style: Theme.of(context).textTheme.bodyMedium?.copyWith( color: Theme.of(context).colorScheme.onSurfaceVariant, + fontSize: 11, ), textAlign: TextAlign.center, - ), - const Gap(32), + ).opacity(0.75), + const Gap(28), ElevatedButton.icon( onPressed: _authenticateWithBiometric, icon: const Icon(Symbols.fingerprint), @@ -462,13 +447,12 @@ class _PaymentContentState extends ConsumerState<_PaymentContent> { padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 12), ), ), - const Gap(16), TextButton( onPressed: () => _fallbackToPinMode(null), child: Text('usePinInstead'.tr()), ), ], - ); + ).center(); } Widget _buildActionButtons() {