Compare commits

..

12 Commits

Author SHA1 Message Date
4c0ad5ef32 Stellar program status showing 2025-06-22 21:03:02 +08:00
35b79d7562 Finishing up stellar program 2025-06-22 19:56:54 +08:00
4728df93e2 Solarpay sheet 2025-06-22 17:55:35 +08:00
3fd9cd4547 Leveling screen basis 2025-06-22 03:37:29 +08:00
beda47ff7e 👽 Update afdian connection 2025-06-22 03:16:09 +08:00
d1506f10ef 💄 Optimized call 2025-06-22 01:40:10 +08:00
006841cf82 Pin code
🐛 Bug fixes
2025-06-22 00:38:51 +08:00
f73cf10a54 Blurry tab background
♻️ Refactored tabs
2025-06-21 21:11:46 +08:00
b39248cc58 🐛 Fix nullable link preview meta fields 2025-06-21 16:52:10 +08:00
4ba809a8d6 Renders embedded links 2025-06-21 15:19:32 +08:00
a6d1ca57d7 Post truncate hint 2025-06-21 12:11:17 +08:00
fbbe373ce8 💄 Optimized compose page 2025-06-21 11:10:42 +08:00
60 changed files with 4581 additions and 819 deletions

View File

@ -65,6 +65,8 @@
"authFactorTOTPDescription": "A one-time code generated by a TOTP authenticator such as Google Authenticator or Authy.", "authFactorTOTPDescription": "A one-time code generated by a TOTP authenticator such as Google Authenticator or Authy.",
"authFactorInAppNotify": "In-app notification", "authFactorInAppNotify": "In-app notification",
"authFactorInAppNotifyDescription": "A one-time code sent via in-app notification.", "authFactorInAppNotifyDescription": "A one-time code sent via in-app notification.",
"authFactorPin": "Pin Code",
"authFactorPinDescription": "It consists of 6 digits. It cannot be used to log in. When performing some dangerous operations, the system will ask you to enter this PIN for confirmation.",
"realms": "Realms", "realms": "Realms",
"createRealm": "Create a Realm", "createRealm": "Create a Realm",
"createRealmHint": "Meet friends with same interests, build communities, and more.", "createRealmHint": "Meet friends with same interests, build communities, and more.",
@ -72,9 +74,10 @@
"deleteRealm": "Delete Realm", "deleteRealm": "Delete Realm",
"deleteRealmHint": "Are you sure to delete this realm? This will also deleted all the channels, publishers, and posts under this realm.", "deleteRealmHint": "Are you sure to delete this realm? This will also deleted all the channels, publishers, and posts under this realm.",
"explore": "Explore", "explore": "Explore",
"exploreFilterSubscriptions": "Subscriptions",
"exploreFilterFriends": "Friends",
"account": "Account", "account": "Account",
"name": "Name", "name": "Name",
"description": "Description",
"slug": "Slug", "slug": "Slug",
"slugHint": "The slug will be used in the URL to access this resource, it should be unique and URL safe.", "slugHint": "The slug will be used in the URL to access this resource, it should be unique and URL safe.",
"createChatRoom": "Create a Room", "createChatRoom": "Create a Room",
@ -152,6 +155,7 @@
"accountConnectionProviderGoogle": "Google", "accountConnectionProviderGoogle": "Google",
"accountConnectionProviderGithub": "GitHub", "accountConnectionProviderGithub": "GitHub",
"accountConnectionProviderDiscord": "Discord", "accountConnectionProviderDiscord": "Discord",
"accountConnectionProviderAfdian": "Afdian",
"checkIn": "Check In", "checkIn": "Check In",
"checkInNone": "Not checked-in yet", "checkInNone": "Not checked-in yet",
"checkInNoneHint": "Get your fortune tips and daily rewards by checking in.", "checkInNoneHint": "Get your fortune tips and daily rewards by checking in.",
@ -251,7 +255,7 @@
"walletCurrencyPoints": "New Solar Points", "walletCurrencyPoints": "New Solar Points",
"walletCurrencyShortPoints": "NSP", "walletCurrencyShortPoints": "NSP",
"walletCurrencyGolds": "The Solar Dollars", "walletCurrencyGolds": "The Solar Dollars",
"walletCurrencyShortGolds": "TSD", "walletCurrencyShortGolds": "NSD",
"retry": "Retry", "retry": "Retry",
"creatorHubUnselectedHint": "Pick / create a publisher to get started.", "creatorHubUnselectedHint": "Pick / create a publisher to get started.",
"relationships": "Relationships", "relationships": "Relationships",
@ -350,6 +354,7 @@
"postVisibilityFriends": "Friends Only", "postVisibilityFriends": "Friends Only",
"postVisibilityUnlisted": "Unlisted", "postVisibilityUnlisted": "Unlisted",
"postVisibilityPrivate": "Private", "postVisibilityPrivate": "Private",
"postTruncated": "Content truncated, tap to view full post",
"copyMessage": "Copy Message", "copyMessage": "Copy Message",
"authFactor": "Authentication Factor", "authFactor": "Authentication Factor",
"authFactorDelete": "Delete the Factor", "authFactorDelete": "Delete the Factor",
@ -459,5 +464,54 @@
"unspecified": "Unspecified", "unspecified": "Unspecified",
"added": "Added", "added": "Added",
"preview": "Preview", "preview": "Preview",
"togglePreview": "Toggle Preview" "togglePreview": "Toggle Preview",
"subscribe": "Subscribe",
"unsubscribe": "Unsubscribe",
"paymentVerification": "Payment Verification",
"paymentSummary": "Payment Summary",
"amount": "Amount",
"description": "Description",
"pinCode": "PIN Code",
"biometric": "Biometric",
"enterPinToConfirm": "Enter your 6-digit PIN to confirm payment",
"clearPin": "Clear PIN",
"useBiometricToConfirm": "Use biometric authentication to confirm payment",
"touchSensorToAuthenticate": "Touch the sensor to authenticate",
"authenticating": "Authenticating...",
"authenticateNow": "Authenticate Now",
"processing": "Processing...",
"processingPayment": "Processing Payment...",
"pleaseWait": "Please wait",
"paymentFailed": "Payment failed. Please try again.",
"invalidPin": "Invalid PIN. Please try again.",
"biometricAuthFailed": "Biometric authentication failed. Please try again.",
"paymentSuccess": "Payment completed successfully!",
"membershipPurchaseSuccess": "Membership purchased successfully!",
"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"
} }

View File

@ -292,6 +292,7 @@
"postVisibilityFriends": "仅好友可见", "postVisibilityFriends": "仅好友可见",
"postVisibilityUnlisted": "不公开", "postVisibilityUnlisted": "不公开",
"postVisibilityPrivate": "私密", "postVisibilityPrivate": "私密",
"postTruncated": "内容已截断,点击查看完整帖子",
"chatNotifyLevel": "通知级别", "chatNotifyLevel": "通知级别",
"chatNotifyLevelDescription": "决定您将收到多少通知。", "chatNotifyLevelDescription": "决定您将收到多少通知。",
"chatNotifyLevelAll": "全部", "chatNotifyLevelAll": "全部",
@ -312,5 +313,11 @@
"checkInResultT1": "凶", "checkInResultT1": "凶",
"checkInResultT2": "中平", "checkInResultT2": "中平",
"checkInResultT3": "吉", "checkInResultT3": "吉",
"checkInResultT4": "大吉" "checkInResultT4": "大吉",
"authenticating": "认证中...",
"processing": "处理中...",
"processingPayment": "处理付款中...",
"pleaseWait": "请稍候",
"paymentFailed": "付款失败,请重试。",
"paymentSuccess": "付款成功完成!"
} }

View File

@ -307,5 +307,32 @@
"chatBreakCleared": "聊天暫停已清除。", "chatBreakCleared": "聊天暫停已清除。",
"chatBreakCustom": "自訂時長", "chatBreakCustom": "自訂時長",
"chatBreakEnterMinutes": "輸入分鐘數", "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": "目前"
} }

View File

@ -0,0 +1,10 @@
<svg
viewBox="0 0 160 160"
xmlns="http://www.w3.org/2000/svg"
>
<path
fill-rule="evenodd"
d="M134.614 98.3714C133.294 97.5334 131.909 97.1697 130.563 97.02C133.724 89.3002 135.736 79.1949 128.887 69.1574C118.406 53.7998 103.38 45.8198 84.2346 45.4382C78.7809 45.3312 72.3517 45.5844 65.5487 45.8554C57.6493 46.1692 47.1369 46.5793 39.9921 45.9873C41.4161 45.2136 42.9326 44.4719 44.2462 43.8336C49.2728 41.384 53.2314 39.4763 51.9214 36.0925C51.2343 34.117 49.1874 33.0794 45.8233 33.0045C38.7426 32.8441 23.4421 36.9447 20.6903 43.8586C19.1418 47.7524 18.8854 55.2689 34.5668 61.9119C41.0174 64.6503 59.237 67.9879 66.2678 68.6867C68.2542 68.8793 69.7743 69.2822 70.9277 69.7101C69.3151 70.7727 67.6597 71.8888 65.9972 73.0298C63.1102 71.3824 58.3897 69.4391 54.8654 71.846C53.502 72.7695 52.7259 74.1316 52.6903 75.6827C52.6405 77.6117 53.8081 79.498 55.1217 81.017C49.9314 85.1639 45.7343 89.1825 44.2462 92.2811C42.5873 96.0893 41.9109 102.322 45.008 108.402C48.9382 116.118 57.6279 121.499 70.8423 124.394C88.1114 128.17 103.027 124.768 112.895 119.566C118.388 116.671 122.286 113.215 124.18 110.131C124.768 110.317 125.355 110.506 125.96 110.695C126.804 110.951 127.648 111.208 128.438 111.49C131.051 112.395 133.942 112.274 136.167 111.151C136.206 111.133 136.248 111.108 136.291 111.087C137.968 110.202 139.175 108.783 139.705 107.072C141.129 102.458 137.064 99.9082 134.614 98.3714ZM64.9999 90.6681C63.4307 90.6681 62.1621 91.9382 62.1621 93.5091C62.1621 95.0836 63.4307 96.3537 64.9999 96.3537C66.5691 96.3537 67.8378 95.0836 67.8378 93.5091C67.8378 91.9382 66.5691 90.6681 64.9999 90.6681ZM91.7568 99.1965C90.1876 99.1965 88.9189 100.467 88.9189 102.038C88.9189 103.612 90.1876 104.882 91.7568 104.882C93.326 104.882 94.5946 103.612 94.5946 102.038C94.5946 100.467 93.326 99.1965 91.7568 99.1965Z"
fill="currentColor"
/>
</svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@ -84,6 +84,8 @@ PODS:
- Flutter - Flutter
- flutter_platform_alert (0.0.1): - flutter_platform_alert (0.0.1):
- Flutter - Flutter
- flutter_secure_storage (3.3.1):
- Flutter
- flutter_timezone (0.0.1): - flutter_timezone (0.0.1):
- Flutter - Flutter
- flutter_udid (0.0.1): - flutter_udid (0.0.1):
@ -131,6 +133,9 @@ PODS:
- Flutter - Flutter
- flutter_webrtc - flutter_webrtc
- WebRTC-SDK (= 125.6422.07) - WebRTC-SDK (= 125.6422.07)
- local_auth_darwin (0.0.1):
- Flutter
- FlutterMacOS
- media_kit_libs_ios_video (1.0.4): - media_kit_libs_ios_video (1.0.4):
- Flutter - Flutter
- media_kit_video (0.0.1): - media_kit_video (0.0.1):
@ -210,6 +215,7 @@ DEPENDENCIES:
- flutter_inappwebview_ios (from `.symlinks/plugins/flutter_inappwebview_ios/ios`) - flutter_inappwebview_ios (from `.symlinks/plugins/flutter_inappwebview_ios/ios`)
- flutter_native_splash (from `.symlinks/plugins/flutter_native_splash/ios`) - flutter_native_splash (from `.symlinks/plugins/flutter_native_splash/ios`)
- flutter_platform_alert (from `.symlinks/plugins/flutter_platform_alert/ios`) - flutter_platform_alert (from `.symlinks/plugins/flutter_platform_alert/ios`)
- flutter_secure_storage (from `.symlinks/plugins/flutter_secure_storage/ios`)
- flutter_timezone (from `.symlinks/plugins/flutter_timezone/ios`) - flutter_timezone (from `.symlinks/plugins/flutter_timezone/ios`)
- flutter_udid (from `.symlinks/plugins/flutter_udid/ios`) - flutter_udid (from `.symlinks/plugins/flutter_udid/ios`)
- flutter_webrtc (from `.symlinks/plugins/flutter_webrtc/ios`) - flutter_webrtc (from `.symlinks/plugins/flutter_webrtc/ios`)
@ -218,6 +224,7 @@ DEPENDENCIES:
- irondash_engine_context (from `.symlinks/plugins/irondash_engine_context/ios`) - irondash_engine_context (from `.symlinks/plugins/irondash_engine_context/ios`)
- Kingfisher (~> 8.0) - Kingfisher (~> 8.0)
- livekit_client (from `.symlinks/plugins/livekit_client/ios`) - livekit_client (from `.symlinks/plugins/livekit_client/ios`)
- local_auth_darwin (from `.symlinks/plugins/local_auth_darwin/darwin`)
- media_kit_libs_ios_video (from `.symlinks/plugins/media_kit_libs_ios_video/ios`) - media_kit_libs_ios_video (from `.symlinks/plugins/media_kit_libs_ios_video/ios`)
- media_kit_video (from `.symlinks/plugins/media_kit_video/ios`) - media_kit_video (from `.symlinks/plugins/media_kit_video/ios`)
- native_exif (from `.symlinks/plugins/native_exif/ios`) - native_exif (from `.symlinks/plugins/native_exif/ios`)
@ -277,6 +284,8 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/flutter_native_splash/ios" :path: ".symlinks/plugins/flutter_native_splash/ios"
flutter_platform_alert: flutter_platform_alert:
:path: ".symlinks/plugins/flutter_platform_alert/ios" :path: ".symlinks/plugins/flutter_platform_alert/ios"
flutter_secure_storage:
:path: ".symlinks/plugins/flutter_secure_storage/ios"
flutter_timezone: flutter_timezone:
:path: ".symlinks/plugins/flutter_timezone/ios" :path: ".symlinks/plugins/flutter_timezone/ios"
flutter_udid: flutter_udid:
@ -291,6 +300,8 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/irondash_engine_context/ios" :path: ".symlinks/plugins/irondash_engine_context/ios"
livekit_client: livekit_client:
:path: ".symlinks/plugins/livekit_client/ios" :path: ".symlinks/plugins/livekit_client/ios"
local_auth_darwin:
:path: ".symlinks/plugins/local_auth_darwin/darwin"
media_kit_libs_ios_video: media_kit_libs_ios_video:
:path: ".symlinks/plugins/media_kit_libs_ios_video/ios" :path: ".symlinks/plugins/media_kit_libs_ios_video/ios"
media_kit_video: media_kit_video:
@ -341,6 +352,7 @@ SPEC CHECKSUMS:
flutter_inappwebview_ios: b89ba3482b96fb25e00c967aae065701b66e9b99 flutter_inappwebview_ios: b89ba3482b96fb25e00c967aae065701b66e9b99
flutter_native_splash: c32d145d68aeda5502d5f543ee38c192065986cf flutter_native_splash: c32d145d68aeda5502d5f543ee38c192065986cf
flutter_platform_alert: bf3b5fcd4ac14bd637e20527e9c471633071afd3 flutter_platform_alert: bf3b5fcd4ac14bd637e20527e9c471633071afd3
flutter_secure_storage: 50035aef357c5a8bdd67fd6bc81370d46efc4d16
flutter_timezone: 7c838e17ffd4645d261e87037e5bebf6d38fe544 flutter_timezone: 7c838e17ffd4645d261e87037e5bebf6d38fe544
flutter_udid: f7c3884e6ec2951efe4f9de082257fc77c4d15e9 flutter_udid: f7c3884e6ec2951efe4f9de082257fc77c4d15e9
flutter_webrtc: fd0d3bdef8766a0736dbbe2e5b7e85f1f3c52117 flutter_webrtc: fd0d3bdef8766a0736dbbe2e5b7e85f1f3c52117
@ -351,6 +363,7 @@ SPEC CHECKSUMS:
irondash_engine_context: 8e58ca8e0212ee9d1c7dc6a42121849986c88486 irondash_engine_context: 8e58ca8e0212ee9d1c7dc6a42121849986c88486
Kingfisher: 0621d0ac0c78fecb19f6dc5303bde2b52abaf2f5 Kingfisher: 0621d0ac0c78fecb19f6dc5303bde2b52abaf2f5
livekit_client: 9e901890552514206e5ff828903ed271531da264 livekit_client: 9e901890552514206e5ff828903ed271531da264
local_auth_darwin: 553ce4f9b16d3fdfeafce9cf042e7c9f77c1c391
media_kit_libs_ios_video: 5a18affdb97d1f5d466dc79988b13eff6c5e2854 media_kit_libs_ios_video: 5a18affdb97d1f5d466dc79988b13eff6c5e2854
media_kit_video: 1746e198cb697d1ffb734b1d05ec429d1fcd1474 media_kit_video: 1746e198cb697d1ffb734b1d05ec429d1fcd1474
nanopb: fad817b59e0457d11a5dfbde799381cd727c1275 nanopb: fad817b59e0457d11a5dfbde799381cd727c1275

View File

@ -74,6 +74,8 @@
<string>UIInterfaceOrientationLandscapeLeft</string> <string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string> <string>UIInterfaceOrientationLandscapeRight</string>
</array> </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> <key>NSUserActivityTypes</key>
<array> <array>
<string>INStartCallIntent</string> <string>INStartCallIntent</string>

View File

@ -18,7 +18,6 @@ import 'package:bitsdojo_window/bitsdojo_window.dart';
import 'package:island/pods/userinfo.dart'; import 'package:island/pods/userinfo.dart';
import 'package:island/pods/websocket.dart'; import 'package:island/pods/websocket.dart';
import 'package:island/route.dart'; import 'package:island/route.dart';
import 'package:island/screens/auth/tabs.dart';
import 'package:island/services/notify.dart'; import 'package:island/services/notify.dart';
import 'package:island/services/timezone.dart'; import 'package:island/services/timezone.dart';
import 'package:island/widgets/alert.dart'; import 'package:island/widgets/alert.dart';
@ -164,22 +163,13 @@ class IslandApp extends HookConsumerWidget {
theme: theme?.light, theme: theme?.light,
darkTheme: theme?.dark, darkTheme: theme?.dark,
themeMode: ThemeMode.system, themeMode: ThemeMode.system,
routerConfig: appRouter.config( routerConfig: appRouter.config(),
navigatorObservers:
() => [
TabNavigationObserver(
onChange: (route) {
ref.read(currentRouteProvider.notifier).state = route;
},
),
],
),
supportedLocales: context.supportedLocales, supportedLocales: context.supportedLocales,
localizationsDelegates: [ localizationsDelegates: [
...context.localizationDelegates, ...context.localizationDelegates,
CroppyLocalizations.delegate, CroppyLocalizations.delegate,
RelativeTimeLocalizations.delegate, RelativeTimeLocalizations.delegate,
], // this contains the cupertino one ],
locale: context.locale, locale: context.locale,
builder: (context, child) { builder: (context, child) {
return Overlay( return Overlay(
@ -188,10 +178,7 @@ class IslandApp extends HookConsumerWidget {
builder: builder:
(_) => WindowScaffold( (_) => WindowScaffold(
router: appRouter, router: appRouter,
child: TabsNavigationWidget( child: child ?? const SizedBox.shrink(),
router: appRouter,
child: child ?? const SizedBox.shrink(),
),
), ),
), ),
], ],

23
lib/models/embed.dart Normal file
View File

@ -0,0 +1,23 @@
import 'package:freezed_annotation/freezed_annotation.dart';
part 'embed.freezed.dart';
part 'embed.g.dart';
@freezed
sealed class SnEmbedLink with _$SnEmbedLink {
const factory SnEmbedLink({
@JsonKey(name: 'Type') required String type,
@JsonKey(name: 'Url') required String url,
@JsonKey(name: 'Title') required String title,
@JsonKey(name: 'Description') required String? description,
@JsonKey(name: 'ImageUrl') required String? imageUrl,
@JsonKey(name: 'FaviconUrl') required String faviconUrl,
@JsonKey(name: 'SiteName') required String siteName,
@JsonKey(name: 'ContentType') required String? contentType,
@JsonKey(name: 'Author') required String? author,
@JsonKey(name: 'PublishedDate') required DateTime? publishedDate,
}) = _SnEmbedLink;
factory SnEmbedLink.fromJson(Map<String, dynamic> json) =>
_$SnEmbedLinkFromJson(json);
}

View File

@ -0,0 +1,175 @@
// dart format width=80
// coverage:ignore-file
// GENERATED CODE - DO NOT MODIFY BY HAND
// ignore_for_file: type=lint
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
part of 'embed.dart';
// **************************************************************************
// FreezedGenerator
// **************************************************************************
// dart format off
T _$identity<T>(T value) => value;
/// @nodoc
mixin _$SnEmbedLink {
@JsonKey(name: 'Type') String get type;@JsonKey(name: 'Url') String get url;@JsonKey(name: 'Title') String get title;@JsonKey(name: 'Description') String? get description;@JsonKey(name: 'ImageUrl') String? get imageUrl;@JsonKey(name: 'FaviconUrl') String get faviconUrl;@JsonKey(name: 'SiteName') String get siteName;@JsonKey(name: 'ContentType') String? get contentType;@JsonKey(name: 'Author') String? get author;@JsonKey(name: 'PublishedDate') DateTime? get publishedDate;
/// Create a copy of SnEmbedLink
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
$SnEmbedLinkCopyWith<SnEmbedLink> get copyWith => _$SnEmbedLinkCopyWithImpl<SnEmbedLink>(this as SnEmbedLink, _$identity);
/// Serializes this SnEmbedLink to a JSON map.
Map<String, dynamic> toJson();
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is SnEmbedLink&&(identical(other.type, type) || other.type == type)&&(identical(other.url, url) || other.url == url)&&(identical(other.title, title) || other.title == title)&&(identical(other.description, description) || other.description == description)&&(identical(other.imageUrl, imageUrl) || other.imageUrl == imageUrl)&&(identical(other.faviconUrl, faviconUrl) || other.faviconUrl == faviconUrl)&&(identical(other.siteName, siteName) || other.siteName == siteName)&&(identical(other.contentType, contentType) || other.contentType == contentType)&&(identical(other.author, author) || other.author == author)&&(identical(other.publishedDate, publishedDate) || other.publishedDate == publishedDate));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType,type,url,title,description,imageUrl,faviconUrl,siteName,contentType,author,publishedDate);
@override
String toString() {
return 'SnEmbedLink(type: $type, url: $url, title: $title, description: $description, imageUrl: $imageUrl, faviconUrl: $faviconUrl, siteName: $siteName, contentType: $contentType, author: $author, publishedDate: $publishedDate)';
}
}
/// @nodoc
abstract mixin class $SnEmbedLinkCopyWith<$Res> {
factory $SnEmbedLinkCopyWith(SnEmbedLink value, $Res Function(SnEmbedLink) _then) = _$SnEmbedLinkCopyWithImpl;
@useResult
$Res call({
@JsonKey(name: 'Type') String type,@JsonKey(name: 'Url') String url,@JsonKey(name: 'Title') String title,@JsonKey(name: 'Description') String? description,@JsonKey(name: 'ImageUrl') String? imageUrl,@JsonKey(name: 'FaviconUrl') String faviconUrl,@JsonKey(name: 'SiteName') String siteName,@JsonKey(name: 'ContentType') String? contentType,@JsonKey(name: 'Author') String? author,@JsonKey(name: 'PublishedDate') DateTime? publishedDate
});
}
/// @nodoc
class _$SnEmbedLinkCopyWithImpl<$Res>
implements $SnEmbedLinkCopyWith<$Res> {
_$SnEmbedLinkCopyWithImpl(this._self, this._then);
final SnEmbedLink _self;
final $Res Function(SnEmbedLink) _then;
/// Create a copy of SnEmbedLink
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline') @override $Res call({Object? type = null,Object? url = null,Object? title = null,Object? description = freezed,Object? imageUrl = freezed,Object? faviconUrl = null,Object? siteName = null,Object? contentType = freezed,Object? author = freezed,Object? publishedDate = freezed,}) {
return _then(_self.copyWith(
type: null == type ? _self.type : type // ignore: cast_nullable_to_non_nullable
as String,url: null == url ? _self.url : url // ignore: cast_nullable_to_non_nullable
as String,title: null == title ? _self.title : title // ignore: cast_nullable_to_non_nullable
as String,description: freezed == description ? _self.description : description // ignore: cast_nullable_to_non_nullable
as String?,imageUrl: freezed == imageUrl ? _self.imageUrl : imageUrl // ignore: cast_nullable_to_non_nullable
as String?,faviconUrl: null == faviconUrl ? _self.faviconUrl : faviconUrl // ignore: cast_nullable_to_non_nullable
as String,siteName: null == siteName ? _self.siteName : siteName // ignore: cast_nullable_to_non_nullable
as String,contentType: freezed == contentType ? _self.contentType : contentType // ignore: cast_nullable_to_non_nullable
as String?,author: freezed == author ? _self.author : author // ignore: cast_nullable_to_non_nullable
as String?,publishedDate: freezed == publishedDate ? _self.publishedDate : publishedDate // ignore: cast_nullable_to_non_nullable
as DateTime?,
));
}
}
/// @nodoc
@JsonSerializable()
class _SnEmbedLink implements SnEmbedLink {
const _SnEmbedLink({@JsonKey(name: 'Type') required this.type, @JsonKey(name: 'Url') required this.url, @JsonKey(name: 'Title') required this.title, @JsonKey(name: 'Description') required this.description, @JsonKey(name: 'ImageUrl') required this.imageUrl, @JsonKey(name: 'FaviconUrl') required this.faviconUrl, @JsonKey(name: 'SiteName') required this.siteName, @JsonKey(name: 'ContentType') required this.contentType, @JsonKey(name: 'Author') required this.author, @JsonKey(name: 'PublishedDate') required this.publishedDate});
factory _SnEmbedLink.fromJson(Map<String, dynamic> json) => _$SnEmbedLinkFromJson(json);
@override@JsonKey(name: 'Type') final String type;
@override@JsonKey(name: 'Url') final String url;
@override@JsonKey(name: 'Title') final String title;
@override@JsonKey(name: 'Description') final String? description;
@override@JsonKey(name: 'ImageUrl') final String? imageUrl;
@override@JsonKey(name: 'FaviconUrl') final String faviconUrl;
@override@JsonKey(name: 'SiteName') final String siteName;
@override@JsonKey(name: 'ContentType') final String? contentType;
@override@JsonKey(name: 'Author') final String? author;
@override@JsonKey(name: 'PublishedDate') final DateTime? publishedDate;
/// Create a copy of SnEmbedLink
/// with the given fields replaced by the non-null parameter values.
@override @JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
_$SnEmbedLinkCopyWith<_SnEmbedLink> get copyWith => __$SnEmbedLinkCopyWithImpl<_SnEmbedLink>(this, _$identity);
@override
Map<String, dynamic> toJson() {
return _$SnEmbedLinkToJson(this, );
}
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is _SnEmbedLink&&(identical(other.type, type) || other.type == type)&&(identical(other.url, url) || other.url == url)&&(identical(other.title, title) || other.title == title)&&(identical(other.description, description) || other.description == description)&&(identical(other.imageUrl, imageUrl) || other.imageUrl == imageUrl)&&(identical(other.faviconUrl, faviconUrl) || other.faviconUrl == faviconUrl)&&(identical(other.siteName, siteName) || other.siteName == siteName)&&(identical(other.contentType, contentType) || other.contentType == contentType)&&(identical(other.author, author) || other.author == author)&&(identical(other.publishedDate, publishedDate) || other.publishedDate == publishedDate));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType,type,url,title,description,imageUrl,faviconUrl,siteName,contentType,author,publishedDate);
@override
String toString() {
return 'SnEmbedLink(type: $type, url: $url, title: $title, description: $description, imageUrl: $imageUrl, faviconUrl: $faviconUrl, siteName: $siteName, contentType: $contentType, author: $author, publishedDate: $publishedDate)';
}
}
/// @nodoc
abstract mixin class _$SnEmbedLinkCopyWith<$Res> implements $SnEmbedLinkCopyWith<$Res> {
factory _$SnEmbedLinkCopyWith(_SnEmbedLink value, $Res Function(_SnEmbedLink) _then) = __$SnEmbedLinkCopyWithImpl;
@override @useResult
$Res call({
@JsonKey(name: 'Type') String type,@JsonKey(name: 'Url') String url,@JsonKey(name: 'Title') String title,@JsonKey(name: 'Description') String? description,@JsonKey(name: 'ImageUrl') String? imageUrl,@JsonKey(name: 'FaviconUrl') String faviconUrl,@JsonKey(name: 'SiteName') String siteName,@JsonKey(name: 'ContentType') String? contentType,@JsonKey(name: 'Author') String? author,@JsonKey(name: 'PublishedDate') DateTime? publishedDate
});
}
/// @nodoc
class __$SnEmbedLinkCopyWithImpl<$Res>
implements _$SnEmbedLinkCopyWith<$Res> {
__$SnEmbedLinkCopyWithImpl(this._self, this._then);
final _SnEmbedLink _self;
final $Res Function(_SnEmbedLink) _then;
/// Create a copy of SnEmbedLink
/// with the given fields replaced by the non-null parameter values.
@override @pragma('vm:prefer-inline') $Res call({Object? type = null,Object? url = null,Object? title = null,Object? description = freezed,Object? imageUrl = freezed,Object? faviconUrl = null,Object? siteName = null,Object? contentType = freezed,Object? author = freezed,Object? publishedDate = freezed,}) {
return _then(_SnEmbedLink(
type: null == type ? _self.type : type // ignore: cast_nullable_to_non_nullable
as String,url: null == url ? _self.url : url // ignore: cast_nullable_to_non_nullable
as String,title: null == title ? _self.title : title // ignore: cast_nullable_to_non_nullable
as String,description: freezed == description ? _self.description : description // ignore: cast_nullable_to_non_nullable
as String?,imageUrl: freezed == imageUrl ? _self.imageUrl : imageUrl // ignore: cast_nullable_to_non_nullable
as String?,faviconUrl: null == faviconUrl ? _self.faviconUrl : faviconUrl // ignore: cast_nullable_to_non_nullable
as String,siteName: null == siteName ? _self.siteName : siteName // ignore: cast_nullable_to_non_nullable
as String,contentType: freezed == contentType ? _self.contentType : contentType // ignore: cast_nullable_to_non_nullable
as String?,author: freezed == author ? _self.author : author // ignore: cast_nullable_to_non_nullable
as String?,publishedDate: freezed == publishedDate ? _self.publishedDate : publishedDate // ignore: cast_nullable_to_non_nullable
as DateTime?,
));
}
}
// dart format on

37
lib/models/embed.g.dart Normal file
View File

@ -0,0 +1,37 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'embed.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
_SnEmbedLink _$SnEmbedLinkFromJson(Map<String, dynamic> json) => _SnEmbedLink(
type: json['Type'] as String,
url: json['Url'] as String,
title: json['Title'] as String,
description: json['Description'] as String?,
imageUrl: json['ImageUrl'] as String?,
faviconUrl: json['FaviconUrl'] as String,
siteName: json['SiteName'] as String,
contentType: json['ContentType'] as String?,
author: json['Author'] as String?,
publishedDate:
json['PublishedDate'] == null
? null
: DateTime.parse(json['PublishedDate'] as String),
);
Map<String, dynamic> _$SnEmbedLinkToJson(_SnEmbedLink instance) =>
<String, dynamic>{
'Type': instance.type,
'Url': instance.url,
'Title': instance.title,
'Description': instance.description,
'ImageUrl': instance.imageUrl,
'FaviconUrl': instance.faviconUrl,
'SiteName': instance.siteName,
'ContentType': instance.contentType,
'Author': instance.author,
'PublishedDate': instance.publishedDate?.toIso8601String(),
};

View File

@ -39,6 +39,7 @@ sealed class SnPost with _$SnPost {
required DateTime createdAt, required DateTime createdAt,
required DateTime updatedAt, required DateTime updatedAt,
required DateTime? deletedAt, required DateTime? deletedAt,
@Default(false) bool isTruncated,
}) = _SnPost; }) = _SnPost;
factory SnPost.fromJson(Map<String, dynamic> json) => _$SnPostFromJson(json); factory SnPost.fromJson(Map<String, dynamic> json) => _$SnPostFromJson(json);
@ -85,7 +86,7 @@ sealed class SnPublisherStats with _$SnPublisherStats {
sealed class SnSubscriptionStatus with _$SnSubscriptionStatus { sealed class SnSubscriptionStatus with _$SnSubscriptionStatus {
const factory SnSubscriptionStatus({ const factory SnSubscriptionStatus({
required bool isSubscribed, required bool isSubscribed,
required int publisherId, required String publisherId,
required String publisherName, required String publisherName,
}) = _SnSubscriptionStatus; }) = _SnSubscriptionStatus;

View File

@ -16,7 +16,7 @@ T _$identity<T>(T value) => value;
/// @nodoc /// @nodoc
mixin _$SnPost { mixin _$SnPost {
String get id; String? get title; String? get description; String? get language; DateTime? get editedAt; DateTime get publishedAt; int get visibility; String? get content; int get type; Map<String, dynamic>? get meta; int get viewsUnique; int get viewsTotal; int get upvotes; int get downvotes; int get repliesCount; String? get threadedPostId; SnPost? get threadedPost; String? get repliedPostId; SnPost? get repliedPost; String? get forwardedPostId; SnPost? get forwardedPost; List<SnCloudFile> get attachments; SnPublisher get publisher; Map<String, int> get reactionsCount; List<dynamic> get reactions; List<dynamic> get tags; List<dynamic> get categories; List<dynamic> get collections; DateTime get createdAt; DateTime get updatedAt; DateTime? get deletedAt; String get id; String? get title; String? get description; String? get language; DateTime? get editedAt; DateTime get publishedAt; int get visibility; String? get content; int get type; Map<String, dynamic>? get meta; int get viewsUnique; int get viewsTotal; int get upvotes; int get downvotes; int get repliesCount; String? get threadedPostId; SnPost? get threadedPost; String? get repliedPostId; SnPost? get repliedPost; String? get forwardedPostId; SnPost? get forwardedPost; List<SnCloudFile> get attachments; SnPublisher get publisher; Map<String, int> get reactionsCount; List<dynamic> get reactions; List<dynamic> get tags; List<dynamic> get categories; List<dynamic> get collections; DateTime get createdAt; DateTime get updatedAt; DateTime? get deletedAt; bool get isTruncated;
/// Create a copy of SnPost /// Create a copy of SnPost
/// with the given fields replaced by the non-null parameter values. /// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false) @JsonKey(includeFromJson: false, includeToJson: false)
@ -29,16 +29,16 @@ $SnPostCopyWith<SnPost> get copyWith => _$SnPostCopyWithImpl<SnPost>(this as SnP
@override @override
bool operator ==(Object other) { bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is SnPost&&(identical(other.id, id) || other.id == id)&&(identical(other.title, title) || other.title == title)&&(identical(other.description, description) || other.description == description)&&(identical(other.language, language) || other.language == language)&&(identical(other.editedAt, editedAt) || other.editedAt == editedAt)&&(identical(other.publishedAt, publishedAt) || other.publishedAt == publishedAt)&&(identical(other.visibility, visibility) || other.visibility == visibility)&&(identical(other.content, content) || other.content == content)&&(identical(other.type, type) || other.type == type)&&const DeepCollectionEquality().equals(other.meta, meta)&&(identical(other.viewsUnique, viewsUnique) || other.viewsUnique == viewsUnique)&&(identical(other.viewsTotal, viewsTotal) || other.viewsTotal == viewsTotal)&&(identical(other.upvotes, upvotes) || other.upvotes == upvotes)&&(identical(other.downvotes, downvotes) || other.downvotes == downvotes)&&(identical(other.repliesCount, repliesCount) || other.repliesCount == repliesCount)&&(identical(other.threadedPostId, threadedPostId) || other.threadedPostId == threadedPostId)&&(identical(other.threadedPost, threadedPost) || other.threadedPost == threadedPost)&&(identical(other.repliedPostId, repliedPostId) || other.repliedPostId == repliedPostId)&&(identical(other.repliedPost, repliedPost) || other.repliedPost == repliedPost)&&(identical(other.forwardedPostId, forwardedPostId) || other.forwardedPostId == forwardedPostId)&&(identical(other.forwardedPost, forwardedPost) || other.forwardedPost == forwardedPost)&&const DeepCollectionEquality().equals(other.attachments, attachments)&&(identical(other.publisher, publisher) || other.publisher == publisher)&&const DeepCollectionEquality().equals(other.reactionsCount, reactionsCount)&&const DeepCollectionEquality().equals(other.reactions, reactions)&&const DeepCollectionEquality().equals(other.tags, tags)&&const DeepCollectionEquality().equals(other.categories, categories)&&const DeepCollectionEquality().equals(other.collections, collections)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt)); return identical(this, other) || (other.runtimeType == runtimeType&&other is SnPost&&(identical(other.id, id) || other.id == id)&&(identical(other.title, title) || other.title == title)&&(identical(other.description, description) || other.description == description)&&(identical(other.language, language) || other.language == language)&&(identical(other.editedAt, editedAt) || other.editedAt == editedAt)&&(identical(other.publishedAt, publishedAt) || other.publishedAt == publishedAt)&&(identical(other.visibility, visibility) || other.visibility == visibility)&&(identical(other.content, content) || other.content == content)&&(identical(other.type, type) || other.type == type)&&const DeepCollectionEquality().equals(other.meta, meta)&&(identical(other.viewsUnique, viewsUnique) || other.viewsUnique == viewsUnique)&&(identical(other.viewsTotal, viewsTotal) || other.viewsTotal == viewsTotal)&&(identical(other.upvotes, upvotes) || other.upvotes == upvotes)&&(identical(other.downvotes, downvotes) || other.downvotes == downvotes)&&(identical(other.repliesCount, repliesCount) || other.repliesCount == repliesCount)&&(identical(other.threadedPostId, threadedPostId) || other.threadedPostId == threadedPostId)&&(identical(other.threadedPost, threadedPost) || other.threadedPost == threadedPost)&&(identical(other.repliedPostId, repliedPostId) || other.repliedPostId == repliedPostId)&&(identical(other.repliedPost, repliedPost) || other.repliedPost == repliedPost)&&(identical(other.forwardedPostId, forwardedPostId) || other.forwardedPostId == forwardedPostId)&&(identical(other.forwardedPost, forwardedPost) || other.forwardedPost == forwardedPost)&&const DeepCollectionEquality().equals(other.attachments, attachments)&&(identical(other.publisher, publisher) || other.publisher == publisher)&&const DeepCollectionEquality().equals(other.reactionsCount, reactionsCount)&&const DeepCollectionEquality().equals(other.reactions, reactions)&&const DeepCollectionEquality().equals(other.tags, tags)&&const DeepCollectionEquality().equals(other.categories, categories)&&const DeepCollectionEquality().equals(other.collections, collections)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt)&&(identical(other.isTruncated, isTruncated) || other.isTruncated == isTruncated));
} }
@JsonKey(includeFromJson: false, includeToJson: false) @JsonKey(includeFromJson: false, includeToJson: false)
@override @override
int get hashCode => Object.hashAll([runtimeType,id,title,description,language,editedAt,publishedAt,visibility,content,type,const DeepCollectionEquality().hash(meta),viewsUnique,viewsTotal,upvotes,downvotes,repliesCount,threadedPostId,threadedPost,repliedPostId,repliedPost,forwardedPostId,forwardedPost,const DeepCollectionEquality().hash(attachments),publisher,const DeepCollectionEquality().hash(reactionsCount),const DeepCollectionEquality().hash(reactions),const DeepCollectionEquality().hash(tags),const DeepCollectionEquality().hash(categories),const DeepCollectionEquality().hash(collections),createdAt,updatedAt,deletedAt]); int get hashCode => Object.hashAll([runtimeType,id,title,description,language,editedAt,publishedAt,visibility,content,type,const DeepCollectionEquality().hash(meta),viewsUnique,viewsTotal,upvotes,downvotes,repliesCount,threadedPostId,threadedPost,repliedPostId,repliedPost,forwardedPostId,forwardedPost,const DeepCollectionEquality().hash(attachments),publisher,const DeepCollectionEquality().hash(reactionsCount),const DeepCollectionEquality().hash(reactions),const DeepCollectionEquality().hash(tags),const DeepCollectionEquality().hash(categories),const DeepCollectionEquality().hash(collections),createdAt,updatedAt,deletedAt,isTruncated]);
@override @override
String toString() { String toString() {
return 'SnPost(id: $id, title: $title, description: $description, language: $language, editedAt: $editedAt, publishedAt: $publishedAt, visibility: $visibility, content: $content, type: $type, meta: $meta, viewsUnique: $viewsUnique, viewsTotal: $viewsTotal, upvotes: $upvotes, downvotes: $downvotes, repliesCount: $repliesCount, threadedPostId: $threadedPostId, threadedPost: $threadedPost, repliedPostId: $repliedPostId, repliedPost: $repliedPost, forwardedPostId: $forwardedPostId, forwardedPost: $forwardedPost, attachments: $attachments, publisher: $publisher, reactionsCount: $reactionsCount, reactions: $reactions, tags: $tags, categories: $categories, collections: $collections, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt)'; return 'SnPost(id: $id, title: $title, description: $description, language: $language, editedAt: $editedAt, publishedAt: $publishedAt, visibility: $visibility, content: $content, type: $type, meta: $meta, viewsUnique: $viewsUnique, viewsTotal: $viewsTotal, upvotes: $upvotes, downvotes: $downvotes, repliesCount: $repliesCount, threadedPostId: $threadedPostId, threadedPost: $threadedPost, repliedPostId: $repliedPostId, repliedPost: $repliedPost, forwardedPostId: $forwardedPostId, forwardedPost: $forwardedPost, attachments: $attachments, publisher: $publisher, reactionsCount: $reactionsCount, reactions: $reactions, tags: $tags, categories: $categories, collections: $collections, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt, isTruncated: $isTruncated)';
} }
@ -49,7 +49,7 @@ abstract mixin class $SnPostCopyWith<$Res> {
factory $SnPostCopyWith(SnPost value, $Res Function(SnPost) _then) = _$SnPostCopyWithImpl; factory $SnPostCopyWith(SnPost value, $Res Function(SnPost) _then) = _$SnPostCopyWithImpl;
@useResult @useResult
$Res call({ $Res call({
String id, String? title, String? description, String? language, DateTime? editedAt, DateTime publishedAt, int visibility, String? content, int type, Map<String, dynamic>? meta, int viewsUnique, int viewsTotal, int upvotes, int downvotes, int repliesCount, String? threadedPostId, SnPost? threadedPost, String? repliedPostId, SnPost? repliedPost, String? forwardedPostId, SnPost? forwardedPost, List<SnCloudFile> attachments, SnPublisher publisher, Map<String, int> reactionsCount, List<dynamic> reactions, List<dynamic> tags, List<dynamic> categories, List<dynamic> collections, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt String id, String? title, String? description, String? language, DateTime? editedAt, DateTime publishedAt, int visibility, String? content, int type, Map<String, dynamic>? meta, int viewsUnique, int viewsTotal, int upvotes, int downvotes, int repliesCount, String? threadedPostId, SnPost? threadedPost, String? repliedPostId, SnPost? repliedPost, String? forwardedPostId, SnPost? forwardedPost, List<SnCloudFile> attachments, SnPublisher publisher, Map<String, int> reactionsCount, List<dynamic> reactions, List<dynamic> tags, List<dynamic> categories, List<dynamic> collections, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt, bool isTruncated
}); });
@ -66,7 +66,7 @@ class _$SnPostCopyWithImpl<$Res>
/// Create a copy of SnPost /// Create a copy of SnPost
/// with the given fields replaced by the non-null parameter values. /// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? title = freezed,Object? description = freezed,Object? language = freezed,Object? editedAt = freezed,Object? publishedAt = null,Object? visibility = null,Object? content = freezed,Object? type = null,Object? meta = freezed,Object? viewsUnique = null,Object? viewsTotal = null,Object? upvotes = null,Object? downvotes = null,Object? repliesCount = null,Object? threadedPostId = freezed,Object? threadedPost = freezed,Object? repliedPostId = freezed,Object? repliedPost = freezed,Object? forwardedPostId = freezed,Object? forwardedPost = freezed,Object? attachments = null,Object? publisher = null,Object? reactionsCount = null,Object? reactions = null,Object? tags = null,Object? categories = null,Object? collections = null,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,}) { @pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? title = freezed,Object? description = freezed,Object? language = freezed,Object? editedAt = freezed,Object? publishedAt = null,Object? visibility = null,Object? content = freezed,Object? type = null,Object? meta = freezed,Object? viewsUnique = null,Object? viewsTotal = null,Object? upvotes = null,Object? downvotes = null,Object? repliesCount = null,Object? threadedPostId = freezed,Object? threadedPost = freezed,Object? repliedPostId = freezed,Object? repliedPost = freezed,Object? forwardedPostId = freezed,Object? forwardedPost = freezed,Object? attachments = null,Object? publisher = null,Object? reactionsCount = null,Object? reactions = null,Object? tags = null,Object? categories = null,Object? collections = null,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,Object? isTruncated = null,}) {
return _then(_self.copyWith( return _then(_self.copyWith(
id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
as String,title: freezed == title ? _self.title : title // ignore: cast_nullable_to_non_nullable as String,title: freezed == title ? _self.title : title // ignore: cast_nullable_to_non_nullable
@ -99,7 +99,8 @@ as List<dynamic>,collections: null == collections ? _self.collections : collecti
as List<dynamic>,createdAt: null == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable as List<dynamic>,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,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,deletedAt: freezed == deletedAt ? _self.deletedAt : deletedAt // ignore: cast_nullable_to_non_nullable
as DateTime?, as DateTime?,isTruncated: null == isTruncated ? _self.isTruncated : isTruncated // ignore: cast_nullable_to_non_nullable
as bool,
)); ));
} }
/// Create a copy of SnPost /// Create a copy of SnPost
@ -155,7 +156,7 @@ $SnPublisherCopyWith<$Res> get publisher {
@JsonSerializable() @JsonSerializable()
class _SnPost implements SnPost { class _SnPost implements SnPost {
const _SnPost({required this.id, required this.title, required this.description, required this.language, required this.editedAt, required this.publishedAt, required this.visibility, required this.content, required this.type, required final Map<String, dynamic>? meta, required this.viewsUnique, required this.viewsTotal, required this.upvotes, required this.downvotes, required this.repliesCount, required this.threadedPostId, required this.threadedPost, required this.repliedPostId, required this.repliedPost, required this.forwardedPostId, required this.forwardedPost, required final List<SnCloudFile> attachments, required this.publisher, final Map<String, int> reactionsCount = const {}, required final List<dynamic> reactions, required final List<dynamic> tags, required final List<dynamic> categories, required final List<dynamic> collections, required this.createdAt, required this.updatedAt, required this.deletedAt}): _meta = meta,_attachments = attachments,_reactionsCount = reactionsCount,_reactions = reactions,_tags = tags,_categories = categories,_collections = collections; const _SnPost({required this.id, required this.title, required this.description, required this.language, required this.editedAt, required this.publishedAt, required this.visibility, required this.content, required this.type, required final Map<String, dynamic>? meta, required this.viewsUnique, required this.viewsTotal, required this.upvotes, required this.downvotes, required this.repliesCount, required this.threadedPostId, required this.threadedPost, required this.repliedPostId, required this.repliedPost, required this.forwardedPostId, required this.forwardedPost, required final List<SnCloudFile> attachments, required this.publisher, final Map<String, int> reactionsCount = const {}, required final List<dynamic> reactions, required final List<dynamic> tags, required final List<dynamic> categories, required final List<dynamic> collections, required this.createdAt, required this.updatedAt, required this.deletedAt, this.isTruncated = false}): _meta = meta,_attachments = attachments,_reactionsCount = reactionsCount,_reactions = reactions,_tags = tags,_categories = categories,_collections = collections;
factory _SnPost.fromJson(Map<String, dynamic> json) => _$SnPostFromJson(json); factory _SnPost.fromJson(Map<String, dynamic> json) => _$SnPostFromJson(json);
@override final String id; @override final String id;
@ -233,6 +234,7 @@ class _SnPost implements SnPost {
@override final DateTime createdAt; @override final DateTime createdAt;
@override final DateTime updatedAt; @override final DateTime updatedAt;
@override final DateTime? deletedAt; @override final DateTime? deletedAt;
@override@JsonKey() final bool isTruncated;
/// Create a copy of SnPost /// Create a copy of SnPost
/// with the given fields replaced by the non-null parameter values. /// with the given fields replaced by the non-null parameter values.
@ -247,16 +249,16 @@ Map<String, dynamic> toJson() {
@override @override
bool operator ==(Object other) { bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is _SnPost&&(identical(other.id, id) || other.id == id)&&(identical(other.title, title) || other.title == title)&&(identical(other.description, description) || other.description == description)&&(identical(other.language, language) || other.language == language)&&(identical(other.editedAt, editedAt) || other.editedAt == editedAt)&&(identical(other.publishedAt, publishedAt) || other.publishedAt == publishedAt)&&(identical(other.visibility, visibility) || other.visibility == visibility)&&(identical(other.content, content) || other.content == content)&&(identical(other.type, type) || other.type == type)&&const DeepCollectionEquality().equals(other._meta, _meta)&&(identical(other.viewsUnique, viewsUnique) || other.viewsUnique == viewsUnique)&&(identical(other.viewsTotal, viewsTotal) || other.viewsTotal == viewsTotal)&&(identical(other.upvotes, upvotes) || other.upvotes == upvotes)&&(identical(other.downvotes, downvotes) || other.downvotes == downvotes)&&(identical(other.repliesCount, repliesCount) || other.repliesCount == repliesCount)&&(identical(other.threadedPostId, threadedPostId) || other.threadedPostId == threadedPostId)&&(identical(other.threadedPost, threadedPost) || other.threadedPost == threadedPost)&&(identical(other.repliedPostId, repliedPostId) || other.repliedPostId == repliedPostId)&&(identical(other.repliedPost, repliedPost) || other.repliedPost == repliedPost)&&(identical(other.forwardedPostId, forwardedPostId) || other.forwardedPostId == forwardedPostId)&&(identical(other.forwardedPost, forwardedPost) || other.forwardedPost == forwardedPost)&&const DeepCollectionEquality().equals(other._attachments, _attachments)&&(identical(other.publisher, publisher) || other.publisher == publisher)&&const DeepCollectionEquality().equals(other._reactionsCount, _reactionsCount)&&const DeepCollectionEquality().equals(other._reactions, _reactions)&&const DeepCollectionEquality().equals(other._tags, _tags)&&const DeepCollectionEquality().equals(other._categories, _categories)&&const DeepCollectionEquality().equals(other._collections, _collections)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt)); return identical(this, other) || (other.runtimeType == runtimeType&&other is _SnPost&&(identical(other.id, id) || other.id == id)&&(identical(other.title, title) || other.title == title)&&(identical(other.description, description) || other.description == description)&&(identical(other.language, language) || other.language == language)&&(identical(other.editedAt, editedAt) || other.editedAt == editedAt)&&(identical(other.publishedAt, publishedAt) || other.publishedAt == publishedAt)&&(identical(other.visibility, visibility) || other.visibility == visibility)&&(identical(other.content, content) || other.content == content)&&(identical(other.type, type) || other.type == type)&&const DeepCollectionEquality().equals(other._meta, _meta)&&(identical(other.viewsUnique, viewsUnique) || other.viewsUnique == viewsUnique)&&(identical(other.viewsTotal, viewsTotal) || other.viewsTotal == viewsTotal)&&(identical(other.upvotes, upvotes) || other.upvotes == upvotes)&&(identical(other.downvotes, downvotes) || other.downvotes == downvotes)&&(identical(other.repliesCount, repliesCount) || other.repliesCount == repliesCount)&&(identical(other.threadedPostId, threadedPostId) || other.threadedPostId == threadedPostId)&&(identical(other.threadedPost, threadedPost) || other.threadedPost == threadedPost)&&(identical(other.repliedPostId, repliedPostId) || other.repliedPostId == repliedPostId)&&(identical(other.repliedPost, repliedPost) || other.repliedPost == repliedPost)&&(identical(other.forwardedPostId, forwardedPostId) || other.forwardedPostId == forwardedPostId)&&(identical(other.forwardedPost, forwardedPost) || other.forwardedPost == forwardedPost)&&const DeepCollectionEquality().equals(other._attachments, _attachments)&&(identical(other.publisher, publisher) || other.publisher == publisher)&&const DeepCollectionEquality().equals(other._reactionsCount, _reactionsCount)&&const DeepCollectionEquality().equals(other._reactions, _reactions)&&const DeepCollectionEquality().equals(other._tags, _tags)&&const DeepCollectionEquality().equals(other._categories, _categories)&&const DeepCollectionEquality().equals(other._collections, _collections)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt)&&(identical(other.isTruncated, isTruncated) || other.isTruncated == isTruncated));
} }
@JsonKey(includeFromJson: false, includeToJson: false) @JsonKey(includeFromJson: false, includeToJson: false)
@override @override
int get hashCode => Object.hashAll([runtimeType,id,title,description,language,editedAt,publishedAt,visibility,content,type,const DeepCollectionEquality().hash(_meta),viewsUnique,viewsTotal,upvotes,downvotes,repliesCount,threadedPostId,threadedPost,repliedPostId,repliedPost,forwardedPostId,forwardedPost,const DeepCollectionEquality().hash(_attachments),publisher,const DeepCollectionEquality().hash(_reactionsCount),const DeepCollectionEquality().hash(_reactions),const DeepCollectionEquality().hash(_tags),const DeepCollectionEquality().hash(_categories),const DeepCollectionEquality().hash(_collections),createdAt,updatedAt,deletedAt]); int get hashCode => Object.hashAll([runtimeType,id,title,description,language,editedAt,publishedAt,visibility,content,type,const DeepCollectionEquality().hash(_meta),viewsUnique,viewsTotal,upvotes,downvotes,repliesCount,threadedPostId,threadedPost,repliedPostId,repliedPost,forwardedPostId,forwardedPost,const DeepCollectionEquality().hash(_attachments),publisher,const DeepCollectionEquality().hash(_reactionsCount),const DeepCollectionEquality().hash(_reactions),const DeepCollectionEquality().hash(_tags),const DeepCollectionEquality().hash(_categories),const DeepCollectionEquality().hash(_collections),createdAt,updatedAt,deletedAt,isTruncated]);
@override @override
String toString() { String toString() {
return 'SnPost(id: $id, title: $title, description: $description, language: $language, editedAt: $editedAt, publishedAt: $publishedAt, visibility: $visibility, content: $content, type: $type, meta: $meta, viewsUnique: $viewsUnique, viewsTotal: $viewsTotal, upvotes: $upvotes, downvotes: $downvotes, repliesCount: $repliesCount, threadedPostId: $threadedPostId, threadedPost: $threadedPost, repliedPostId: $repliedPostId, repliedPost: $repliedPost, forwardedPostId: $forwardedPostId, forwardedPost: $forwardedPost, attachments: $attachments, publisher: $publisher, reactionsCount: $reactionsCount, reactions: $reactions, tags: $tags, categories: $categories, collections: $collections, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt)'; return 'SnPost(id: $id, title: $title, description: $description, language: $language, editedAt: $editedAt, publishedAt: $publishedAt, visibility: $visibility, content: $content, type: $type, meta: $meta, viewsUnique: $viewsUnique, viewsTotal: $viewsTotal, upvotes: $upvotes, downvotes: $downvotes, repliesCount: $repliesCount, threadedPostId: $threadedPostId, threadedPost: $threadedPost, repliedPostId: $repliedPostId, repliedPost: $repliedPost, forwardedPostId: $forwardedPostId, forwardedPost: $forwardedPost, attachments: $attachments, publisher: $publisher, reactionsCount: $reactionsCount, reactions: $reactions, tags: $tags, categories: $categories, collections: $collections, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt, isTruncated: $isTruncated)';
} }
@ -267,7 +269,7 @@ abstract mixin class _$SnPostCopyWith<$Res> implements $SnPostCopyWith<$Res> {
factory _$SnPostCopyWith(_SnPost value, $Res Function(_SnPost) _then) = __$SnPostCopyWithImpl; factory _$SnPostCopyWith(_SnPost value, $Res Function(_SnPost) _then) = __$SnPostCopyWithImpl;
@override @useResult @override @useResult
$Res call({ $Res call({
String id, String? title, String? description, String? language, DateTime? editedAt, DateTime publishedAt, int visibility, String? content, int type, Map<String, dynamic>? meta, int viewsUnique, int viewsTotal, int upvotes, int downvotes, int repliesCount, String? threadedPostId, SnPost? threadedPost, String? repliedPostId, SnPost? repliedPost, String? forwardedPostId, SnPost? forwardedPost, List<SnCloudFile> attachments, SnPublisher publisher, Map<String, int> reactionsCount, List<dynamic> reactions, List<dynamic> tags, List<dynamic> categories, List<dynamic> collections, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt String id, String? title, String? description, String? language, DateTime? editedAt, DateTime publishedAt, int visibility, String? content, int type, Map<String, dynamic>? meta, int viewsUnique, int viewsTotal, int upvotes, int downvotes, int repliesCount, String? threadedPostId, SnPost? threadedPost, String? repliedPostId, SnPost? repliedPost, String? forwardedPostId, SnPost? forwardedPost, List<SnCloudFile> attachments, SnPublisher publisher, Map<String, int> reactionsCount, List<dynamic> reactions, List<dynamic> tags, List<dynamic> categories, List<dynamic> collections, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt, bool isTruncated
}); });
@ -284,7 +286,7 @@ class __$SnPostCopyWithImpl<$Res>
/// Create a copy of SnPost /// Create a copy of SnPost
/// with the given fields replaced by the non-null parameter values. /// with the given fields replaced by the non-null parameter values.
@override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? title = freezed,Object? description = freezed,Object? language = freezed,Object? editedAt = freezed,Object? publishedAt = null,Object? visibility = null,Object? content = freezed,Object? type = null,Object? meta = freezed,Object? viewsUnique = null,Object? viewsTotal = null,Object? upvotes = null,Object? downvotes = null,Object? repliesCount = null,Object? threadedPostId = freezed,Object? threadedPost = freezed,Object? repliedPostId = freezed,Object? repliedPost = freezed,Object? forwardedPostId = freezed,Object? forwardedPost = freezed,Object? attachments = null,Object? publisher = null,Object? reactionsCount = null,Object? reactions = null,Object? tags = null,Object? categories = null,Object? collections = null,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,}) { @override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? title = freezed,Object? description = freezed,Object? language = freezed,Object? editedAt = freezed,Object? publishedAt = null,Object? visibility = null,Object? content = freezed,Object? type = null,Object? meta = freezed,Object? viewsUnique = null,Object? viewsTotal = null,Object? upvotes = null,Object? downvotes = null,Object? repliesCount = null,Object? threadedPostId = freezed,Object? threadedPost = freezed,Object? repliedPostId = freezed,Object? repliedPost = freezed,Object? forwardedPostId = freezed,Object? forwardedPost = freezed,Object? attachments = null,Object? publisher = null,Object? reactionsCount = null,Object? reactions = null,Object? tags = null,Object? categories = null,Object? collections = null,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,Object? isTruncated = null,}) {
return _then(_SnPost( return _then(_SnPost(
id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
as String,title: freezed == title ? _self.title : title // ignore: cast_nullable_to_non_nullable as String,title: freezed == title ? _self.title : title // ignore: cast_nullable_to_non_nullable
@ -317,7 +319,8 @@ as List<dynamic>,collections: null == collections ? _self._collections : collect
as List<dynamic>,createdAt: null == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable as List<dynamic>,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,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,deletedAt: freezed == deletedAt ? _self.deletedAt : deletedAt // ignore: cast_nullable_to_non_nullable
as DateTime?, as DateTime?,isTruncated: null == isTruncated ? _self.isTruncated : isTruncated // ignore: cast_nullable_to_non_nullable
as bool,
)); ));
} }
@ -786,7 +789,7 @@ as int,
/// @nodoc /// @nodoc
mixin _$SnSubscriptionStatus { mixin _$SnSubscriptionStatus {
bool get isSubscribed; int get publisherId; String get publisherName; bool get isSubscribed; String get publisherId; String get publisherName;
/// Create a copy of SnSubscriptionStatus /// Create a copy of SnSubscriptionStatus
/// with the given fields replaced by the non-null parameter values. /// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false) @JsonKey(includeFromJson: false, includeToJson: false)
@ -819,7 +822,7 @@ abstract mixin class $SnSubscriptionStatusCopyWith<$Res> {
factory $SnSubscriptionStatusCopyWith(SnSubscriptionStatus value, $Res Function(SnSubscriptionStatus) _then) = _$SnSubscriptionStatusCopyWithImpl; factory $SnSubscriptionStatusCopyWith(SnSubscriptionStatus value, $Res Function(SnSubscriptionStatus) _then) = _$SnSubscriptionStatusCopyWithImpl;
@useResult @useResult
$Res call({ $Res call({
bool isSubscribed, int publisherId, String publisherName bool isSubscribed, String publisherId, String publisherName
}); });
@ -840,7 +843,7 @@ class _$SnSubscriptionStatusCopyWithImpl<$Res>
return _then(_self.copyWith( return _then(_self.copyWith(
isSubscribed: null == isSubscribed ? _self.isSubscribed : isSubscribed // ignore: cast_nullable_to_non_nullable isSubscribed: null == isSubscribed ? _self.isSubscribed : isSubscribed // ignore: cast_nullable_to_non_nullable
as bool,publisherId: null == publisherId ? _self.publisherId : publisherId // ignore: cast_nullable_to_non_nullable as bool,publisherId: null == publisherId ? _self.publisherId : publisherId // ignore: cast_nullable_to_non_nullable
as int,publisherName: null == publisherName ? _self.publisherName : publisherName // ignore: cast_nullable_to_non_nullable as String,publisherName: null == publisherName ? _self.publisherName : publisherName // ignore: cast_nullable_to_non_nullable
as String, as String,
)); ));
} }
@ -856,7 +859,7 @@ class _SnSubscriptionStatus implements SnSubscriptionStatus {
factory _SnSubscriptionStatus.fromJson(Map<String, dynamic> json) => _$SnSubscriptionStatusFromJson(json); factory _SnSubscriptionStatus.fromJson(Map<String, dynamic> json) => _$SnSubscriptionStatusFromJson(json);
@override final bool isSubscribed; @override final bool isSubscribed;
@override final int publisherId; @override final String publisherId;
@override final String publisherName; @override final String publisherName;
/// Create a copy of SnSubscriptionStatus /// Create a copy of SnSubscriptionStatus
@ -892,7 +895,7 @@ abstract mixin class _$SnSubscriptionStatusCopyWith<$Res> implements $SnSubscrip
factory _$SnSubscriptionStatusCopyWith(_SnSubscriptionStatus value, $Res Function(_SnSubscriptionStatus) _then) = __$SnSubscriptionStatusCopyWithImpl; factory _$SnSubscriptionStatusCopyWith(_SnSubscriptionStatus value, $Res Function(_SnSubscriptionStatus) _then) = __$SnSubscriptionStatusCopyWithImpl;
@override @useResult @override @useResult
$Res call({ $Res call({
bool isSubscribed, int publisherId, String publisherName bool isSubscribed, String publisherId, String publisherName
}); });
@ -913,7 +916,7 @@ class __$SnSubscriptionStatusCopyWithImpl<$Res>
return _then(_SnSubscriptionStatus( return _then(_SnSubscriptionStatus(
isSubscribed: null == isSubscribed ? _self.isSubscribed : isSubscribed // ignore: cast_nullable_to_non_nullable isSubscribed: null == isSubscribed ? _self.isSubscribed : isSubscribed // ignore: cast_nullable_to_non_nullable
as bool,publisherId: null == publisherId ? _self.publisherId : publisherId // ignore: cast_nullable_to_non_nullable as bool,publisherId: null == publisherId ? _self.publisherId : publisherId // ignore: cast_nullable_to_non_nullable
as int,publisherName: null == publisherName ? _self.publisherName : publisherName // ignore: cast_nullable_to_non_nullable as String,publisherName: null == publisherName ? _self.publisherName : publisherName // ignore: cast_nullable_to_non_nullable
as String, as String,
)); ));
} }

View File

@ -60,6 +60,7 @@ _SnPost _$SnPostFromJson(Map<String, dynamic> json) => _SnPost(
json['deleted_at'] == null json['deleted_at'] == null
? null ? null
: DateTime.parse(json['deleted_at'] as String), : DateTime.parse(json['deleted_at'] as String),
isTruncated: json['is_truncated'] as bool? ?? false,
); );
Map<String, dynamic> _$SnPostToJson(_SnPost instance) => <String, dynamic>{ Map<String, dynamic> _$SnPostToJson(_SnPost instance) => <String, dynamic>{
@ -94,6 +95,7 @@ Map<String, dynamic> _$SnPostToJson(_SnPost instance) => <String, dynamic>{
'created_at': instance.createdAt.toIso8601String(), 'created_at': instance.createdAt.toIso8601String(),
'updated_at': instance.updatedAt.toIso8601String(), 'updated_at': instance.updatedAt.toIso8601String(),
'deleted_at': instance.deletedAt?.toIso8601String(), 'deleted_at': instance.deletedAt?.toIso8601String(),
'is_truncated': instance.isTruncated,
}; };
_SnPublisher _$SnPublisherFromJson(Map<String, dynamic> json) => _SnPublisher( _SnPublisher _$SnPublisherFromJson(Map<String, dynamic> json) => _SnPublisher(
@ -170,7 +172,7 @@ _SnSubscriptionStatus _$SnSubscriptionStatusFromJson(
Map<String, dynamic> json, Map<String, dynamic> json,
) => _SnSubscriptionStatus( ) => _SnSubscriptionStatus(
isSubscribed: json['is_subscribed'] as bool, isSubscribed: json['is_subscribed'] as bool,
publisherId: (json['publisher_id'] as num).toInt(), publisherId: json['publisher_id'] as String,
publisherName: json['publisher_name'] as String, publisherName: json['publisher_name'] as String,
); );

View File

@ -1,5 +1,6 @@
import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:island/models/file.dart'; import 'package:island/models/file.dart';
import 'package:island/models/wallet.dart';
part 'user.freezed.dart'; part 'user.freezed.dart';
part 'user.g.dart'; part 'user.g.dart';
@ -44,6 +45,7 @@ sealed class SnAccountProfile with _$SnAccountProfile {
required SnCloudFile? picture, required SnCloudFile? picture,
required SnCloudFile? background, required SnCloudFile? background,
required SnVerificationMark? verification, required SnVerificationMark? verification,
required SnWalletSubscriptionRef? stellarMembership,
required DateTime createdAt, required DateTime createdAt,
required DateTime updatedAt, required DateTime updatedAt,
required DateTime? deletedAt, required DateTime? deletedAt,

View File

@ -200,7 +200,7 @@ $SnAccountProfileCopyWith<$Res> get profile {
/// @nodoc /// @nodoc
mixin _$SnAccountProfile { 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; 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 /// Create a copy of SnAccountProfile
/// with the given fields replaced by the non-null parameter values. /// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false) @JsonKey(includeFromJson: false, includeToJson: false)
@ -213,16 +213,16 @@ $SnAccountProfileCopyWith<SnAccountProfile> get copyWith => _$SnAccountProfileCo
@override @override
bool operator ==(Object other) { bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is SnAccountProfile&&(identical(other.id, id) || other.id == id)&&(identical(other.firstName, firstName) || other.firstName == firstName)&&(identical(other.middleName, middleName) || other.middleName == middleName)&&(identical(other.lastName, lastName) || other.lastName == lastName)&&(identical(other.bio, bio) || other.bio == bio)&&(identical(other.gender, gender) || other.gender == gender)&&(identical(other.pronouns, pronouns) || other.pronouns == pronouns)&&(identical(other.location, location) || other.location == location)&&(identical(other.timeZone, timeZone) || other.timeZone == timeZone)&&(identical(other.birthday, birthday) || other.birthday == birthday)&&(identical(other.lastSeenAt, lastSeenAt) || other.lastSeenAt == lastSeenAt)&&(identical(other.activeBadge, activeBadge) || other.activeBadge == activeBadge)&&(identical(other.experience, experience) || other.experience == experience)&&(identical(other.level, level) || other.level == level)&&(identical(other.levelingProgress, levelingProgress) || other.levelingProgress == levelingProgress)&&(identical(other.picture, picture) || other.picture == picture)&&(identical(other.background, background) || other.background == background)&&(identical(other.verification, verification) || other.verification == verification)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt)); return identical(this, other) || (other.runtimeType == runtimeType&&other is SnAccountProfile&&(identical(other.id, id) || other.id == id)&&(identical(other.firstName, firstName) || other.firstName == firstName)&&(identical(other.middleName, middleName) || other.middleName == middleName)&&(identical(other.lastName, lastName) || other.lastName == lastName)&&(identical(other.bio, bio) || other.bio == bio)&&(identical(other.gender, gender) || other.gender == gender)&&(identical(other.pronouns, pronouns) || other.pronouns == pronouns)&&(identical(other.location, location) || other.location == location)&&(identical(other.timeZone, timeZone) || other.timeZone == timeZone)&&(identical(other.birthday, birthday) || other.birthday == birthday)&&(identical(other.lastSeenAt, lastSeenAt) || other.lastSeenAt == lastSeenAt)&&(identical(other.activeBadge, activeBadge) || other.activeBadge == activeBadge)&&(identical(other.experience, experience) || other.experience == experience)&&(identical(other.level, level) || other.level == level)&&(identical(other.levelingProgress, levelingProgress) || other.levelingProgress == levelingProgress)&&(identical(other.picture, picture) || other.picture == picture)&&(identical(other.background, background) || other.background == background)&&(identical(other.verification, verification) || other.verification == verification)&&(identical(other.stellarMembership, stellarMembership) || other.stellarMembership == stellarMembership)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt));
} }
@JsonKey(includeFromJson: false, includeToJson: false) @JsonKey(includeFromJson: false, includeToJson: false)
@override @override
int get hashCode => Object.hashAll([runtimeType,id,firstName,middleName,lastName,bio,gender,pronouns,location,timeZone,birthday,lastSeenAt,activeBadge,experience,level,levelingProgress,picture,background,verification,createdAt,updatedAt,deletedAt]); int get hashCode => Object.hashAll([runtimeType,id,firstName,middleName,lastName,bio,gender,pronouns,location,timeZone,birthday,lastSeenAt,activeBadge,experience,level,levelingProgress,picture,background,verification,stellarMembership,createdAt,updatedAt,deletedAt]);
@override @override
String toString() { String toString() {
return 'SnAccountProfile(id: $id, firstName: $firstName, middleName: $middleName, lastName: $lastName, bio: $bio, gender: $gender, pronouns: $pronouns, location: $location, timeZone: $timeZone, birthday: $birthday, lastSeenAt: $lastSeenAt, activeBadge: $activeBadge, experience: $experience, level: $level, levelingProgress: $levelingProgress, picture: $picture, background: $background, verification: $verification, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt)'; return 'SnAccountProfile(id: $id, firstName: $firstName, middleName: $middleName, lastName: $lastName, bio: $bio, gender: $gender, pronouns: $pronouns, location: $location, timeZone: $timeZone, birthday: $birthday, lastSeenAt: $lastSeenAt, activeBadge: $activeBadge, experience: $experience, level: $level, levelingProgress: $levelingProgress, picture: $picture, background: $background, verification: $verification, stellarMembership: $stellarMembership, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt)';
} }
@ -233,11 +233,11 @@ abstract mixin class $SnAccountProfileCopyWith<$Res> {
factory $SnAccountProfileCopyWith(SnAccountProfile value, $Res Function(SnAccountProfile) _then) = _$SnAccountProfileCopyWithImpl; factory $SnAccountProfileCopyWith(SnAccountProfile value, $Res Function(SnAccountProfile) _then) = _$SnAccountProfileCopyWithImpl;
@useResult @useResult
$Res call({ $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, 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; $SnAccountBadgeCopyWith<$Res>? get activeBadge;$SnCloudFileCopyWith<$Res>? get picture;$SnCloudFileCopyWith<$Res>? get background;$SnVerificationMarkCopyWith<$Res>? get verification;$SnWalletSubscriptionRefCopyWith<$Res>? get stellarMembership;
} }
/// @nodoc /// @nodoc
@ -250,7 +250,7 @@ class _$SnAccountProfileCopyWithImpl<$Res>
/// Create a copy of SnAccountProfile /// Create a copy of SnAccountProfile
/// with the given fields replaced by the non-null parameter values. /// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? firstName = null,Object? middleName = null,Object? lastName = null,Object? bio = null,Object? gender = null,Object? pronouns = null,Object? location = null,Object? timeZone = null,Object? birthday = freezed,Object? lastSeenAt = freezed,Object? activeBadge = freezed,Object? experience = null,Object? level = null,Object? levelingProgress = null,Object? picture = freezed,Object? background = freezed,Object? verification = freezed,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,}) { @pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? firstName = null,Object? middleName = null,Object? lastName = null,Object? bio = null,Object? gender = null,Object? pronouns = null,Object? location = null,Object? timeZone = null,Object? birthday = freezed,Object? lastSeenAt = freezed,Object? activeBadge = freezed,Object? experience = null,Object? level = null,Object? levelingProgress = null,Object? picture = freezed,Object? background = freezed,Object? verification = freezed,Object? stellarMembership = freezed,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,}) {
return _then(_self.copyWith( return _then(_self.copyWith(
id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
as String,firstName: null == firstName ? _self.firstName : firstName // ignore: cast_nullable_to_non_nullable as String,firstName: null == firstName ? _self.firstName : firstName // ignore: cast_nullable_to_non_nullable
@ -270,7 +270,8 @@ as int,levelingProgress: null == levelingProgress ? _self.levelingProgress : lev
as double,picture: freezed == picture ? _self.picture : picture // ignore: cast_nullable_to_non_nullable as double,picture: freezed == picture ? _self.picture : picture // ignore: cast_nullable_to_non_nullable
as SnCloudFile?,background: freezed == background ? _self.background : background // ignore: cast_nullable_to_non_nullable 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 SnCloudFile?,verification: freezed == verification ? _self.verification : verification // ignore: cast_nullable_to_non_nullable
as SnVerificationMark?,createdAt: null == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable as SnVerificationMark?,stellarMembership: freezed == stellarMembership ? _self.stellarMembership : stellarMembership // 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,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,deletedAt: freezed == deletedAt ? _self.deletedAt : deletedAt // ignore: cast_nullable_to_non_nullable
as DateTime?, as DateTime?,
@ -324,6 +325,18 @@ $SnVerificationMarkCopyWith<$Res>? get verification {
return $SnVerificationMarkCopyWith<$Res>(_self.verification!, (value) { return $SnVerificationMarkCopyWith<$Res>(_self.verification!, (value) {
return _then(_self.copyWith(verification: value)); return _then(_self.copyWith(verification: value));
}); });
}/// Create a copy of SnAccountProfile
/// with the given fields replaced by the non-null parameter values.
@override
@pragma('vm:prefer-inline')
$SnWalletSubscriptionRefCopyWith<$Res>? get stellarMembership {
if (_self.stellarMembership == null) {
return null;
}
return $SnWalletSubscriptionRefCopyWith<$Res>(_self.stellarMembership!, (value) {
return _then(_self.copyWith(stellarMembership: value));
});
} }
} }
@ -332,7 +345,7 @@ $SnVerificationMarkCopyWith<$Res>? get verification {
@JsonSerializable() @JsonSerializable()
class _SnAccountProfile implements SnAccountProfile { class _SnAccountProfile implements SnAccountProfile {
const _SnAccountProfile({required this.id, this.firstName = '', this.middleName = '', this.lastName = '', this.bio = '', this.gender = '', this.pronouns = '', this.location = '', this.timeZone = '', this.birthday, this.lastSeenAt, this.activeBadge, required this.experience, required this.level, required this.levelingProgress, required this.picture, required this.background, required this.verification, required this.createdAt, required this.updatedAt, required this.deletedAt}); const _SnAccountProfile({required this.id, this.firstName = '', this.middleName = '', this.lastName = '', this.bio = '', this.gender = '', this.pronouns = '', this.location = '', this.timeZone = '', this.birthday, this.lastSeenAt, this.activeBadge, required this.experience, required this.level, required this.levelingProgress, required this.picture, required this.background, required this.verification, required this.stellarMembership, required this.createdAt, required this.updatedAt, required this.deletedAt});
factory _SnAccountProfile.fromJson(Map<String, dynamic> json) => _$SnAccountProfileFromJson(json); factory _SnAccountProfile.fromJson(Map<String, dynamic> json) => _$SnAccountProfileFromJson(json);
@override final String id; @override final String id;
@ -353,6 +366,7 @@ class _SnAccountProfile implements SnAccountProfile {
@override final SnCloudFile? picture; @override final SnCloudFile? picture;
@override final SnCloudFile? background; @override final SnCloudFile? background;
@override final SnVerificationMark? verification; @override final SnVerificationMark? verification;
@override final SnWalletSubscriptionRef? stellarMembership;
@override final DateTime createdAt; @override final DateTime createdAt;
@override final DateTime updatedAt; @override final DateTime updatedAt;
@override final DateTime? deletedAt; @override final DateTime? deletedAt;
@ -370,16 +384,16 @@ Map<String, dynamic> toJson() {
@override @override
bool operator ==(Object other) { bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is _SnAccountProfile&&(identical(other.id, id) || other.id == id)&&(identical(other.firstName, firstName) || other.firstName == firstName)&&(identical(other.middleName, middleName) || other.middleName == middleName)&&(identical(other.lastName, lastName) || other.lastName == lastName)&&(identical(other.bio, bio) || other.bio == bio)&&(identical(other.gender, gender) || other.gender == gender)&&(identical(other.pronouns, pronouns) || other.pronouns == pronouns)&&(identical(other.location, location) || other.location == location)&&(identical(other.timeZone, timeZone) || other.timeZone == timeZone)&&(identical(other.birthday, birthday) || other.birthday == birthday)&&(identical(other.lastSeenAt, lastSeenAt) || other.lastSeenAt == lastSeenAt)&&(identical(other.activeBadge, activeBadge) || other.activeBadge == activeBadge)&&(identical(other.experience, experience) || other.experience == experience)&&(identical(other.level, level) || other.level == level)&&(identical(other.levelingProgress, levelingProgress) || other.levelingProgress == levelingProgress)&&(identical(other.picture, picture) || other.picture == picture)&&(identical(other.background, background) || other.background == background)&&(identical(other.verification, verification) || other.verification == verification)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt)); return identical(this, other) || (other.runtimeType == runtimeType&&other is _SnAccountProfile&&(identical(other.id, id) || other.id == id)&&(identical(other.firstName, firstName) || other.firstName == firstName)&&(identical(other.middleName, middleName) || other.middleName == middleName)&&(identical(other.lastName, lastName) || other.lastName == lastName)&&(identical(other.bio, bio) || other.bio == bio)&&(identical(other.gender, gender) || other.gender == gender)&&(identical(other.pronouns, pronouns) || other.pronouns == pronouns)&&(identical(other.location, location) || other.location == location)&&(identical(other.timeZone, timeZone) || other.timeZone == timeZone)&&(identical(other.birthday, birthday) || other.birthday == birthday)&&(identical(other.lastSeenAt, lastSeenAt) || other.lastSeenAt == lastSeenAt)&&(identical(other.activeBadge, activeBadge) || other.activeBadge == activeBadge)&&(identical(other.experience, experience) || other.experience == experience)&&(identical(other.level, level) || other.level == level)&&(identical(other.levelingProgress, levelingProgress) || other.levelingProgress == levelingProgress)&&(identical(other.picture, picture) || other.picture == picture)&&(identical(other.background, background) || other.background == background)&&(identical(other.verification, verification) || other.verification == verification)&&(identical(other.stellarMembership, stellarMembership) || other.stellarMembership == stellarMembership)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt));
} }
@JsonKey(includeFromJson: false, includeToJson: false) @JsonKey(includeFromJson: false, includeToJson: false)
@override @override
int get hashCode => Object.hashAll([runtimeType,id,firstName,middleName,lastName,bio,gender,pronouns,location,timeZone,birthday,lastSeenAt,activeBadge,experience,level,levelingProgress,picture,background,verification,createdAt,updatedAt,deletedAt]); int get hashCode => Object.hashAll([runtimeType,id,firstName,middleName,lastName,bio,gender,pronouns,location,timeZone,birthday,lastSeenAt,activeBadge,experience,level,levelingProgress,picture,background,verification,stellarMembership,createdAt,updatedAt,deletedAt]);
@override @override
String toString() { String toString() {
return 'SnAccountProfile(id: $id, firstName: $firstName, middleName: $middleName, lastName: $lastName, bio: $bio, gender: $gender, pronouns: $pronouns, location: $location, timeZone: $timeZone, birthday: $birthday, lastSeenAt: $lastSeenAt, activeBadge: $activeBadge, experience: $experience, level: $level, levelingProgress: $levelingProgress, picture: $picture, background: $background, verification: $verification, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt)'; return 'SnAccountProfile(id: $id, firstName: $firstName, middleName: $middleName, lastName: $lastName, bio: $bio, gender: $gender, pronouns: $pronouns, location: $location, timeZone: $timeZone, birthday: $birthday, lastSeenAt: $lastSeenAt, activeBadge: $activeBadge, experience: $experience, level: $level, levelingProgress: $levelingProgress, picture: $picture, background: $background, verification: $verification, stellarMembership: $stellarMembership, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt)';
} }
@ -390,11 +404,11 @@ abstract mixin class _$SnAccountProfileCopyWith<$Res> implements $SnAccountProfi
factory _$SnAccountProfileCopyWith(_SnAccountProfile value, $Res Function(_SnAccountProfile) _then) = __$SnAccountProfileCopyWithImpl; factory _$SnAccountProfileCopyWith(_SnAccountProfile value, $Res Function(_SnAccountProfile) _then) = __$SnAccountProfileCopyWithImpl;
@override @useResult @override @useResult
$Res call({ $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, 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 $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 /// @nodoc
@ -407,7 +421,7 @@ class __$SnAccountProfileCopyWithImpl<$Res>
/// Create a copy of SnAccountProfile /// Create a copy of SnAccountProfile
/// with the given fields replaced by the non-null parameter values. /// with the given fields replaced by the non-null parameter values.
@override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? firstName = null,Object? middleName = null,Object? lastName = null,Object? bio = null,Object? gender = null,Object? pronouns = null,Object? location = null,Object? timeZone = null,Object? birthday = freezed,Object? lastSeenAt = freezed,Object? activeBadge = freezed,Object? experience = null,Object? level = null,Object? levelingProgress = null,Object? picture = freezed,Object? background = freezed,Object? verification = freezed,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,}) { @override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? firstName = null,Object? middleName = null,Object? lastName = null,Object? bio = null,Object? gender = null,Object? pronouns = null,Object? location = null,Object? timeZone = null,Object? birthday = freezed,Object? lastSeenAt = freezed,Object? activeBadge = freezed,Object? experience = null,Object? level = null,Object? levelingProgress = null,Object? picture = freezed,Object? background = freezed,Object? verification = freezed,Object? stellarMembership = freezed,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,}) {
return _then(_SnAccountProfile( return _then(_SnAccountProfile(
id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
as String,firstName: null == firstName ? _self.firstName : firstName // ignore: cast_nullable_to_non_nullable as String,firstName: null == firstName ? _self.firstName : firstName // ignore: cast_nullable_to_non_nullable
@ -427,7 +441,8 @@ as int,levelingProgress: null == levelingProgress ? _self.levelingProgress : lev
as double,picture: freezed == picture ? _self.picture : picture // ignore: cast_nullable_to_non_nullable as double,picture: freezed == picture ? _self.picture : picture // ignore: cast_nullable_to_non_nullable
as SnCloudFile?,background: freezed == background ? _self.background : background // ignore: cast_nullable_to_non_nullable 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 SnCloudFile?,verification: freezed == verification ? _self.verification : verification // ignore: cast_nullable_to_non_nullable
as SnVerificationMark?,createdAt: null == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable as SnVerificationMark?,stellarMembership: freezed == stellarMembership ? _self.stellarMembership : stellarMembership // 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,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,deletedAt: freezed == deletedAt ? _self.deletedAt : deletedAt // ignore: cast_nullable_to_non_nullable
as DateTime?, as DateTime?,
@ -482,6 +497,18 @@ $SnVerificationMarkCopyWith<$Res>? get verification {
return $SnVerificationMarkCopyWith<$Res>(_self.verification!, (value) { return $SnVerificationMarkCopyWith<$Res>(_self.verification!, (value) {
return _then(_self.copyWith(verification: value)); return _then(_self.copyWith(verification: value));
}); });
}/// Create a copy of SnAccountProfile
/// with the given fields replaced by the non-null parameter values.
@override
@pragma('vm:prefer-inline')
$SnWalletSubscriptionRefCopyWith<$Res>? get stellarMembership {
if (_self.stellarMembership == null) {
return null;
}
return $SnWalletSubscriptionRefCopyWith<$Res>(_self.stellarMembership!, (value) {
return _then(_self.copyWith(stellarMembership: value));
});
} }
} }

View File

@ -84,6 +84,12 @@ _SnAccountProfile _$SnAccountProfileFromJson(Map<String, dynamic> json) =>
: SnVerificationMark.fromJson( : SnVerificationMark.fromJson(
json['verification'] as Map<String, dynamic>, json['verification'] as Map<String, dynamic>,
), ),
stellarMembership:
json['stellar_membership'] == null
? null
: SnWalletSubscriptionRef.fromJson(
json['stellar_membership'] as Map<String, dynamic>,
),
createdAt: DateTime.parse(json['created_at'] as String), createdAt: DateTime.parse(json['created_at'] as String),
updatedAt: DateTime.parse(json['updated_at'] as String), updatedAt: DateTime.parse(json['updated_at'] as String),
deletedAt: deletedAt:
@ -112,6 +118,7 @@ Map<String, dynamic> _$SnAccountProfileToJson(_SnAccountProfile instance) =>
'picture': instance.picture?.toJson(), 'picture': instance.picture?.toJson(),
'background': instance.background?.toJson(), 'background': instance.background?.toJson(),
'verification': instance.verification?.toJson(), 'verification': instance.verification?.toJson(),
'stellar_membership': instance.stellarMembership?.toJson(),
'created_at': instance.createdAt.toIso8601String(), 'created_at': instance.createdAt.toIso8601String(),
'updated_at': instance.updatedAt.toIso8601String(), 'updated_at': instance.updatedAt.toIso8601String(),
'deleted_at': instance.deletedAt?.toIso8601String(), 'deleted_at': instance.deletedAt?.toIso8601String(),

View File

@ -56,3 +56,74 @@ sealed class SnTransaction with _$SnTransaction {
factory SnTransaction.fromJson(Map<String, dynamic> json) => factory SnTransaction.fromJson(Map<String, dynamic> json) =>
_$SnTransactionFromJson(json); _$SnTransactionFromJson(json);
} }
@freezed
sealed class SnWalletSubscription with _$SnWalletSubscription {
const factory SnWalletSubscription({
required String id,
required DateTime begunAt,
required DateTime? endedAt,
required String identifier,
@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 String accountId,
required SnAccount? account,
@Default(true) bool isAvailable,
required double? finalPrice,
required DateTime createdAt,
required DateTime updatedAt,
required DateTime? deletedAt,
}) = _SnWalletSubscription;
factory SnWalletSubscription.fromJson(Map<String, dynamic> json) =>
_$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<String, dynamic> json) =>
_$SnWalletSubscriptionRefFromJson(json);
}
@freezed
sealed class SnWalletOrder with _$SnWalletOrder {
const factory SnWalletOrder({
required String id,
required int status,
required String currency,
required dynamic remarks,
required String appIdentifier,
@Default({}) Map<String, dynamic> meta,
required int amount,
required DateTime expiredAt,
required String? payeeWalletId,
required SnWallet? payeeWallet,
required String? transactionId,
required SnTransaction? transaction,
required String? issuerAppId,
required dynamic issuerApp,
required DateTime createdAt,
required DateTime updatedAt,
required DateTime? deletedAt,
}) = _SnWalletOrder;
factory SnWalletOrder.fromJson(Map<String, dynamic> json) =>
_$SnWalletOrderFromJson(json);
}

View File

@ -558,4 +558,612 @@ $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;
/// Create a copy of SnWalletSubscription
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
$SnWalletSubscriptionCopyWith<SnWalletSubscription> get copyWith => _$SnWalletSubscriptionCopyWithImpl<SnWalletSubscription>(this as SnWalletSubscription, _$identity);
/// Serializes this SnWalletSubscription to a JSON map.
Map<String, dynamic> toJson();
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is SnWalletSubscription&&(identical(other.id, id) || other.id == id)&&(identical(other.begunAt, begunAt) || other.begunAt == begunAt)&&(identical(other.endedAt, endedAt) || other.endedAt == endedAt)&&(identical(other.identifier, identifier) || other.identifier == identifier)&&(identical(other.isActive, isActive) || other.isActive == isActive)&&(identical(other.isFreeTrial, isFreeTrial) || other.isFreeTrial == isFreeTrial)&&(identical(other.status, status) || other.status == status)&&(identical(other.paymentMethod, paymentMethod) || other.paymentMethod == paymentMethod)&&const DeepCollectionEquality().equals(other.paymentDetails, paymentDetails)&&(identical(other.basePrice, basePrice) || other.basePrice == basePrice)&&(identical(other.couponId, couponId) || other.couponId == couponId)&&const DeepCollectionEquality().equals(other.coupon, coupon)&&(identical(other.renewalAt, renewalAt) || other.renewalAt == renewalAt)&&(identical(other.accountId, accountId) || other.accountId == accountId)&&(identical(other.account, account) || other.account == account)&&(identical(other.isAvailable, isAvailable) || other.isAvailable == isAvailable)&&(identical(other.finalPrice, finalPrice) || other.finalPrice == finalPrice)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hashAll([runtimeType,id,begunAt,endedAt,identifier,isActive,isFreeTrial,status,paymentMethod,const DeepCollectionEquality().hash(paymentDetails),basePrice,couponId,const DeepCollectionEquality().hash(coupon),renewalAt,accountId,account,isAvailable,finalPrice,createdAt,updatedAt,deletedAt]);
@override
String toString() {
return 'SnWalletSubscription(id: $id, begunAt: $begunAt, endedAt: $endedAt, identifier: $identifier, isActive: $isActive, isFreeTrial: $isFreeTrial, status: $status, paymentMethod: $paymentMethod, paymentDetails: $paymentDetails, basePrice: $basePrice, couponId: $couponId, coupon: $coupon, renewalAt: $renewalAt, accountId: $accountId, account: $account, isAvailable: $isAvailable, finalPrice: $finalPrice, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt)';
}
}
/// @nodoc
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
});
$SnAccountCopyWith<$Res>? get account;
}
/// @nodoc
class _$SnWalletSubscriptionCopyWithImpl<$Res>
implements $SnWalletSubscriptionCopyWith<$Res> {
_$SnWalletSubscriptionCopyWithImpl(this._self, this._then);
final SnWalletSubscription _self;
final $Res Function(SnWalletSubscription) _then;
/// 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 = 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: 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: 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: 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: 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?,
));
}
/// Create a copy of SnWalletSubscription
/// with the given fields replaced by the non-null parameter values.
@override
@pragma('vm:prefer-inline')
$SnAccountCopyWith<$Res>? get account {
if (_self.account == null) {
return null;
}
return $SnAccountCopyWith<$Res>(_self.account!, (value) {
return _then(_self.copyWith(account: value));
});
}
}
/// @nodoc
@JsonSerializable()
class _SnWalletSubscription implements SnWalletSubscription {
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 String identifier;
@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(value);
}
@override final double? basePrice;
@override final String? couponId;
@override final dynamic coupon;
@override final DateTime? renewalAt;
@override final String accountId;
@override final SnAccount? account;
@override@JsonKey() final bool isAvailable;
@override final double? finalPrice;
@override final DateTime createdAt;
@override final DateTime updatedAt;
@override final DateTime? deletedAt;
/// Create a copy of SnWalletSubscription
/// with the given fields replaced by the non-null parameter values.
@override @JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
_$SnWalletSubscriptionCopyWith<_SnWalletSubscription> get copyWith => __$SnWalletSubscriptionCopyWithImpl<_SnWalletSubscription>(this, _$identity);
@override
Map<String, dynamic> toJson() {
return _$SnWalletSubscriptionToJson(this, );
}
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is _SnWalletSubscription&&(identical(other.id, id) || other.id == id)&&(identical(other.begunAt, begunAt) || other.begunAt == begunAt)&&(identical(other.endedAt, endedAt) || other.endedAt == endedAt)&&(identical(other.identifier, identifier) || other.identifier == identifier)&&(identical(other.isActive, isActive) || other.isActive == isActive)&&(identical(other.isFreeTrial, isFreeTrial) || other.isFreeTrial == isFreeTrial)&&(identical(other.status, status) || other.status == status)&&(identical(other.paymentMethod, paymentMethod) || other.paymentMethod == paymentMethod)&&const DeepCollectionEquality().equals(other._paymentDetails, _paymentDetails)&&(identical(other.basePrice, basePrice) || other.basePrice == basePrice)&&(identical(other.couponId, couponId) || other.couponId == couponId)&&const DeepCollectionEquality().equals(other.coupon, coupon)&&(identical(other.renewalAt, renewalAt) || other.renewalAt == renewalAt)&&(identical(other.accountId, accountId) || other.accountId == accountId)&&(identical(other.account, account) || other.account == account)&&(identical(other.isAvailable, isAvailable) || other.isAvailable == isAvailable)&&(identical(other.finalPrice, finalPrice) || other.finalPrice == finalPrice)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hashAll([runtimeType,id,begunAt,endedAt,identifier,isActive,isFreeTrial,status,paymentMethod,const DeepCollectionEquality().hash(_paymentDetails),basePrice,couponId,const DeepCollectionEquality().hash(coupon),renewalAt,accountId,account,isAvailable,finalPrice,createdAt,updatedAt,deletedAt]);
@override
String toString() {
return 'SnWalletSubscription(id: $id, begunAt: $begunAt, endedAt: $endedAt, identifier: $identifier, isActive: $isActive, isFreeTrial: $isFreeTrial, status: $status, paymentMethod: $paymentMethod, paymentDetails: $paymentDetails, basePrice: $basePrice, couponId: $couponId, coupon: $coupon, renewalAt: $renewalAt, accountId: $accountId, account: $account, isAvailable: $isAvailable, finalPrice: $finalPrice, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt)';
}
}
/// @nodoc
abstract mixin class _$SnWalletSubscriptionCopyWith<$Res> implements $SnWalletSubscriptionCopyWith<$Res> {
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
});
@override $SnAccountCopyWith<$Res>? get account;
}
/// @nodoc
class __$SnWalletSubscriptionCopyWithImpl<$Res>
implements _$SnWalletSubscriptionCopyWith<$Res> {
__$SnWalletSubscriptionCopyWithImpl(this._self, this._then);
final _SnWalletSubscription _self;
final $Res Function(_SnWalletSubscription) _then;
/// 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 = 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: 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: 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: 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: 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?,
));
}
/// Create a copy of SnWalletSubscription
/// with the given fields replaced by the non-null parameter values.
@override
@pragma('vm:prefer-inline')
$SnAccountCopyWith<$Res>? get account {
if (_self.account == null) {
return null;
}
return $SnAccountCopyWith<$Res>(_self.account!, (value) {
return _then(_self.copyWith(account: value));
});
}
}
/// @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<SnWalletSubscriptionRef> get copyWith => _$SnWalletSubscriptionRefCopyWithImpl<SnWalletSubscriptionRef>(this as SnWalletSubscriptionRef, _$identity);
/// Serializes this SnWalletSubscriptionRef to a JSON map.
Map<String, dynamic> 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<String, dynamic> 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<String, dynamic> 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 {
String get id; int get status; String get currency; dynamic get remarks; String get appIdentifier; Map<String, dynamic> get meta; int get amount; DateTime get expiredAt; String? get payeeWalletId; SnWallet? get payeeWallet; String? get transactionId; SnTransaction? get transaction; String? get issuerAppId; dynamic get issuerApp; DateTime get createdAt; DateTime get updatedAt; DateTime? get deletedAt;
/// Create a copy of SnWalletOrder
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
$SnWalletOrderCopyWith<SnWalletOrder> get copyWith => _$SnWalletOrderCopyWithImpl<SnWalletOrder>(this as SnWalletOrder, _$identity);
/// Serializes this SnWalletOrder to a JSON map.
Map<String, dynamic> toJson();
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is SnWalletOrder&&(identical(other.id, id) || other.id == id)&&(identical(other.status, status) || other.status == status)&&(identical(other.currency, currency) || other.currency == currency)&&const DeepCollectionEquality().equals(other.remarks, remarks)&&(identical(other.appIdentifier, appIdentifier) || other.appIdentifier == appIdentifier)&&const DeepCollectionEquality().equals(other.meta, meta)&&(identical(other.amount, amount) || other.amount == amount)&&(identical(other.expiredAt, expiredAt) || other.expiredAt == expiredAt)&&(identical(other.payeeWalletId, payeeWalletId) || other.payeeWalletId == payeeWalletId)&&(identical(other.payeeWallet, payeeWallet) || other.payeeWallet == payeeWallet)&&(identical(other.transactionId, transactionId) || other.transactionId == transactionId)&&(identical(other.transaction, transaction) || other.transaction == transaction)&&(identical(other.issuerAppId, issuerAppId) || other.issuerAppId == issuerAppId)&&const DeepCollectionEquality().equals(other.issuerApp, issuerApp)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType,id,status,currency,const DeepCollectionEquality().hash(remarks),appIdentifier,const DeepCollectionEquality().hash(meta),amount,expiredAt,payeeWalletId,payeeWallet,transactionId,transaction,issuerAppId,const DeepCollectionEquality().hash(issuerApp),createdAt,updatedAt,deletedAt);
@override
String toString() {
return 'SnWalletOrder(id: $id, status: $status, currency: $currency, remarks: $remarks, appIdentifier: $appIdentifier, meta: $meta, amount: $amount, expiredAt: $expiredAt, payeeWalletId: $payeeWalletId, payeeWallet: $payeeWallet, transactionId: $transactionId, transaction: $transaction, issuerAppId: $issuerAppId, issuerApp: $issuerApp, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt)';
}
}
/// @nodoc
abstract mixin class $SnWalletOrderCopyWith<$Res> {
factory $SnWalletOrderCopyWith(SnWalletOrder value, $Res Function(SnWalletOrder) _then) = _$SnWalletOrderCopyWithImpl;
@useResult
$Res call({
String id, int status, String currency, dynamic remarks, String appIdentifier, Map<String, dynamic> meta, int amount, DateTime expiredAt, String? payeeWalletId, SnWallet? payeeWallet, String? transactionId, SnTransaction? transaction, String? issuerAppId, dynamic issuerApp, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt
});
$SnWalletCopyWith<$Res>? get payeeWallet;$SnTransactionCopyWith<$Res>? get transaction;
}
/// @nodoc
class _$SnWalletOrderCopyWithImpl<$Res>
implements $SnWalletOrderCopyWith<$Res> {
_$SnWalletOrderCopyWithImpl(this._self, this._then);
final SnWalletOrder _self;
final $Res Function(SnWalletOrder) _then;
/// Create a copy of SnWalletOrder
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? status = null,Object? currency = null,Object? remarks = freezed,Object? appIdentifier = null,Object? meta = null,Object? amount = null,Object? expiredAt = null,Object? payeeWalletId = freezed,Object? payeeWallet = freezed,Object? transactionId = freezed,Object? transaction = freezed,Object? issuerAppId = freezed,Object? issuerApp = 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,status: null == status ? _self.status : status // ignore: cast_nullable_to_non_nullable
as int,currency: null == currency ? _self.currency : currency // ignore: cast_nullable_to_non_nullable
as String,remarks: freezed == remarks ? _self.remarks : remarks // ignore: cast_nullable_to_non_nullable
as dynamic,appIdentifier: null == appIdentifier ? _self.appIdentifier : appIdentifier // ignore: cast_nullable_to_non_nullable
as String,meta: null == meta ? _self.meta : meta // ignore: cast_nullable_to_non_nullable
as Map<String, dynamic>,amount: null == amount ? _self.amount : amount // ignore: cast_nullable_to_non_nullable
as int,expiredAt: null == expiredAt ? _self.expiredAt : expiredAt // ignore: cast_nullable_to_non_nullable
as DateTime,payeeWalletId: freezed == payeeWalletId ? _self.payeeWalletId : payeeWalletId // ignore: cast_nullable_to_non_nullable
as String?,payeeWallet: freezed == payeeWallet ? _self.payeeWallet : payeeWallet // ignore: cast_nullable_to_non_nullable
as SnWallet?,transactionId: freezed == transactionId ? _self.transactionId : transactionId // ignore: cast_nullable_to_non_nullable
as String?,transaction: freezed == transaction ? _self.transaction : transaction // ignore: cast_nullable_to_non_nullable
as SnTransaction?,issuerAppId: freezed == issuerAppId ? _self.issuerAppId : issuerAppId // ignore: cast_nullable_to_non_nullable
as String?,issuerApp: freezed == issuerApp ? _self.issuerApp : issuerApp // ignore: cast_nullable_to_non_nullable
as dynamic,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?,
));
}
/// Create a copy of SnWalletOrder
/// with the given fields replaced by the non-null parameter values.
@override
@pragma('vm:prefer-inline')
$SnWalletCopyWith<$Res>? get payeeWallet {
if (_self.payeeWallet == null) {
return null;
}
return $SnWalletCopyWith<$Res>(_self.payeeWallet!, (value) {
return _then(_self.copyWith(payeeWallet: value));
});
}/// Create a copy of SnWalletOrder
/// with the given fields replaced by the non-null parameter values.
@override
@pragma('vm:prefer-inline')
$SnTransactionCopyWith<$Res>? get transaction {
if (_self.transaction == null) {
return null;
}
return $SnTransactionCopyWith<$Res>(_self.transaction!, (value) {
return _then(_self.copyWith(transaction: value));
});
}
}
/// @nodoc
@JsonSerializable()
class _SnWalletOrder implements SnWalletOrder {
const _SnWalletOrder({required this.id, required this.status, required this.currency, required this.remarks, required this.appIdentifier, final Map<String, dynamic> meta = const {}, required this.amount, required this.expiredAt, required this.payeeWalletId, required this.payeeWallet, required this.transactionId, required this.transaction, required this.issuerAppId, required this.issuerApp, required this.createdAt, required this.updatedAt, required this.deletedAt}): _meta = meta;
factory _SnWalletOrder.fromJson(Map<String, dynamic> json) => _$SnWalletOrderFromJson(json);
@override final String id;
@override final int status;
@override final String currency;
@override final dynamic remarks;
@override final String appIdentifier;
final Map<String, dynamic> _meta;
@override@JsonKey() Map<String, dynamic> get meta {
if (_meta is EqualUnmodifiableMapView) return _meta;
// ignore: implicit_dynamic_type
return EqualUnmodifiableMapView(_meta);
}
@override final int amount;
@override final DateTime expiredAt;
@override final String? payeeWalletId;
@override final SnWallet? payeeWallet;
@override final String? transactionId;
@override final SnTransaction? transaction;
@override final String? issuerAppId;
@override final dynamic issuerApp;
@override final DateTime createdAt;
@override final DateTime updatedAt;
@override final DateTime? deletedAt;
/// Create a copy of SnWalletOrder
/// with the given fields replaced by the non-null parameter values.
@override @JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
_$SnWalletOrderCopyWith<_SnWalletOrder> get copyWith => __$SnWalletOrderCopyWithImpl<_SnWalletOrder>(this, _$identity);
@override
Map<String, dynamic> toJson() {
return _$SnWalletOrderToJson(this, );
}
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is _SnWalletOrder&&(identical(other.id, id) || other.id == id)&&(identical(other.status, status) || other.status == status)&&(identical(other.currency, currency) || other.currency == currency)&&const DeepCollectionEquality().equals(other.remarks, remarks)&&(identical(other.appIdentifier, appIdentifier) || other.appIdentifier == appIdentifier)&&const DeepCollectionEquality().equals(other._meta, _meta)&&(identical(other.amount, amount) || other.amount == amount)&&(identical(other.expiredAt, expiredAt) || other.expiredAt == expiredAt)&&(identical(other.payeeWalletId, payeeWalletId) || other.payeeWalletId == payeeWalletId)&&(identical(other.payeeWallet, payeeWallet) || other.payeeWallet == payeeWallet)&&(identical(other.transactionId, transactionId) || other.transactionId == transactionId)&&(identical(other.transaction, transaction) || other.transaction == transaction)&&(identical(other.issuerAppId, issuerAppId) || other.issuerAppId == issuerAppId)&&const DeepCollectionEquality().equals(other.issuerApp, issuerApp)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType,id,status,currency,const DeepCollectionEquality().hash(remarks),appIdentifier,const DeepCollectionEquality().hash(_meta),amount,expiredAt,payeeWalletId,payeeWallet,transactionId,transaction,issuerAppId,const DeepCollectionEquality().hash(issuerApp),createdAt,updatedAt,deletedAt);
@override
String toString() {
return 'SnWalletOrder(id: $id, status: $status, currency: $currency, remarks: $remarks, appIdentifier: $appIdentifier, meta: $meta, amount: $amount, expiredAt: $expiredAt, payeeWalletId: $payeeWalletId, payeeWallet: $payeeWallet, transactionId: $transactionId, transaction: $transaction, issuerAppId: $issuerAppId, issuerApp: $issuerApp, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt)';
}
}
/// @nodoc
abstract mixin class _$SnWalletOrderCopyWith<$Res> implements $SnWalletOrderCopyWith<$Res> {
factory _$SnWalletOrderCopyWith(_SnWalletOrder value, $Res Function(_SnWalletOrder) _then) = __$SnWalletOrderCopyWithImpl;
@override @useResult
$Res call({
String id, int status, String currency, dynamic remarks, String appIdentifier, Map<String, dynamic> meta, int amount, DateTime expiredAt, String? payeeWalletId, SnWallet? payeeWallet, String? transactionId, SnTransaction? transaction, String? issuerAppId, dynamic issuerApp, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt
});
@override $SnWalletCopyWith<$Res>? get payeeWallet;@override $SnTransactionCopyWith<$Res>? get transaction;
}
/// @nodoc
class __$SnWalletOrderCopyWithImpl<$Res>
implements _$SnWalletOrderCopyWith<$Res> {
__$SnWalletOrderCopyWithImpl(this._self, this._then);
final _SnWalletOrder _self;
final $Res Function(_SnWalletOrder) _then;
/// Create a copy of SnWalletOrder
/// with the given fields replaced by the non-null parameter values.
@override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? status = null,Object? currency = null,Object? remarks = freezed,Object? appIdentifier = null,Object? meta = null,Object? amount = null,Object? expiredAt = null,Object? payeeWalletId = freezed,Object? payeeWallet = freezed,Object? transactionId = freezed,Object? transaction = freezed,Object? issuerAppId = freezed,Object? issuerApp = freezed,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,}) {
return _then(_SnWalletOrder(
id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
as String,status: null == status ? _self.status : status // ignore: cast_nullable_to_non_nullable
as int,currency: null == currency ? _self.currency : currency // ignore: cast_nullable_to_non_nullable
as String,remarks: freezed == remarks ? _self.remarks : remarks // ignore: cast_nullable_to_non_nullable
as dynamic,appIdentifier: null == appIdentifier ? _self.appIdentifier : appIdentifier // ignore: cast_nullable_to_non_nullable
as String,meta: null == meta ? _self._meta : meta // ignore: cast_nullable_to_non_nullable
as Map<String, dynamic>,amount: null == amount ? _self.amount : amount // ignore: cast_nullable_to_non_nullable
as int,expiredAt: null == expiredAt ? _self.expiredAt : expiredAt // ignore: cast_nullable_to_non_nullable
as DateTime,payeeWalletId: freezed == payeeWalletId ? _self.payeeWalletId : payeeWalletId // ignore: cast_nullable_to_non_nullable
as String?,payeeWallet: freezed == payeeWallet ? _self.payeeWallet : payeeWallet // ignore: cast_nullable_to_non_nullable
as SnWallet?,transactionId: freezed == transactionId ? _self.transactionId : transactionId // ignore: cast_nullable_to_non_nullable
as String?,transaction: freezed == transaction ? _self.transaction : transaction // ignore: cast_nullable_to_non_nullable
as SnTransaction?,issuerAppId: freezed == issuerAppId ? _self.issuerAppId : issuerAppId // ignore: cast_nullable_to_non_nullable
as String?,issuerApp: freezed == issuerApp ? _self.issuerApp : issuerApp // ignore: cast_nullable_to_non_nullable
as dynamic,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?,
));
}
/// Create a copy of SnWalletOrder
/// with the given fields replaced by the non-null parameter values.
@override
@pragma('vm:prefer-inline')
$SnWalletCopyWith<$Res>? get payeeWallet {
if (_self.payeeWallet == null) {
return null;
}
return $SnWalletCopyWith<$Res>(_self.payeeWallet!, (value) {
return _then(_self.copyWith(payeeWallet: value));
});
}/// Create a copy of SnWalletOrder
/// with the given fields replaced by the non-null parameter values.
@override
@pragma('vm:prefer-inline')
$SnTransactionCopyWith<$Res>? get transaction {
if (_self.transaction == null) {
return null;
}
return $SnTransactionCopyWith<$Res>(_self.transaction!, (value) {
return _then(_self.copyWith(transaction: value));
});
}
}
// dart format on // dart format on

View File

@ -100,3 +100,145 @@ Map<String, dynamic> _$SnTransactionToJson(_SnTransaction instance) =>
'updated_at': instance.updatedAt.toIso8601String(), 'updated_at': instance.updatedAt.toIso8601String(),
'deleted_at': instance.deletedAt?.toIso8601String(), 'deleted_at': instance.deletedAt?.toIso8601String(),
}; };
_SnWalletSubscription _$SnWalletSubscriptionFromJson(
Map<String, dynamic> json,
) => _SnWalletSubscription(
id: json['id'] as String,
begunAt: DateTime.parse(json['begun_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? ?? 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:
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? ?? 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:
json['deleted_at'] == null
? null
: DateTime.parse(json['deleted_at'] as String),
);
Map<String, dynamic> _$SnWalletSubscriptionToJson(
_SnWalletSubscription instance,
) => <String, dynamic>{
'id': instance.id,
'begun_at': instance.begunAt.toIso8601String(),
'ended_at': instance.endedAt?.toIso8601String(),
'identifier': instance.identifier,
'is_active': instance.isActive,
'is_free_trial': instance.isFreeTrial,
'status': instance.status,
'payment_method': instance.paymentMethod,
'payment_details': instance.paymentDetails,
'base_price': instance.basePrice,
'coupon_id': instance.couponId,
'coupon': instance.coupon,
'renewal_at': instance.renewalAt?.toIso8601String(),
'account_id': instance.accountId,
'account': instance.account?.toJson(),
'is_available': instance.isAvailable,
'final_price': instance.finalPrice,
'created_at': instance.createdAt.toIso8601String(),
'updated_at': instance.updatedAt.toIso8601String(),
'deleted_at': instance.deletedAt?.toIso8601String(),
};
_SnWalletSubscriptionRef _$SnWalletSubscriptionRefFromJson(
Map<String, dynamic> 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<String, dynamic> _$SnWalletSubscriptionRefToJson(
_SnWalletSubscriptionRef instance,
) => <String, dynamic>{
'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<String, dynamic> json) =>
_SnWalletOrder(
id: json['id'] as String,
status: (json['status'] as num).toInt(),
currency: json['currency'] as String,
remarks: json['remarks'],
appIdentifier: json['app_identifier'] as String,
meta: json['meta'] as Map<String, dynamic>? ?? const {},
amount: (json['amount'] as num).toInt(),
expiredAt: DateTime.parse(json['expired_at'] as String),
payeeWalletId: json['payee_wallet_id'] as String?,
payeeWallet:
json['payee_wallet'] == null
? null
: SnWallet.fromJson(json['payee_wallet'] as Map<String, dynamic>),
transactionId: json['transaction_id'] as String?,
transaction:
json['transaction'] == null
? null
: SnTransaction.fromJson(
json['transaction'] as Map<String, dynamic>,
),
issuerAppId: json['issuer_app_id'] as String?,
issuerApp: json['issuer_app'],
createdAt: DateTime.parse(json['created_at'] as String),
updatedAt: DateTime.parse(json['updated_at'] as String),
deletedAt:
json['deleted_at'] == null
? null
: DateTime.parse(json['deleted_at'] as String),
);
Map<String, dynamic> _$SnWalletOrderToJson(_SnWalletOrder instance) =>
<String, dynamic>{
'id': instance.id,
'status': instance.status,
'currency': instance.currency,
'remarks': instance.remarks,
'app_identifier': instance.appIdentifier,
'meta': instance.meta,
'amount': instance.amount,
'expired_at': instance.expiredAt.toIso8601String(),
'payee_wallet_id': instance.payeeWalletId,
'payee_wallet': instance.payeeWallet?.toJson(),
'transaction_id': instance.transactionId,
'transaction': instance.transaction?.toJson(),
'issuer_app_id': instance.issuerAppId,
'issuer_app': instance.issuerApp,
'created_at': instance.createdAt.toIso8601String(),
'updated_at': instance.updatedAt.toIso8601String(),
'deleted_at': instance.deletedAt?.toIso8601String(),
};

View File

@ -66,6 +66,8 @@ class CallNotifier extends _$CallNotifier {
Timer? _durationTimer; Timer? _durationTimer;
Room? get room => _room;
@override @override
CallState build() { CallState build() {
// Subscribe to websocket updates // Subscribe to websocket updates

View File

@ -6,7 +6,7 @@ part of 'call.dart';
// RiverpodGenerator // RiverpodGenerator
// ************************************************************************** // **************************************************************************
String _$callNotifierHash() => r'e04cea314c823e407d49fd616d90d77491232c12'; String _$callNotifierHash() => r'47eaba43aa2af1a107725998f4a34af2c94fbc55';
/// See also [CallNotifier]. /// See also [CallNotifier].
@ProviderFor(CallNotifier) @ProviderFor(CallNotifier)

View File

@ -8,44 +8,54 @@ class AppRouter extends RootStackRouter {
@override @override
List<AutoRoute> get routes => [ List<AutoRoute> get routes => [
AutoRoute(page: PostComposeRoute.page, path: '/posts/compose'),
AutoRoute(page: PostEditRoute.page, path: '/posts/:id/edit'),
AutoRoute( AutoRoute(
page: ExploreShellRoute.page, page: TabsRoute.page,
path: '/', path: '/',
children: [ children: [
AutoRoute(page: ExploreRoute.page, path: ''), AutoRoute(
AutoRoute(page: PostDetailRoute.page, path: 'posts/:id'), page: ExploreShellRoute.page,
AutoRoute(page: PublisherProfileRoute.page, path: 'publishers/:name'), path: '',
], children: [
), AutoRoute(page: ExploreRoute.page, path: ''),
AutoRoute( AutoRoute(page: PostDetailRoute.page, path: 'posts/:id'),
page: AccountShellRoute.page, AutoRoute(
path: '/account', page: PublisherProfileRoute.page,
children: [ path: 'publishers/:name',
AutoRoute(page: AccountRoute.page, path: ''), ),
AutoRoute(page: NotificationRoute.page, path: 'notifications'), ],
AutoRoute(page: WalletRoute.page, path: 'wallet'), ),
AutoRoute(page: RelationshipRoute.page, path: 'relationships'), AutoRoute(
AutoRoute(page: AccountProfileRoute.page, path: ':name'), page: AccountShellRoute.page,
AutoRoute(page: UpdateProfileRoute.page, path: 'me/update'), path: 'account',
AutoRoute(page: AccountSettingsRoute.page, path: 'settings'), children: [
AutoRoute(page: AccountRoute.page, path: ''),
AutoRoute(page: NotificationRoute.page, path: 'notifications'),
AutoRoute(page: WalletRoute.page, path: 'wallet'),
AutoRoute(page: RelationshipRoute.page, path: 'relationships'),
AutoRoute(page: AccountProfileRoute.page, path: ':name'),
AutoRoute(page: UpdateProfileRoute.page, path: 'me/update'),
AutoRoute(page: LevelingRoute.page, path: 'me/leveling'),
AutoRoute(page: AccountSettingsRoute.page, path: 'settings'),
],
),
AutoRoute(page: RealmListRoute.page, path: 'realms'),
AutoRoute(
page: ChatShellRoute.page,
path: 'chat',
children: [
AutoRoute(page: ChatListRoute.page, path: ''),
AutoRoute(page: ChatRoomRoute.page, path: ':id'),
AutoRoute(page: NewChatRoute.page, path: 'new'),
AutoRoute(page: EditChatRoute.page, path: ':id/edit'),
AutoRoute(page: ChatDetailRoute.page, path: ':id/detail'),
],
),
], ],
), ),
AutoRoute(page: PostComposeRoute.page, path: '/posts/compose'),
AutoRoute(page: PostEditRoute.page, path: '/posts/:id/edit'),
AutoRoute(page: CallRoute.page, path: '/chat/:id/call'),
AutoRoute(page: EventCalanderRoute.page, path: '/account/:name/calendar'), AutoRoute(page: EventCalanderRoute.page, path: '/account/:name/calendar'),
AutoRoute(page: RealmListRoute.page, path: '/realms'),
AutoRoute(
page: ChatShellRoute.page,
path: '/chat',
children: [
AutoRoute(page: ChatListRoute.page, path: ''),
AutoRoute(page: ChatRoomRoute.page, path: ':id'),
AutoRoute(page: CallRoute.page, path: ':id/call'),
AutoRoute(page: NewChatRoute.page, path: 'new'),
AutoRoute(page: EditChatRoute.page, path: ':id/edit'),
AutoRoute(page: ChatDetailRoute.page, path: ':id/detail'),
],
),
AutoRoute( AutoRoute(
page: CreatorHubShellRoute.page, page: CreatorHubShellRoute.page,
path: '/creators', path: '/creators',

File diff suppressed because it is too large Load Diff

View File

@ -67,8 +67,9 @@ class AccountScreen extends HookConsumerWidget {
return AppScaffold( return AppScaffold(
noBackground: isWide, noBackground: isWide,
appBar: AppBar(title: const Text('account').tr()), appBar: AppBar(backgroundColor: Colors.transparent, toolbarHeight: 0),
body: SingleChildScrollView( body: SingleChildScrollView(
padding: getTabbedPadding(context),
child: Column( child: Column(
children: <Widget>[ children: <Widget>[
Card( Card(
@ -139,11 +140,16 @@ class AccountScreen extends HookConsumerWidget {
], ],
), ),
).padding(horizontal: 8), ).padding(horizontal: 8),
LevelingProgressCard( GestureDetector(
level: user.value!.profile.level, child: LevelingProgressCard(
experience: user.value!.profile.experience, level: user.value!.profile.level,
progress: user.value!.profile.levelingProgress, experience: user.value!.profile.experience,
).padding(horizontal: 8), progress: user.value!.profile.levelingProgress,
),
onTap: () {
context.router.push(LevelingRoute());
},
).padding(horizontal: 12),
Row( Row(
children: [ children: [
Expanded( Expanded(

View File

@ -0,0 +1,670 @@
import 'package:auto_route/auto_route.dart';
import 'package:dio/dio.dart';
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/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';
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<SnWalletSubscription?> 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 {
const LevelingScreen({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final user = ref.watch(userInfoProvider);
final stellarSubscription = ref.watch(accountStellarSubscriptionProvider);
if (user.value == null) {
return AppScaffold(
appBar: AppBar(title: Text('levelingProgress'.tr())),
body: const Center(child: CircularProgressIndicator()),
);
}
final currentLevel = user.value!.profile.level;
final currentExp = user.value!.profile.experience;
final progress = user.value!.profile.levelingProgress;
return AppScaffold(
appBar: AppBar(title: Text('levelingProgress'.tr())),
body: SingleChildScrollView(
padding: getTabbedPadding(context, horizontal: 20, vertical: 20),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
// Current Progress Card
LevelingProgressCard(
level: currentLevel,
experience: currentExp,
progress: progress,
),
const Gap(24),
// Level Stairs Graph
Text(
'levelProgress'.tr(),
style: Theme.of(
context,
).textTheme.headlineSmall?.copyWith(fontWeight: FontWeight.bold),
),
const Gap(16),
// Stairs visualization with fixed height and horizontal scroll
_buildLevelStairs(context, currentLevel),
const Gap(24),
// Membership section
_buildMembershipSection(context, ref, stellarSubscription),
const Gap(16),
// Unlocked features section
Container(
width: double.infinity,
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.surfaceContainerHigh,
borderRadius: BorderRadius.circular(12),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'unlockedFeatures'.tr(),
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
),
const Gap(8),
Text(
'unlockedFeaturesDescription'.tr(),
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
),
],
),
),
],
),
),
);
}
Widget _buildLevelStairs(BuildContext context, int currentLevel) {
const totalLevels = 14;
const stairHeight = 20.0;
const stairWidth = 50.0;
const containerHeight = 280.0;
return Container(
height: containerHeight,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(12),
border: Border.all(
color: Theme.of(context).colorScheme.outline.withOpacity(0.2),
),
),
child: SingleChildScrollView(
scrollDirection: Axis.horizontal,
padding: const EdgeInsets.symmetric(horizontal: 16),
child: SizedBox(
width: (totalLevels * (stairWidth + 8)) + 40,
height: containerHeight,
child: CustomPaint(
painter: LevelStairsPainter(
currentLevel: currentLevel,
totalLevels: totalLevels,
primaryColor: Theme.of(context).colorScheme.primary,
surfaceColor: Theme.of(context).colorScheme.surfaceContainerHigh,
onSurfaceColor: Theme.of(context).colorScheme.onSurface,
stairHeight: stairHeight,
stairWidth: stairWidth,
),
child: Stack(
children: List.generate(totalLevels, (index) {
final level = index + 1;
final isCompleted = level <= currentLevel;
final isCurrent = level == currentLevel;
// Calculate position from bottom
final bottomPosition = 0.0;
final leftPosition = 20.0 + (index * (stairWidth + 8));
// Make higher levels progressively taller
final progressiveHeight =
40.0 + (index * 15.0); // Base height + progressive increase
return Positioned(
left: leftPosition,
bottom: bottomPosition,
child: Container(
width: stairWidth,
height: progressiveHeight,
decoration: BoxDecoration(
color:
isCompleted
? Theme.of(context).colorScheme.primary
: Theme.of(
context,
).colorScheme.surfaceContainerHigh,
borderRadius: const BorderRadius.only(
topLeft: Radius.circular(6),
topRight: Radius.circular(6),
),
border:
isCurrent
? Border.all(
color: Theme.of(context).colorScheme.primary,
width: 2,
)
: null,
boxShadow:
isCurrent
? [
BoxShadow(
color: Theme.of(
context,
).colorScheme.primary.withOpacity(0.3),
blurRadius: 6,
spreadRadius: 1,
),
]
: null,
),
child: Padding(
padding: const EdgeInsets.only(top: 8),
child: Column(
children: [
Text(
level.toString(),
style: GoogleFonts.robotoMono(
fontSize: 14,
fontWeight: FontWeight.bold,
color:
isCompleted
? Theme.of(context).colorScheme.onPrimary
: Theme.of(context).colorScheme.onSurface,
),
),
if (isCurrent) ...[
const Gap(4),
Container(
width: 4,
height: 4,
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.onPrimary,
shape: BoxShape.circle,
),
),
],
],
),
),
),
);
}),
),
),
),
),
);
}
Widget _buildMembershipSection(
BuildContext context,
WidgetRef ref,
AsyncValue<SnWalletSubscription?> 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 isActive = membership?.isActive ?? false;
return 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: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Icon(
isActive ? Icons.star : Icons.star_border,
color: Theme.of(context).colorScheme.primary,
size: 24,
),
const Gap(8),
Text(
'stellarMembership'.tr(),
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
),
],
),
const Gap(12),
if (isActive) ...[
_buildCurrentMembershipCard(context, membership!),
const Gap(16),
],
Text(
isActive ? 'upgradeYourPlan'.tr() : 'chooseYourPlan'.tr(),
style: TextStyle(fontSize: 16, fontWeight: FontWeight.w600),
),
const Gap(12),
_buildMembershipTiers(context, ref, membership),
],
),
);
}
Widget _buildCurrentMembershipCard(
BuildContext context,
SnWalletSubscription membership,
) {
final tierName = _getMembershipTierName(membership.identifier);
final tierColor = _getMembershipTierColor(context, membership.identifier);
return Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: tierColor.withOpacity(0.1),
borderRadius: BorderRadius.circular(8),
border: Border.all(color: tierColor, width: 1),
),
child: Row(
children: [
Icon(Icons.verified, color: tierColor, size: 20),
const Gap(8),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'currentMembership'.tr(args: [tierName]),
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w600,
color: tierColor,
),
),
if (membership.endedAt != null)
Text(
'membershipExpires'.tr(
args: [membership.endedAt!.formatSystem()],
),
style: Theme.of(context).textTheme.bodySmall?.copyWith(
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
),
],
),
),
],
),
);
}
Widget _buildMembershipTiers(
BuildContext context,
WidgetRef ref,
SnWalletSubscription? currentMembership,
) {
final tiers = [
{
'id': 'solian.stellar.primary',
'name': 'membershipTierStellar'.tr(),
'price': 'membershipPriceStellar'.tr(),
'features': [
'membershipFeatureBasic'.tr(),
'membershipFeaturePrioritySupport'.tr(),
'membershipFeatureAdFree'.tr(),
],
'color': Colors.blue,
},
{
'id': 'solian.stellar.nova',
'name': 'membershipTierNova'.tr(),
'price': 'membershipPriceNova'.tr(),
'features': [
'membershipFeatureAllPrimary'.tr(),
'membershipFeatureAdvancedCustomization'.tr(),
'membershipFeatureEarlyAccess'.tr(),
],
'color': Colors.purple,
},
{
'id': 'solian.stellar.supernova',
'name': 'membershipTierSupernova'.tr(),
'price': 'membershipPriceSupernova'.tr(),
'features': [
'membershipFeatureAllNova'.tr(),
'membershipFeatureExclusiveContent'.tr(),
'membershipFeatureVipSupport'.tr(),
],
'color': Colors.orange,
},
];
return Column(
children:
tiers.map((tier) {
final isCurrentTier = currentMembership?.identifier == tier['id'];
final tierColor = tier['color'] as Color;
return Container(
margin: const EdgeInsets.only(bottom: 8),
child: Material(
color: Colors.transparent,
child: InkWell(
onTap:
isCurrentTier
? null
: () => _purchaseMembership(
context,
ref,
tier['id'] as String,
),
borderRadius: BorderRadius.circular(8),
child: Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color:
isCurrentTier
? tierColor.withOpacity(0.1)
: Theme.of(context).colorScheme.surface,
borderRadius: BorderRadius.circular(8),
border: Border.all(
color:
isCurrentTier
? tierColor
: Theme.of(
context,
).colorScheme.outline.withOpacity(0.2),
width: isCurrentTier ? 2 : 1,
),
),
child: Row(
children: [
Container(
width: 4,
height: 40,
decoration: BoxDecoration(
color: tierColor,
borderRadius: BorderRadius.circular(2),
),
),
const Gap(12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Text(
tier['name'] as String,
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
color: isCurrentTier ? tierColor : null,
),
),
const Gap(8),
if (isCurrentTier)
Container(
padding: const EdgeInsets.symmetric(
horizontal: 6,
vertical: 2,
),
decoration: BoxDecoration(
color: tierColor,
borderRadius: BorderRadius.circular(4),
),
child: Text(
'membershipCurrentBadge'.tr(),
style: TextStyle(
fontSize: 10,
fontWeight: FontWeight.bold,
color: Colors.white,
),
),
),
],
),
Text(
tier['price'] as String,
style: Theme.of(
context,
).textTheme.bodyMedium?.copyWith(
color:
Theme.of(
context,
).colorScheme.onSurfaceVariant,
),
),
],
),
),
if (!isCurrentTier)
Icon(
Icons.arrow_forward_ios,
size: 16,
color:
Theme.of(context).colorScheme.onSurfaceVariant,
),
],
),
),
),
),
);
}).toList(),
);
}
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(BuildContext context, String identifier) {
switch (identifier) {
case 'solian.stellar.primary':
return Colors.blue;
case 'solian.stellar.nova':
return Colors.purple;
case 'solian.stellar.supernova':
return Colors.orange;
default:
return Theme.of(context).colorScheme.primary;
}
}
Future<void> _purchaseMembership(
BuildContext context,
WidgetRef ref,
String tierId,
) async {
final client = ref.watch(apiClientProvider);
try {
showLoadingModal(context);
final resp = await client.post(
'/subscriptions',
data: {
'identifier': tierId,
'payment_method': 'solian.wallet',
'payment_details': {'currency': 'golds'},
'cycle_duration_days': 30,
},
options: Options(headers: {'X-Noop': true}),
);
final subscription = SnWalletSubscription.fromJson(resp.data);
if (subscription.status == 1) return;
final orderResp = await client.post(
'/subscriptions/${subscription.identifier}/order',
);
final order = SnWalletOrder.fromJson(orderResp.data);
if (context.mounted) hideLoadingModal(context);
// Show payment overlay to complete the payment
if (!context.mounted) return;
final paidOrder = await PaymentOverlay.show(
context: context,
order: order,
enableBiometric: true,
);
if (context.mounted) showLoadingModal(context);
if (paidOrder != null) {
await client.post(
'/subscriptions/order/handle',
data: {'order_id': paidOrder.id},
);
ref.invalidate(accountStellarSubscriptionProvider);
ref.read(userInfoProvider.notifier).fetchUser();
if (context.mounted) {
showSnackBar(context, 'membershipPurchaseSuccess'.tr());
}
}
} catch (err) {
showErrorAlert(err);
} finally {
if (context.mounted) hideLoadingModal(context);
}
}
}
class LevelStairsPainter extends CustomPainter {
final int currentLevel;
final int totalLevels;
final Color primaryColor;
final Color surfaceColor;
final Color onSurfaceColor;
final double stairHeight;
final double stairWidth;
LevelStairsPainter({
required this.currentLevel,
required this.totalLevels,
required this.primaryColor,
required this.surfaceColor,
required this.onSurfaceColor,
required this.stairHeight,
required this.stairWidth,
});
@override
void paint(Canvas canvas, Size size) {
final paint =
Paint()
..color = surfaceColor.withOpacity(0.2)
..strokeWidth = 1.5
..style = PaintingStyle.stroke;
// Draw connecting lines between stairs
for (int i = 0; i < totalLevels - 1; i++) {
final startX = 20.0 + (i * (stairWidth + 8)) + stairWidth;
final startHeight =
40.0 + (i * 15.0); // Progressive height for current stair
final startY = size.height - (20.0 + startHeight);
final endX = 20.0 + ((i + 1) * (stairWidth + 8));
final endHeight =
40.0 + ((i + 1) * 15.0); // Progressive height for next stair
final endY = size.height - (20.0 + endHeight);
canvas.drawLine(Offset(startX, startY), Offset(endX, endY), paint);
}
}
@override
bool shouldRepaint(covariant CustomPainter oldDelegate) => false;
}

View File

@ -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<SnWalletSubscription?>.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<SnWalletSubscription?>;
// 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,4 +1,5 @@
import 'dart:convert'; import 'dart:convert';
import 'dart:math' as math;
import 'package:easy_localization/easy_localization.dart'; import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
@ -138,13 +139,13 @@ class AuthFactorSheet extends HookConsumerWidget {
children: [ children: [
if (factor.enabledAt == null) if (factor.enabledAt == null)
Badge( Badge(
label: Text('authFactorDisabled'.tr()), label: Text('authFactorDisabled').tr(),
textColor: Theme.of(context).colorScheme.onSecondary, textColor: Theme.of(context).colorScheme.onSecondary,
backgroundColor: Theme.of(context).colorScheme.secondary, backgroundColor: Theme.of(context).colorScheme.secondary,
) )
else else
Badge( Badge(
label: Text('authFactorEnabled'.tr()), label: Text('authFactorEnabled').tr(),
textColor: Theme.of(context).colorScheme.onPrimary, textColor: Theme.of(context).colorScheme.onPrimary,
backgroundColor: Theme.of(context).colorScheme.primary, backgroundColor: Theme.of(context).colorScheme.primary,
), ),
@ -217,6 +218,8 @@ class AuthFactorNewSheet extends HookConsumerWidget {
} }
} }
final width = math.min(400, MediaQuery.of(context).size.width);
return SheetScaffold( return SheetScaffold(
titleText: 'authFactorNew'.tr(), titleText: 'authFactorNew'.tr(),
child: Column( child: Column(
@ -248,7 +251,7 @@ class AuthFactorNewSheet extends HookConsumerWidget {
} }
}, },
), ),
if (factorType.value == 0) if ([0].contains(factorType.value))
TextField( TextField(
controller: secretController, controller: secretController,
decoration: InputDecoration( decoration: InputDecoration(
@ -259,6 +262,20 @@ class AuthFactorNewSheet extends HookConsumerWidget {
), ),
onTapOutside: onTapOutside:
(_) => FocusManager.instance.primaryFocus?.unfocus(), (_) => FocusManager.instance.primaryFocus?.unfocus(),
)
else if ([4].contains(factorType.value))
OtpTextField(
showCursor: false,
numberOfFields: 6,
obscureText: false,
showFieldAsBox: true,
focusedBorderColor: Theme.of(context).colorScheme.primary,
fieldWidth: (width / 6) - 10,
keyboardType: TextInputType.number,
onSubmit: (String verificationCode) {
secretController.text = verificationCode;
},
textStyle: Theme.of(context).textTheme.titleLarge!,
), ),
Padding( Padding(
padding: const EdgeInsets.symmetric(horizontal: 16.0), padding: const EdgeInsets.symmetric(horizontal: 16.0),

View File

@ -28,6 +28,7 @@ Widget getProviderIcon(String provider, {double size = 24, Color? color}) {
case 'google': case 'google':
case 'github': case 'github':
case 'discord': case 'discord':
case 'afdian':
return SvgPicture.asset( return SvgPicture.asset(
'assets/images/oidc/$providerLower.svg', 'assets/images/oidc/$providerLower.svg',
width: size, width: size,
@ -51,6 +52,8 @@ String getLocalizedProviderName(String provider) {
return 'accountConnectionProviderGithub'.tr(); return 'accountConnectionProviderGithub'.tr();
case 'discord': case 'discord':
return 'accountConnectionProviderDiscord'.tr(); return 'accountConnectionProviderDiscord'.tr();
case 'afdian':
return 'accountConnectionProviderAfdian'.tr();
default: default:
return provider; return provider;
} }
@ -141,7 +144,14 @@ class AccountConnectionNewSheet extends HookConsumerWidget {
final selectedProvider = useState<String>('apple'); final selectedProvider = useState<String>('apple');
// List of available providers // List of available providers
final providers = ['apple', 'microsoft', 'google', 'github', 'discord']; final providers = [
'apple',
'microsoft',
'google',
'github',
'discord',
'afdian',
];
Future<void> addConnection() async { Future<void> addConnection() async {
final client = ref.watch(apiClientProvider); final client = ref.watch(apiClientProvider);
@ -182,7 +192,8 @@ class AccountConnectionNewSheet extends HookConsumerWidget {
case 'google': case 'google':
case 'github': case 'github':
case 'discord': case 'discord':
await Navigator.of(context).push( case 'afdian':
await Navigator.of(context, rootNavigator: true).push(
MaterialPageRoute( MaterialPageRoute(
builder: builder:
(context) => OidcScreen( (context) => OidcScreen(

View File

@ -278,7 +278,10 @@ class AccountProfileScreen extends HookConsumerWidget {
children: [ children: [
Row( Row(
children: [ children: [
Text(data.nick).fontSize(20), AccountName(
account: data,
style: TextStyle(fontSize: 20),
),
const Gap(6), const Gap(6),
Text( Text(
'@${data.name}', '@${data.name}',

View File

@ -40,6 +40,7 @@ final Map<int, (String, String, IconData)> kFactorTypes = {
Symbols.notifications_active, Symbols.notifications_active,
), ),
3: ('authFactorTOTP', 'authFactorTOTPDescription', Symbols.timer), 3: ('authFactorTOTP', 'authFactorTOTPDescription', Symbols.timer),
4: ('authFactorPin', 'authFactorPinDescription', Symbols.nest_secure_alarm),
}; };
@RoutePage() @RoutePage()
@ -651,7 +652,7 @@ class _LoginLookupScreen extends HookConsumerWidget {
} }
Future<void> withOidc(String provider) async { Future<void> withOidc(String provider) async {
final challengeId = await Navigator.of(context).push( final challengeId = await Navigator.of(context, rootNavigator: true).push(
MaterialPageRoute( MaterialPageRoute(
builder: (context) => OidcScreen(provider: provider.toLowerCase()), builder: (context) => OidcScreen(provider: provider.toLowerCase()),
), ),

View File

@ -1,161 +0,0 @@
import 'dart:developer';
import 'package:auto_route/auto_route.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:gap/gap.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:island/route.dart';
import 'package:island/route.gr.dart';
import 'package:island/screens/notification.dart';
import 'package:island/services/responsive.dart';
import 'package:material_symbols_icons/symbols.dart';
final currentRouteProvider = StateProvider<String?>((ref) => null);
class TabNavigationObserver extends AutoRouterObserver {
Function(String?) onChange;
TabNavigationObserver({required this.onChange});
@override
void didPush(Route route, Route? previousRoute) {
log('pushed ${previousRoute?.settings.name} -> ${route.settings.name}');
if (route is DialogRoute) return;
Future(() {
onChange(route.settings.name);
});
}
@override
void didPop(Route route, Route? previousRoute) {
log('popped ${route.settings.name} -> ${previousRoute?.settings.name}');
if (route is DialogRoute) return;
Future(() {
onChange(previousRoute?.settings.name);
});
}
}
@RoutePage()
class TabsNavigationWidget extends HookConsumerWidget {
final Widget child;
final AppRouter router;
const TabsNavigationWidget({
super.key,
required this.child,
required this.router,
});
@override
Widget build(BuildContext context, WidgetRef ref) {
final useHorizontalLayout = isWideScreen(context);
final currentRoute = ref.watch(currentRouteProvider);
final notificationUnreadCount = ref.watch(
notificationUnreadCountNotifierProvider,
);
int activeIndex = 0;
final destinations = [
NavigationDestination(
label: 'explore'.tr(),
icon: const Icon(Symbols.explore),
),
NavigationDestination(label: 'chat'.tr(), icon: const Icon(Symbols.chat)),
NavigationDestination(
label: 'realms'.tr(),
icon: const Icon(Symbols.workspaces),
),
NavigationDestination(
label: 'account'.tr(),
icon: Badge.count(
count: notificationUnreadCount.value ?? 0,
isLabelVisible: (notificationUnreadCount.value ?? 0) > 0,
child: const Icon(Symbols.account_circle),
),
),
];
final routes = <PageRouteInfo>[
ExploreRoute(),
ChatListRoute(),
RealmListRoute(),
AccountRoute(),
];
final routeNames = [
ExploreRoute.name,
ExploreShellRoute.name,
ChatListRoute.name,
RealmListRoute.name,
AccountRoute.name,
ChatShellRoute.name,
AccountShellRoute.name,
];
activeIndex = routes.indexWhere((route) => route.routeName == currentRoute);
if (activeIndex == -1) {
activeIndex = 0;
}
final isTabRoute = routeNames.any((route) {
return route == currentRoute;
});
return Scaffold(
extendBodyBehindAppBar: true,
backgroundColor: Colors.transparent,
body:
useHorizontalLayout
? Row(
children: [
ColoredBox(
color: Theme.of(context).colorScheme.surface,
child: Column(
children: [
Gap(MediaQuery.of(context).padding.top + 8),
Expanded(
child: NavigationRail(
selectedIndex: activeIndex,
onDestinationSelected: (index) {
router.replace(routes[index]);
},
// labelType: NavigationRailLabelType.all,
destinations:
destinations
.map(
(d) => NavigationRailDestination(
icon: d.icon,
label: Text(d.label),
),
)
.toList(),
),
),
Gap(MediaQuery.of(context).padding.bottom + 8),
],
),
),
VerticalDivider(
color: Theme.of(context).dividerColor,
width: 1 / MediaQuery.of(context).devicePixelRatio,
),
Expanded(child: child),
],
)
: child,
bottomNavigationBar:
!useHorizontalLayout && isTabRoute
? NavigationBar(
height: 56,
labelBehavior: NavigationDestinationLabelBehavior.alwaysHide,
selectedIndex: activeIndex,
onDestinationSelected: (index) {
router.replace(routes[index]);
},
destinations: destinations,
)
: null,
);
}
}

View File

@ -11,6 +11,7 @@ import 'package:island/widgets/chat/call_button.dart';
import 'package:island/widgets/chat/call_overlay.dart'; import 'package:island/widgets/chat/call_overlay.dart';
import 'package:island/widgets/chat/call_participant_tile.dart'; import 'package:island/widgets/chat/call_participant_tile.dart';
import 'package:livekit_client/livekit_client.dart'; import 'package:livekit_client/livekit_client.dart';
import 'package:material_symbols_icons/symbols.dart';
import 'package:styled_widget/styled_widget.dart'; import 'package:styled_widget/styled_widget.dart';
@RoutePage() @RoutePage()
@ -32,37 +33,9 @@ class CallScreen extends HookConsumerWidget {
final viewMode = useState<String>('grid'); final viewMode = useState<String>('grid');
return AppScaffold( return AppScaffold(
noBackground: false,
appBar: AppBar( appBar: AppBar(
leading: PageBackButton( leading: PageBackButton(),
onWillPop: () {
showDialog<void>(
context: context,
builder: (context) {
return AlertDialog(
content: const Text(
'Do you want to leave the call or leave it in background?',
),
actions: [
TextButton(
onPressed: () {
Navigator.of(context).pop();
},
child: const Text('In Background'),
),
TextButton(
onPressed: () async {
Navigator.of(context).pop();
await callNotifier.disconnect();
callNotifier.dispose();
},
child: const Text('Leave'),
),
],
);
},
);
},
),
title: Column( title: Column(
crossAxisAlignment: CrossAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center,
children: [ children: [
@ -83,7 +56,7 @@ class CallScreen extends HookConsumerWidget {
mainAxisAlignment: MainAxisAlignment.end, mainAxisAlignment: MainAxisAlignment.end,
children: [ children: [
IconButton( IconButton(
icon: Icon(Icons.grid_view), icon: Icon(Symbols.grid_view),
tooltip: 'Grid View', tooltip: 'Grid View',
onPressed: () => viewMode.value = 'grid', onPressed: () => viewMode.value = 'grid',
color: color:
@ -92,7 +65,7 @@ class CallScreen extends HookConsumerWidget {
: null, : null,
), ),
IconButton( IconButton(
icon: Icon(Icons.view_agenda), icon: Icon(Symbols.view_agenda),
tooltip: 'Stage View', tooltip: 'Stage View',
onPressed: () => viewMode.value = 'stage', onPressed: () => viewMode.value = 'stage',
color: color:

View File

@ -27,6 +27,7 @@ import 'package:island/widgets/content/cloud_files.dart';
import 'package:island/widgets/content/sheet.dart'; import 'package:island/widgets/content/sheet.dart';
import 'package:island/widgets/realms/selection_dropdown.dart'; import 'package:island/widgets/realms/selection_dropdown.dart';
import 'package:island/widgets/response.dart'; import 'package:island/widgets/response.dart';
import 'package:island/screens/tabs.dart';
import 'package:material_symbols_icons/symbols.dart'; import 'package:material_symbols_icons/symbols.dart';
import 'package:relative_time/relative_time.dart'; import 'package:relative_time/relative_time.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart';
@ -241,6 +242,7 @@ class ChatListScreen extends HookConsumerWidget {
} }
return AppScaffold( return AppScaffold(
extendBody: false, // Prevent conflicts with tabs navigation
appBar: AppBar( appBar: AppBar(
title: Text('chat').tr(), title: Text('chat').tr(),
bottom: TabBar( bottom: TabBar(
@ -339,6 +341,7 @@ class ChatListScreen extends HookConsumerWidget {
}, },
child: const Icon(Symbols.add), child: const Icon(Symbols.add),
), ),
floatingActionButtonLocation: TabbedFabLocation(context),
body: Stack( body: Stack(
children: [ children: [
Column( Column(
@ -365,10 +368,10 @@ class ChatListScreen extends HookConsumerWidget {
ref.invalidate(chatroomsJoinedProvider); ref.invalidate(chatroomsJoinedProvider);
}), }),
child: ListView.builder( child: ListView.builder(
padding: padding: getTabbedPadding(
callState.isConnected context,
? EdgeInsets.only(bottom: 96) bottom: callState.isConnected ? 96 : null,
: EdgeInsets.zero, ),
itemCount: itemCount:
items items
.where( .where(
@ -428,7 +431,7 @@ class ChatListScreen extends HookConsumerWidget {
Positioned( Positioned(
left: 0, left: 0,
right: 0, right: 0,
bottom: 0, bottom: getTabbedPadding(context).bottom + 8,
child: const CallOverlayBar().padding(horizontal: 16, vertical: 12), child: const CallOverlayBar().padding(horizontal: 16, vertical: 12),
), ),
], ],

View File

@ -1,6 +1,7 @@
import 'package:auto_route/auto_route.dart'; import 'package:auto_route/auto_route.dart';
import 'package:easy_localization/easy_localization.dart'; import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:gap/gap.dart'; import 'package:gap/gap.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:island/models/activity.dart'; import 'package:island/models/activity.dart';
@ -12,6 +13,7 @@ import 'package:island/models/post.dart';
import 'package:island/widgets/check_in.dart'; import 'package:island/widgets/check_in.dart';
import 'package:island/widgets/post/post_item.dart'; import 'package:island/widgets/post/post_item.dart';
import 'package:island/widgets/tour/tour.dart'; import 'package:island/widgets/tour/tour.dart';
import 'package:island/screens/tabs.dart';
import 'package:material_symbols_icons/symbols.dart'; import 'package:material_symbols_icons/symbols.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:riverpod_paging_utils/riverpod_paging_utils.dart'; import 'package:riverpod_paging_utils/riverpod_paging_utils.dart';
@ -46,7 +48,7 @@ class ExploreShellScreen extends ConsumerWidget {
} }
@RoutePage() @RoutePage()
class ExploreScreen extends ConsumerWidget { class ExploreScreen extends HookConsumerWidget {
final bool isAside; final bool isAside;
const ExploreScreen({super.key, this.isAside = false}); const ExploreScreen({super.key, this.isAside = false});
@ -57,11 +59,67 @@ class ExploreScreen extends ConsumerWidget {
return const EmptyPageHolder(); return const EmptyPageHolder();
} }
final activitiesNotifier = ref.watch(activityListNotifierProvider.notifier); final tabController = useTabController(initialLength: 3);
final currentFilter = useState<String?>(null);
useEffect(() {
void listener() {
switch (tabController.index) {
case 0:
currentFilter.value = null;
break;
case 1:
currentFilter.value = 'subscriptions';
break;
case 2:
currentFilter.value = 'friends';
break;
}
}
tabController.addListener(listener);
return () => tabController.removeListener(listener);
}, [tabController]);
final activitiesNotifier = ref.watch(
activityListNotifierProvider(currentFilter.value).notifier,
);
return TourTriggerWidget( return TourTriggerWidget(
child: AppScaffold( child: AppScaffold(
appBar: AppBar(title: const Text('explore').tr()), extendBody: false, // Prevent conflicts with tabs navigation
appBar: AppBar(
toolbarHeight: 0,
bottom: TabBar(
controller: tabController,
tabs: [
Tab(
child: Text(
'explore'.tr(),
style: TextStyle(
color: Theme.of(context).appBarTheme.foregroundColor!,
),
),
),
Tab(
child: Text(
'exploreFilterSubscriptions'.tr(),
style: TextStyle(
color: Theme.of(context).appBarTheme.foregroundColor!,
),
),
),
Tab(
child: Text(
'exploreFilterFriends'.tr(),
style: TextStyle(
color: Theme.of(context).appBarTheme.foregroundColor!,
),
),
),
],
),
),
floatingActionButton: FloatingActionButton( floatingActionButton: FloatingActionButton(
heroTag: Key("explore-page-fab"), heroTag: Key("explore-page-fab"),
onPressed: () { onPressed: () {
@ -73,33 +131,50 @@ class ExploreScreen extends ConsumerWidget {
}, },
child: const Icon(Symbols.edit), child: const Icon(Symbols.edit),
), ),
floatingActionButtonLocation: FloatingActionButtonLocation.endFloat, floatingActionButtonLocation: TabbedFabLocation(context),
body: RefreshIndicator( body: TabBarView(
onRefresh: () => Future.sync(activitiesNotifier.forceRefresh), controller: tabController,
child: PagingHelperView( children: [
provider: activityListNotifierProvider, _buildActivityList(ref, null),
futureRefreshable: activityListNotifierProvider.future, _buildActivityList(ref, 'subscriptions'),
notifierRefreshable: activityListNotifierProvider.notifier, _buildActivityList(ref, 'friends'),
contentBuilder: ],
(data, widgetCount, endItemView) => Center(
child: _ActivityListView(
data: data,
widgetCount: widgetCount,
endItemView: endItemView,
activitiesNotifier: activitiesNotifier,
),
),
),
), ),
), ),
); );
} }
Widget _buildActivityList(WidgetRef ref, String? filter) {
final activitiesNotifier = ref.watch(
activityListNotifierProvider(filter).notifier,
);
return RefreshIndicator(
onRefresh: () => Future.sync(activitiesNotifier.forceRefresh),
child: PagingHelperView(
provider: activityListNotifierProvider(filter),
futureRefreshable: activityListNotifierProvider(filter).future,
notifierRefreshable: activityListNotifierProvider(filter).notifier,
contentBuilder:
(data, widgetCount, endItemView) => Center(
child: _ActivityListView(
data: data,
widgetCount: widgetCount,
endItemView: endItemView,
activitiesNotifier: activitiesNotifier,
contentOnly: filter != null,
),
),
),
);
}
} }
class _ActivityListView extends HookConsumerWidget { class _ActivityListView extends HookConsumerWidget {
final CursorPagingData<SnActivity> data; final CursorPagingData<SnActivity> data;
final int widgetCount; final int widgetCount;
final Widget endItemView; final Widget endItemView;
final bool contentOnly;
final ActivityListNotifier activitiesNotifier; final ActivityListNotifier activitiesNotifier;
const _ActivityListView({ const _ActivityListView({
@ -107,6 +182,7 @@ class _ActivityListView extends HookConsumerWidget {
required this.widgetCount, required this.widgetCount,
required this.endItemView, required this.endItemView,
required this.activitiesNotifier, required this.activitiesNotifier,
this.contentOnly = false,
}); });
@override @override
@ -115,7 +191,8 @@ class _ActivityListView extends HookConsumerWidget {
return CustomScrollView( return CustomScrollView(
slivers: [ slivers: [
if (user.hasValue) SliverToBoxAdapter(child: CheckInWidget()), if (user.hasValue && !contentOnly)
SliverToBoxAdapter(child: CheckInWidget()),
SliverList.builder( SliverList.builder(
itemCount: widgetCount, itemCount: widgetCount,
itemBuilder: (context, index) { itemBuilder: (context, index) {
@ -174,7 +251,7 @@ class _ActivityListView extends HookConsumerWidget {
return Column(children: [itemWidget, const Divider(height: 1)]); return Column(children: [itemWidget, const Divider(height: 1)]);
}, },
), ),
SliverGap(MediaQuery.of(context).padding.bottom + 16), SliverGap(getTabbedPadding(context).bottom),
], ],
); );
} }
@ -184,16 +261,23 @@ class _ActivityListView extends HookConsumerWidget {
class ActivityListNotifier extends _$ActivityListNotifier class ActivityListNotifier extends _$ActivityListNotifier
with CursorPagingNotifierMixin<SnActivity> { with CursorPagingNotifierMixin<SnActivity> {
@override @override
Future<CursorPagingData<SnActivity>> build() => fetch(cursor: null); Future<CursorPagingData<SnActivity>> build(String? filter) =>
fetch(cursor: null);
@override @override
Future<CursorPagingData<SnActivity>> fetch({required String? cursor}) async { Future<CursorPagingData<SnActivity>> fetch({required String? cursor}) async {
final client = ref.read(apiClientProvider); final client = ref.read(apiClientProvider);
final take = 20; final take = 20;
final queryParameters = {
if (cursor != null) 'cursor': cursor,
'take': take,
if (filter != null) 'filter': filter,
};
final response = await client.get( final response = await client.get(
'/activities', '/activities',
queryParameters: {if (cursor != null) 'cursor': cursor, 'take': take}, queryParameters: queryParameters,
); );
final List<SnActivity> items = final List<SnActivity> items =

View File

@ -7,25 +7,174 @@ part of 'explore.dart';
// ************************************************************************** // **************************************************************************
String _$activityListNotifierHash() => String _$activityListNotifierHash() =>
r'c9683035f7a66a2f331689e274642b60064fbb2e'; r'14ec2f211c86e1e64a9a34b142d0e8f78ff6361a';
/// Copied from Dart SDK
class _SystemHash {
_SystemHash._();
static int combine(int hash, int value) {
// ignore: parameter_assignments
hash = 0x1fffffff & (hash + value);
// ignore: parameter_assignments
hash = 0x1fffffff & (hash + ((0x0007ffff & hash) << 10));
return hash ^ (hash >> 6);
}
static int finish(int hash) {
// ignore: parameter_assignments
hash = 0x1fffffff & (hash + ((0x03ffffff & hash) << 3));
// ignore: parameter_assignments
hash = hash ^ (hash >> 11);
return 0x1fffffff & (hash + ((0x00003fff & hash) << 15));
}
}
abstract class _$ActivityListNotifier
extends BuildlessAutoDisposeAsyncNotifier<CursorPagingData<SnActivity>> {
late final String? filter;
FutureOr<CursorPagingData<SnActivity>> build(String? filter);
}
/// See also [ActivityListNotifier]. /// See also [ActivityListNotifier].
@ProviderFor(ActivityListNotifier) @ProviderFor(ActivityListNotifier)
final activityListNotifierProvider = AutoDisposeAsyncNotifierProvider< const activityListNotifierProvider = ActivityListNotifierFamily();
ActivityListNotifier,
CursorPagingData<SnActivity> /// See also [ActivityListNotifier].
>.internal( class ActivityListNotifierFamily
ActivityListNotifier.new, extends Family<AsyncValue<CursorPagingData<SnActivity>>> {
name: r'activityListNotifierProvider', /// See also [ActivityListNotifier].
debugGetCreateSourceHash: const ActivityListNotifierFamily();
const bool.fromEnvironment('dart.vm.product')
? null /// See also [ActivityListNotifier].
: _$activityListNotifierHash, ActivityListNotifierProvider call(String? filter) {
dependencies: null, return ActivityListNotifierProvider(filter);
allTransitiveDependencies: null, }
);
@override
ActivityListNotifierProvider getProviderOverride(
covariant ActivityListNotifierProvider provider,
) {
return call(provider.filter);
}
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'activityListNotifierProvider';
}
/// See also [ActivityListNotifier].
class ActivityListNotifierProvider
extends
AutoDisposeAsyncNotifierProviderImpl<
ActivityListNotifier,
CursorPagingData<SnActivity>
> {
/// See also [ActivityListNotifier].
ActivityListNotifierProvider(String? filter)
: this._internal(
() => ActivityListNotifier()..filter = filter,
from: activityListNotifierProvider,
name: r'activityListNotifierProvider',
debugGetCreateSourceHash:
const bool.fromEnvironment('dart.vm.product')
? null
: _$activityListNotifierHash,
dependencies: ActivityListNotifierFamily._dependencies,
allTransitiveDependencies:
ActivityListNotifierFamily._allTransitiveDependencies,
filter: filter,
);
ActivityListNotifierProvider._internal(
super._createNotifier, {
required super.name,
required super.dependencies,
required super.allTransitiveDependencies,
required super.debugGetCreateSourceHash,
required super.from,
required this.filter,
}) : super.internal();
final String? filter;
@override
FutureOr<CursorPagingData<SnActivity>> runNotifierBuild(
covariant ActivityListNotifier notifier,
) {
return notifier.build(filter);
}
@override
Override overrideWith(ActivityListNotifier Function() create) {
return ProviderOverride(
origin: this,
override: ActivityListNotifierProvider._internal(
() => create()..filter = filter,
from: from,
name: null,
dependencies: null,
allTransitiveDependencies: null,
debugGetCreateSourceHash: null,
filter: filter,
),
);
}
@override
AutoDisposeAsyncNotifierProviderElement<
ActivityListNotifier,
CursorPagingData<SnActivity>
>
createElement() {
return _ActivityListNotifierProviderElement(this);
}
@override
bool operator ==(Object other) {
return other is ActivityListNotifierProvider && other.filter == filter;
}
@override
int get hashCode {
var hash = _SystemHash.combine(0, runtimeType.hashCode);
hash = _SystemHash.combine(hash, filter.hashCode);
return _SystemHash.finish(hash);
}
}
@Deprecated('Will be removed in 3.0. Use Ref instead')
// ignore: unused_element
mixin ActivityListNotifierRef
on AutoDisposeAsyncNotifierProviderRef<CursorPagingData<SnActivity>> {
/// The parameter `filter` of this provider.
String? get filter;
}
class _ActivityListNotifierProviderElement
extends
AutoDisposeAsyncNotifierProviderElement<
ActivityListNotifier,
CursorPagingData<SnActivity>
>
with ActivityListNotifierRef {
_ActivityListNotifierProviderElement(super.provider);
@override
String? get filter => (origin as ActivityListNotifierProvider).filter;
}
typedef _$ActivityListNotifier =
AutoDisposeAsyncNotifier<CursorPagingData<SnActivity>>;
// ignore_for_file: type=lint // 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 // 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

@ -7,7 +7,7 @@ part of 'notification.dart';
// ************************************************************************** // **************************************************************************
String _$notificationUnreadCountNotifierHash() => String _$notificationUnreadCountNotifierHash() =>
r'372a2cc259d7d838cd4f33a9129f7396ef31dbb9'; r'0d5b07caa625c24575c5581d5fcd3089effc2844';
/// See also [NotificationUnreadCountNotifier]. /// See also [NotificationUnreadCountNotifier].
@ProviderFor(NotificationUnreadCountNotifier) @ProviderFor(NotificationUnreadCountNotifier)

View File

@ -4,6 +4,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:gap/gap.dart'; import 'package:gap/gap.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:island/models/file.dart';
import 'package:island/models/post.dart'; import 'package:island/models/post.dart';
import 'package:island/screens/creators/publishers.dart'; import 'package:island/screens/creators/publishers.dart';
import 'package:island/screens/posts/compose_article.dart'; import 'package:island/screens/posts/compose_article.dart';
@ -71,13 +72,18 @@ class PostComposeScreen extends HookConsumerWidget {
final theme = Theme.of(context); final theme = Theme.of(context);
final colorScheme = theme.colorScheme; final colorScheme = theme.colorScheme;
// When editing, preserve the original replied/forwarded post references
final effectiveRepliedPost = repliedPost ?? originalPost?.repliedPost;
final effectiveForwardedPost = forwardedPost ?? originalPost?.forwardedPost;
final publishers = ref.watch(publishersManagedProvider); final publishers = ref.watch(publishersManagedProvider);
final state = useMemoized( final state = useMemoized(
() => ComposeLogic.createState( () => ComposeLogic.createState(
originalPost: originalPost, originalPost: originalPost,
forwardedPost: forwardedPost, forwardedPost: effectiveForwardedPost,
repliedPost: effectiveRepliedPost,
), ),
[originalPost, forwardedPost], [originalPost, effectiveForwardedPost, effectiveRepliedPost],
); );
// Initialize publisher once when data is available // Initialize publisher once when data is available
@ -148,17 +154,22 @@ class PostComposeScreen extends HookConsumerWidget {
), ),
itemCount: state.attachments.value.length, itemCount: state.attachments.value.length,
itemBuilder: (context, idx) { itemBuilder: (context, idx) {
return AttachmentPreview( return ValueListenableBuilder<Map<int, double>>(
item: state.attachments.value[idx], valueListenable: state.attachmentProgress,
progress: state.attachmentProgress.value[idx], builder: (context, progressMap, _) {
onRequestUpload: return AttachmentPreview(
() => ComposeLogic.uploadAttachment(ref, state, idx), item: state.attachments.value[idx],
onDelete: () => ComposeLogic.deleteAttachment(ref, state, idx), progress: progressMap[idx],
onMove: (delta) { onRequestUpload:
state.attachments.value = ComposeLogic.moveAttachment( () => ComposeLogic.uploadAttachment(ref, state, idx),
state.attachments.value, onDelete: () => ComposeLogic.deleteAttachment(ref, state, idx),
idx, onMove: (delta) {
delta, state.attachments.value = ComposeLogic.moveAttachment(
state.attachments.value,
idx,
delta,
);
},
); );
}, },
); );
@ -172,17 +183,22 @@ class PostComposeScreen extends HookConsumerWidget {
for (var idx = 0; idx < state.attachments.value.length; idx++) for (var idx = 0; idx < state.attachments.value.length; idx++)
Container( Container(
margin: const EdgeInsets.only(bottom: 8), margin: const EdgeInsets.only(bottom: 8),
child: AttachmentPreview( child: ValueListenableBuilder<Map<int, double>>(
item: state.attachments.value[idx], valueListenable: state.attachmentProgress,
progress: state.attachmentProgress.value[idx], builder: (context, progressMap, _) {
onRequestUpload: return AttachmentPreview(
() => ComposeLogic.uploadAttachment(ref, state, idx), item: state.attachments.value[idx],
onDelete: () => ComposeLogic.deleteAttachment(ref, state, idx), progress: progressMap[idx],
onMove: (delta) { onRequestUpload:
state.attachments.value = ComposeLogic.moveAttachment( () => ComposeLogic.uploadAttachment(ref, state, idx),
state.attachments.value, onDelete: () => ComposeLogic.deleteAttachment(ref, state, idx),
idx, onMove: (delta) {
delta, state.attachments.value = ComposeLogic.moveAttachment(
state.attachments.value,
idx,
delta,
);
},
); );
}, },
), ),
@ -323,12 +339,18 @@ class PostComposeScreen extends HookConsumerWidget {
const Gap(8), const Gap(8),
// Attachments preview // Attachments preview
LayoutBuilder( ValueListenableBuilder<List<UniversalFile>>(
builder: (context, constraints) { valueListenable: state.attachments,
final isWide = isWideScreen(context); builder: (context, attachments, _) {
return isWide if (attachments.isEmpty) return const SizedBox.shrink();
? buildWideAttachmentGrid() return LayoutBuilder(
: buildNarrowAttachmentList(); builder: (context, constraints) {
final isWide = isWideScreen(context);
return isWide
? buildWideAttachmentGrid()
: buildNarrowAttachmentList();
},
);
}, },
), ),
], ],
@ -367,7 +389,91 @@ class PostComposeScreen extends HookConsumerWidget {
} }
Widget _buildInfoBanner(BuildContext context) { Widget _buildInfoBanner(BuildContext context) {
// When editing, preserve the original replied/forwarded post references
final effectiveRepliedPost = repliedPost ?? originalPost?.repliedPost;
final effectiveForwardedPost = forwardedPost ?? originalPost?.forwardedPost;
// Show editing banner when editing a post
if (originalPost != null) { if (originalPost != null) {
return Column(
children: [
Container(
width: double.infinity,
color: Theme.of(context).colorScheme.primaryContainer,
child: Row(
children: [
Icon(
Symbols.edit,
size: 16,
color: Theme.of(context).colorScheme.onPrimaryContainer,
),
const Gap(4),
Text(
'edit'.tr(),
style: Theme.of(context).textTheme.labelMedium?.copyWith(
color: Theme.of(context).colorScheme.onPrimaryContainer,
),
),
],
).padding(all: 16),
),
// Show reply/forward banners below editing banner if they exist
if (effectiveRepliedPost != null)
Container(
width: double.infinity,
color: Theme.of(context).colorScheme.surfaceContainerHigh,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Icon(
Symbols.reply,
size: 16,
),
const Gap(4),
Text(
'postReplyingTo'.tr(),
style: Theme.of(context).textTheme.labelMedium,
),
],
),
const Gap(8),
_buildCompactReferencePost(context, effectiveRepliedPost),
],
).padding(all: 16),
),
if (effectiveForwardedPost != null)
Container(
width: double.infinity,
color: Theme.of(context).colorScheme.surfaceContainerHigh,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Icon(
Symbols.forward,
size: 16,
),
const Gap(4),
Text(
'postForwardingTo'.tr(),
style: Theme.of(context).textTheme.labelMedium,
),
],
),
const Gap(8),
_buildCompactReferencePost(context, effectiveForwardedPost),
],
).padding(all: 16),
),
],
);
}
// Show banner for replies (including when editing a reply)
if (effectiveRepliedPost != null) {
return Container( return Container(
width: double.infinity, width: double.infinity,
color: Theme.of(context).colorScheme.surfaceContainerHigh, color: Theme.of(context).colorScheme.surfaceContainerHigh,
@ -377,20 +483,46 @@ class PostComposeScreen extends HookConsumerWidget {
Row( Row(
children: [ children: [
Icon( Icon(
repliedPost != null ? Symbols.reply : Symbols.forward, Symbols.reply,
size: 16, size: 16,
), ),
const Gap(4), const Gap(4),
Text( Text(
repliedPost != null 'postReplyingTo'.tr(),
? 'postReplyingTo'.tr()
: 'postForwardingTo'.tr(),
style: Theme.of(context).textTheme.labelMedium, style: Theme.of(context).textTheme.labelMedium,
), ),
], ],
), ),
const Gap(8), const Gap(8),
PostItem(item: originalPost!, isOpenable: false), _buildCompactReferencePost(context, effectiveRepliedPost),
],
).padding(all: 16),
);
}
// Show banner for forwards (including when editing a forward)
if (effectiveForwardedPost != null) {
return Container(
width: double.infinity,
color: Theme.of(context).colorScheme.surfaceContainerHigh,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Icon(
Symbols.forward,
size: 16,
),
const Gap(4),
Text(
'postForwardingTo'.tr(),
style: Theme.of(context).textTheme.labelMedium,
),
],
),
const Gap(8),
_buildCompactReferencePost(context, effectiveForwardedPost),
], ],
).padding(all: 16), ).padding(all: 16),
); );
@ -398,4 +530,124 @@ class PostComposeScreen extends HookConsumerWidget {
return const SizedBox.shrink(); return const SizedBox.shrink();
} }
Widget _buildCompactReferencePost(BuildContext context, SnPost post) {
return GestureDetector(
onTap: () {
showModalBottomSheet(
context: context,
isScrollControlled: true,
builder: (context) => DraggableScrollableSheet(
initialChildSize: 0.7,
maxChildSize: 0.9,
minChildSize: 0.5,
builder: (context, scrollController) => Container(
decoration: BoxDecoration(
color: Theme.of(context).scaffoldBackgroundColor,
borderRadius: const BorderRadius.vertical(top: Radius.circular(16)),
),
child: Column(
children: [
Container(
width: 40,
height: 4,
margin: const EdgeInsets.symmetric(vertical: 8),
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.outline,
borderRadius: BorderRadius.circular(2),
),
),
Expanded(
child: SingleChildScrollView(
controller: scrollController,
padding: const EdgeInsets.all(16),
child: PostItem(item: post, isOpenable: false),
),
),
],
),
),
),
);
},
child: Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.surface,
borderRadius: BorderRadius.circular(8),
border: Border.all(
color: Theme.of(context).colorScheme.outline.withOpacity(0.3),
),
),
child: Row(
children: [
ProfilePictureWidget(
fileId: post.publisher.picture?.id,
radius: 16,
),
const Gap(8),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
post.publisher.nick,
style: const TextStyle(
fontWeight: FontWeight.bold,
fontSize: 14,
),
),
if (post.title?.isNotEmpty ?? false)
Text(
post.title!,
style: TextStyle(
fontWeight: FontWeight.w500,
fontSize: 13,
color: Theme.of(context).colorScheme.onSurface,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
if (post.content?.isNotEmpty ?? false)
Text(
post.content!,
style: TextStyle(
fontSize: 12,
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
if (post.attachments.isNotEmpty)
Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(
Symbols.attach_file,
size: 12,
color: Theme.of(context).colorScheme.secondary,
),
const Gap(4),
Text(
'postHasAttachments'.plural(post.attachments.length),
style: TextStyle(
color: Theme.of(context).colorScheme.secondary,
fontSize: 11,
),
),
],
),
],
),
),
Icon(
Symbols.open_in_full,
size: 16,
color: Theme.of(context).colorScheme.outline,
),
],
),
),
);
}
} }

View File

@ -6,6 +6,7 @@ import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:gap/gap.dart'; import 'package:gap/gap.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:island/models/file.dart';
import 'package:island/models/post.dart'; import 'package:island/models/post.dart';
import 'package:island/screens/creators/publishers.dart'; import 'package:island/screens/creators/publishers.dart';
import 'package:island/services/responsive.dart'; import 'package:island/services/responsive.dart';
@ -258,35 +259,48 @@ class ArticleComposeScreen extends HookConsumerWidget {
), ),
// Attachments preview // Attachments preview
if (state.attachments.value.isNotEmpty) ...[ ValueListenableBuilder<List<UniversalFile>>(
const Gap(16), valueListenable: state.attachments,
Wrap( builder: (context, attachments, _) {
spacing: 8, if (attachments.isEmpty) return const SizedBox.shrink();
runSpacing: 8, return Column(
children: [ children: [
for (var idx = 0; idx < state.attachments.value.length; idx++) const Gap(16),
SizedBox( ValueListenableBuilder<Map<int, double>>(
width: 120, valueListenable: state.attachmentProgress,
height: 120, builder: (context, progressMap, _) {
child: AttachmentPreview( return Wrap(
item: state.attachments.value[idx], spacing: 8,
progress: state.attachmentProgress.value[idx], runSpacing: 8,
onRequestUpload: children: [
() => ComposeLogic.uploadAttachment(ref, state, idx), for (var idx = 0; idx < attachments.length; idx++)
onDelete: SizedBox(
() => ComposeLogic.deleteAttachment(ref, state, idx), width: 120,
onMove: (delta) { height: 120,
state.attachments.value = ComposeLogic.moveAttachment( child: AttachmentPreview(
state.attachments.value, item: attachments[idx],
idx, progress: progressMap[idx],
delta, onRequestUpload:
); () => ComposeLogic.uploadAttachment(ref, state, idx),
}, onDelete:
), () => ComposeLogic.deleteAttachment(ref, state, idx),
onMove: (delta) {
state.attachments.value = ComposeLogic.moveAttachment(
state.attachments.value,
idx,
delta,
);
},
),
),
],
);
},
), ),
], ],
), );
], },
),
], ],
); );
} }
@ -295,6 +309,8 @@ class ArticleComposeScreen extends HookConsumerWidget {
appBar: AppBar( appBar: AppBar(
leading: const PageBackButton(), leading: const PageBackButton(),
actions: [ actions: [
// Info banner for article compose
const SizedBox.shrink(),
IconButton( IconButton(
icon: const Icon(Symbols.settings), icon: const Icon(Symbols.settings),
onPressed: showSettingsSheet, onPressed: showSettingsSheet,

View File

@ -161,48 +161,34 @@ 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: [appbarShadow],
),
),
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( SliverToBoxAdapter(
child: Row( child: Row(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
spacing: 20, spacing: 20,
children: [ children: [
ProfilePictureWidget(file: data.picture, radius: 32), GestureDetector(
child: Badge(
isLabelVisible: data.type == 0,
padding: EdgeInsets.all(4),
label: Icon(
Symbols.launch,
size: 16,
color: Theme.of(context).colorScheme.onPrimary,
),
backgroundColor:
Theme.of(context).colorScheme.primary,
offset: Offset(0, 48),
child: ProfilePictureWidget(
file: data.picture,
radius: 32,
),
),
onTap: () {
Navigator.pop(context, true);
context.router.pushPath('/account/${data.name}');
},
),
Expanded( Expanded(
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch, crossAxisAlignment: CrossAxisAlignment.stretch,
@ -242,19 +228,49 @@ class PublisherProfileScreen extends HookConsumerWidget {
uname: data.account!.name, uname: data.account!.name,
padding: EdgeInsets.zero, padding: EdgeInsets.zero,
), ),
OutlinedButton.icon( subStatus
onPressed: () { .when(
Navigator.pop(context); data:
context.router.pushPath( (status) => FilledButton.icon(
'/account/${data.name}', onPressed:
); subscribing.value
}, ? null
icon: const Icon(Symbols.launch), : (status.isSubscribed
label: Text('accountProfileView').tr(), ? unsubscribe
style: ButtonStyle( : subscribe),
visualDensity: VisualDensity(vertical: -2), icon: Icon(
), status.isSubscribed
).padding(top: 8), ? Symbols.remove_circle
: Symbols.add_circle,
),
label:
Text(
status.isSubscribed
? 'unsubscribe'
: 'subscribe',
).tr(),
style: ButtonStyle(
visualDensity: VisualDensity(
vertical: -2,
),
),
),
error: (_, _) => const SizedBox(),
loading:
() => const SizedBox(
height: 36,
child: Center(
child: SizedBox(
width: 20,
height: 20,
child: CircularProgressIndicator(
strokeWidth: 2,
),
),
),
),
)
.padding(top: 8),
], ],
), ),
), ),

View File

@ -13,11 +13,13 @@ import 'package:island/pods/config.dart';
import 'package:island/pods/network.dart'; import 'package:island/pods/network.dart';
import 'package:island/route.gr.dart'; import 'package:island/route.gr.dart';
import 'package:island/services/file.dart'; import 'package:island/services/file.dart';
import 'package:island/services/responsive.dart';
import 'package:island/widgets/alert.dart'; import 'package:island/widgets/alert.dart';
import 'package:island/widgets/app_scaffold.dart'; import 'package:island/widgets/app_scaffold.dart';
import 'package:island/widgets/content/cloud_files.dart'; import 'package:island/widgets/content/cloud_files.dart';
import 'package:island/widgets/content/sheet.dart'; import 'package:island/widgets/content/sheet.dart';
import 'package:island/widgets/response.dart'; import 'package:island/widgets/response.dart';
import 'package:island/screens/tabs.dart';
import 'package:material_symbols_icons/symbols.dart'; import 'package:material_symbols_icons/symbols.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:styled_widget/styled_widget.dart'; import 'package:styled_widget/styled_widget.dart';
@ -41,6 +43,7 @@ class RealmListScreen extends HookConsumerWidget {
final realmInvites = ref.watch(realmInvitesProvider); final realmInvites = ref.watch(realmInvitesProvider);
return AppScaffold( return AppScaffold(
extendBody: false, // Prevent conflicts with tabs navigation
noBackground: false, noBackground: false,
appBar: AppBar( appBar: AppBar(
title: const Text('realms').tr(), title: const Text('realms').tr(),
@ -83,6 +86,7 @@ class RealmListScreen extends HookConsumerWidget {
}); });
}, },
), ),
floatingActionButtonLocation: TabbedFabLocation(context),
body: RefreshIndicator( body: RefreshIndicator(
child: realms.when( child: realms.when(
data: data:
@ -90,9 +94,7 @@ class RealmListScreen extends HookConsumerWidget {
children: [ children: [
Expanded( Expanded(
child: ListView.builder( child: ListView.builder(
padding: EdgeInsets.only( padding: getTabbedPadding(context),
bottom: MediaQuery.of(context).padding.bottom,
),
itemCount: value.length, itemCount: value.length,
itemBuilder: (context, item) { itemBuilder: (context, item) {
return ListTile( return ListTile(

152
lib/screens/tabs.dart Normal file
View File

@ -0,0 +1,152 @@
import 'dart:ui';
import 'package:auto_route/auto_route.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:island/route.gr.dart';
import 'package:island/screens/notification.dart';
import 'package:island/services/responsive.dart';
import 'package:material_symbols_icons/symbols.dart';
@RoutePage()
class TabsScreen extends HookConsumerWidget {
const TabsScreen({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final useHorizontalLayout = isWideScreen(context);
final notificationUnreadCount = ref.watch(
notificationUnreadCountNotifierProvider,
);
final destinations = [
NavigationDestination(
label: 'explore'.tr(),
icon: const Icon(Symbols.explore),
),
NavigationDestination(label: 'chat'.tr(), icon: const Icon(Symbols.chat)),
NavigationDestination(
label: 'realms'.tr(),
icon: const Icon(Symbols.workspaces),
),
NavigationDestination(
label: 'account'.tr(),
icon: Badge.count(
count: notificationUnreadCount.value ?? 0,
isLabelVisible: (notificationUnreadCount.value ?? 0) > 0,
child: const Icon(Symbols.account_circle),
),
),
];
final routes = <PageRouteInfo>[
ExploreRoute(),
ChatListRoute(),
RealmListRoute(),
AccountRoute(),
];
return AutoTabsRouter.tabBar(
routes: routes,
scrollDirection: useHorizontalLayout ? Axis.vertical : Axis.horizontal,
physics: const NeverScrollableScrollPhysics(),
builder: (context, child, _) {
final tabsRouter = AutoTabsRouter.of(context);
if (isWideScreen(context)) {
return Row(
children: [
NavigationRail(
destinations:
destinations
.map(
(e) => NavigationRailDestination(
icon: e.icon,
label: Text(e.label),
),
)
.toList(),
selectedIndex: tabsRouter.activeIndex,
onDestinationSelected: tabsRouter.setActiveIndex,
),
const VerticalDivider(width: 1),
Expanded(child: child),
],
);
}
return Stack(
children: [
Positioned.fill(child: child),
Positioned(
left: 0,
right: 0,
bottom: 0,
child: ClipRRect(
child: BackdropFilter(
filter: ImageFilter.blur(sigmaX: 10, sigmaY: 10),
child: Container(
decoration: BoxDecoration(
color: Theme.of(
context,
).colorScheme.surface.withOpacity(0.8),
),
child: MediaQuery.removePadding(
context: context,
removeTop: true,
child: NavigationBar(
backgroundColor: Colors.transparent,
shadowColor: Colors.transparent,
overlayColor: WidgetStatePropertyAll(
Colors.transparent,
),
surfaceTintColor: Colors.transparent,
height: 56,
labelBehavior:
NavigationDestinationLabelBehavior.alwaysHide,
selectedIndex: tabsRouter.activeIndex,
onDestinationSelected: tabsRouter.setActiveIndex,
destinations: destinations,
),
),
),
),
),
),
],
);
},
);
}
}
class TabbedFabLocation extends FloatingActionButtonLocation {
final BuildContext context;
const TabbedFabLocation(this.context);
@override
Offset getOffset(ScaffoldPrelayoutGeometry scaffoldGeometry) {
final mediaQuery = MediaQuery.of(context);
final safeAreaPadding = mediaQuery.padding;
// Calculate position with proper safe area considerations
final double fabX =
scaffoldGeometry.scaffoldSize.width -
scaffoldGeometry.floatingActionButtonSize.width -
16.0 -
safeAreaPadding.right;
// Use safe area bottom padding + navigation bar height (typically 80px)
final double fabY =
scaffoldGeometry.scaffoldSize.height -
scaffoldGeometry.floatingActionButtonSize.height -
scaffoldGeometry.bottomSheetSize.height -
safeAreaPadding.bottom -
80.0 +
16;
return Offset(fabX, fabY);
}
}

View File

@ -15,3 +15,32 @@ bool isWiderScreen(BuildContext context) {
bool isWidestScreen(BuildContext context) { bool isWidestScreen(BuildContext context) {
return MediaQuery.of(context).size.width > kWidescreenWidth; return MediaQuery.of(context).size.width > kWidescreenWidth;
} }
EdgeInsets getTabbedPadding(
BuildContext context, {
double? horizontal,
double? vertical,
double? left,
double? right,
double? top,
double? bottom,
}) {
if (isWideScreen(context)) {
return EdgeInsets.only(
left: left ?? horizontal ?? 0,
right: right ?? horizontal ?? 0,
top: top ?? vertical ?? 0,
bottom: bottom ?? vertical ?? 0,
);
}
final effectiveBottom = bottom ?? vertical;
return EdgeInsets.only(
left: left ?? horizontal ?? 0,
right: right ?? horizontal ?? 0,
top: top ?? vertical ?? 0,
bottom:
effectiveBottom != null
? effectiveBottom + MediaQuery.of(context).padding.bottom + 56
: MediaQuery.of(context).padding.bottom + 56,
);
}

View File

@ -2,6 +2,7 @@ import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:gap/gap.dart'; import 'package:gap/gap.dart';
import 'package:island/models/user.dart'; import 'package:island/models/user.dart';
import 'package:island/models/wallet.dart';
import 'package:material_symbols_icons/symbols.dart'; import 'package:material_symbols_icons/symbols.dart';
import 'package:styled_widget/styled_widget.dart'; import 'package:styled_widget/styled_widget.dart';
@ -25,6 +26,8 @@ class AccountName extends StatelessWidget {
spacing: 4, spacing: 4,
children: [ children: [
Flexible(child: Text(account.nick, style: style)), Flexible(child: Text(account.nick, style: style)),
if (account.profile.stellarMembership != null)
StellarMembershipMark(membership: account.profile.stellarMembership!),
if (account.profile.verification != null) if (account.profile.verification != null)
VerificationMark(mark: account.profile.verification!), 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 { class VerificationStatusCard extends StatelessWidget {
final SnVerificationMark mark; final SnVerificationMark mark;
const VerificationStatusCard({super.key, required this.mark}); const VerificationStatusCard({super.key, required this.mark});

View File

@ -23,7 +23,6 @@ class LevelingProgressCard extends StatelessWidget {
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch, crossAxisAlignment: CrossAxisAlignment.stretch,
children: [ children: [
Text('levelingProgress').tr().fontSize(16).bold(),
Row( Row(
spacing: 8, spacing: 8,
crossAxisAlignment: CrossAxisAlignment.baseline, crossAxisAlignment: CrossAxisAlignment.baseline,

View File

@ -1,6 +1,7 @@
import 'package:easy_localization/easy_localization.dart'; import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:gap/gap.dart'; import 'package:gap/gap.dart';
import 'package:island/services/responsive.dart';
import 'package:styled_widget/styled_widget.dart'; import 'package:styled_widget/styled_widget.dart';
export 'content/alert.native.dart' export 'content/alert.native.dart'
@ -11,9 +12,21 @@ void showSnackBar(
String message, { String message, {
SnackBarAction? action, SnackBarAction? action,
}) { }) {
ScaffoldMessenger.of( ScaffoldMessenger.of(context).showSnackBar(
context, SnackBar(
).showSnackBar(SnackBar(content: Text(message), action: action)); content: Text(message),
action: action,
margin:
isWideScreen(context)
? null
: EdgeInsets.fromLTRB(
15.0,
5.0,
15.0,
MediaQuery.of(context).padding.bottom + 28,
),
),
);
} }
void clearSnackBar(BuildContext context) { void clearSnackBar(BuildContext context) {

View File

@ -92,7 +92,11 @@ class WindowScaffold extends HookConsumerWidget {
return Stack( return Stack(
fit: StackFit.expand, fit: StackFit.expand,
children: [child, _WebSocketIndicator(), AppNotificationToast()], children: [
Positioned.fill(child: child),
_WebSocketIndicator(),
AppNotificationToast(),
],
); );
} }
} }
@ -112,6 +116,7 @@ class AppScaffold extends StatelessWidget {
final DrawerCallback? onDrawerChanged; final DrawerCallback? onDrawerChanged;
final DrawerCallback? onEndDrawerChanged; final DrawerCallback? onEndDrawerChanged;
final bool? noBackground; final bool? noBackground;
final bool? extendBody;
const AppScaffold({ const AppScaffold({
super.key, super.key,
@ -127,6 +132,7 @@ class AppScaffold extends StatelessWidget {
this.onDrawerChanged, this.onDrawerChanged,
this.onEndDrawerChanged, this.onEndDrawerChanged,
this.noBackground, this.noBackground,
this.extendBody,
}); });
@override @override
@ -146,7 +152,7 @@ class AppScaffold extends StatelessWidget {
); );
return Scaffold( return Scaffold(
extendBody: true, extendBody: extendBody ?? true,
extendBodyBehindAppBar: true, extendBodyBehindAppBar: true,
backgroundColor: backgroundColor:
noBackground noBackground

View File

@ -4,10 +4,11 @@ import 'package:flutter/material.dart';
import 'package:gap/gap.dart'; import 'package:gap/gap.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:island/pods/call.dart'; import 'package:island/pods/call.dart';
import 'package:island/pods/userinfo.dart';
import 'package:island/route.gr.dart'; import 'package:island/route.gr.dart';
import 'package:island/widgets/chat/call_participant_tile.dart'; import 'package:island/widgets/chat/call_participant_tile.dart';
import 'package:island/widgets/content/sheet.dart';
import 'package:styled_widget/styled_widget.dart'; import 'package:styled_widget/styled_widget.dart';
import 'package:livekit_client/livekit_client.dart';
class CallControlsBar extends HookConsumerWidget { class CallControlsBar extends HookConsumerWidget {
const CallControlsBar({super.key}); const CallControlsBar({super.key});
@ -17,76 +18,227 @@ class CallControlsBar extends HookConsumerWidget {
final callState = ref.watch(callNotifierProvider); final callState = ref.watch(callNotifierProvider);
final callNotifier = ref.read(callNotifierProvider.notifier); final callNotifier = ref.read(callNotifierProvider.notifier);
final userInfo = ref.watch(userInfoProvider); return Container(
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 16),
final actionButtonStyle = ButtonStyle(
minimumSize: const MaterialStatePropertyAll(Size(24, 24)),
);
return Card(
margin: const EdgeInsets.only(left: 12, right: 12, top: 8),
child: Row( child: Row(
crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
Expanded( _buildCircularButtonWithDropdown(
child: Row( context: context,
children: [ ref: ref,
Builder( icon:
builder: (context) { callState.isCameraEnabled ? Icons.videocam : Icons.videocam_off,
if (callNotifier.localParticipant == null) { onPressed: () => callNotifier.toggleCamera(),
return CircularProgressIndicator().center(); backgroundColor: const Color(0xFF424242),
} hasDropdown: true,
return SizedBox( deviceType: 'videoinput',
width: 40,
height: 40,
child:
SpeakingRippleAvatar(
isSpeaking:
callNotifier.localParticipant!.isSpeaking,
audioLevel:
callNotifier.localParticipant!.audioLevel,
pictureId: userInfo.value?.profile.picture?.id,
size: 36,
).center(),
);
},
),
],
),
), ),
IconButton( const Gap(16),
icon: Icon( _buildCircularButton(
callState.isMicrophoneEnabled ? Icons.mic : Icons.mic_off, icon:
), callState.isScreenSharing
onPressed: () { ? Icons.stop_screen_share
callNotifier.toggleMicrophone(); : Icons.screen_share,
}, onPressed: () => callNotifier.toggleScreenShare(),
style: actionButtonStyle, backgroundColor: const Color(0xFF424242),
), ),
IconButton( const Gap(16),
icon: Icon( _buildCircularButtonWithDropdown(
callState.isCameraEnabled ? Icons.videocam : Icons.videocam_off, context: context,
), ref: ref,
onPressed: () { icon: callState.isMicrophoneEnabled ? Icons.mic : Icons.mic_off,
callNotifier.toggleCamera(); onPressed: () => callNotifier.toggleMicrophone(),
}, backgroundColor: const Color(0xFF424242),
style: actionButtonStyle, hasDropdown: true,
deviceType: 'audioinput',
), ),
IconButton( const Gap(16),
icon: Icon( _buildCircularButton(
callState.isScreenSharing icon: Icons.call_end,
? Icons.stop_screen_share onPressed: () => callNotifier.disconnect(),
: Icons.screen_share, backgroundColor: const Color(0xFFE53E3E),
), iconColor: Colors.white,
onPressed: () {
callNotifier.toggleScreenShare();
},
style: actionButtonStyle,
), ),
], ],
).padding(all: 16), ),
); );
} }
Widget _buildCircularButton({
required IconData icon,
required VoidCallback onPressed,
required Color backgroundColor,
Color? iconColor,
}) {
return Container(
width: 56,
height: 56,
decoration: BoxDecoration(color: backgroundColor, shape: BoxShape.circle),
child: IconButton(
icon: Icon(icon, color: iconColor ?? Colors.white, size: 24),
onPressed: onPressed,
),
);
}
Widget _buildCircularButtonWithDropdown({
required BuildContext context,
required WidgetRef ref,
required IconData icon,
required VoidCallback onPressed,
required Color backgroundColor,
required bool hasDropdown,
Color? iconColor,
String? deviceType, // 'videoinput' or 'audioinput'
}) {
return Stack(
children: [
Container(
width: 56,
height: 56,
decoration: BoxDecoration(
color: backgroundColor,
shape: BoxShape.circle,
),
child: IconButton(
icon: Icon(icon, color: iconColor ?? Colors.white, size: 24),
onPressed: onPressed,
),
),
if (hasDropdown && deviceType != null)
Positioned(
bottom: 4,
right: 4,
child: GestureDetector(
onTap: () => _showDeviceSelectionDialog(context, ref, deviceType),
child: Container(
width: 16,
height: 16,
decoration: BoxDecoration(
color: backgroundColor.withOpacity(0.8),
shape: BoxShape.circle,
border: Border.all(
color: Colors.white.withOpacity(0.3),
width: 0.5,
),
),
child: Icon(
Icons.arrow_drop_down,
color: Colors.white,
size: 12,
),
),
),
),
],
);
}
Future<void> _showDeviceSelectionDialog(
BuildContext context,
WidgetRef ref,
String deviceType,
) async {
try {
final devices = await Hardware.instance.enumerateDevices(
type: deviceType,
);
if (!context.mounted) return;
showModalBottomSheet(
context: context,
builder: (BuildContext dialogContext) {
return SheetScaffold(
titleText:
deviceType == 'videoinput'
? 'selectCamera'.tr()
: 'selectMicrophone'.tr(),
child: ListView.builder(
itemCount: devices.length,
itemBuilder: (context, index) {
final device = devices[index];
return ListTile(
title: Text(
device.label.isNotEmpty
? device.label
: '${'device'.tr()} ${index + 1}',
),
onTap: () {
Navigator.of(dialogContext).pop();
_switchDevice(context, ref, device, deviceType);
},
);
},
),
);
},
);
} catch (e) {
if (context.mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('${'failedToEnumerateDevices'.tr()}: $e'),
backgroundColor: Colors.red,
),
);
}
}
}
Future<void> _switchDevice(
BuildContext context,
WidgetRef ref,
MediaDevice device,
String deviceType,
) async {
try {
final callNotifier = ref.read(callNotifierProvider.notifier);
if (deviceType == 'videoinput') {
// Switch camera device
final localParticipant = callNotifier.room?.localParticipant;
final videoTrack =
localParticipant?.videoTrackPublications.firstOrNull?.track;
if (videoTrack is LocalVideoTrack) {
await videoTrack.switchCamera(device.deviceId);
}
} else if (deviceType == 'audioinput') {
// Switch microphone device
final localParticipant = callNotifier.room?.localParticipant;
final audioTrack =
localParticipant?.audioTrackPublications.firstOrNull?.track;
if (audioTrack is LocalAudioTrack) {
// For audio devices, we need to restart the track with new device
await audioTrack.restartTrack(
AudioCaptureOptions(deviceId: device.deviceId),
);
}
}
if (context.mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(
'${'switchedTo'.tr()} ${device.label.isNotEmpty ? device.label : 'selectedDevice'.tr()}',
),
backgroundColor: Colors.green,
),
);
}
} catch (e) {
if (context.mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('${'failedToSwitchDevice'.tr()}: $e'),
backgroundColor: Colors.red,
),
);
}
}
}
} }
class CallOverlayBar extends HookConsumerWidget { class CallOverlayBar extends HookConsumerWidget {

View File

@ -1,4 +1,5 @@
import 'dart:io'; import 'dart:io';
import 'dart:math' as math;
import 'package:easy_localization/easy_localization.dart'; import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
@ -8,12 +9,14 @@ import 'package:gap/gap.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:island/database/message.dart'; import 'package:island/database/message.dart';
import 'package:island/models/chat.dart'; import 'package:island/models/chat.dart';
import 'package:island/models/embed.dart';
import 'package:island/pods/call.dart'; import 'package:island/pods/call.dart';
import 'package:island/screens/chat/room.dart'; import 'package:island/screens/chat/room.dart';
import 'package:island/widgets/account/account_pfc.dart'; import 'package:island/widgets/account/account_pfc.dart';
import 'package:island/widgets/app_scaffold.dart'; import 'package:island/widgets/app_scaffold.dart';
import 'package:island/widgets/content/cloud_file_collection.dart'; import 'package:island/widgets/content/cloud_file_collection.dart';
import 'package:island/widgets/content/cloud_files.dart'; import 'package:island/widgets/content/cloud_files.dart';
import 'package:island/widgets/content/embed/link.dart';
import 'package:island/widgets/content/markdown.dart'; import 'package:island/widgets/content/markdown.dart';
import 'package:material_symbols_icons/material_symbols_icons.dart'; import 'package:material_symbols_icons/material_symbols_icons.dart';
import 'package:styled_widget/styled_widget.dart'; import 'package:styled_widget/styled_widget.dart';
@ -227,6 +230,20 @@ class MessageItem extends HookConsumerWidget {
).padding(vertical: 4); ).padding(vertical: 4);
}, },
), ),
if (remoteMessage.meta['embeds'] != null)
...((remoteMessage.meta['embeds'] as List<dynamic>)
.where((embed) => embed['Type'] == 'link')
.map((embed) => SnEmbedLink.fromJson(embed as Map<String, dynamic>))
.map((link) => LayoutBuilder(
builder: (context, constraints) {
return EmbedLinkWidget(
link: link,
maxWidth: math.min(constraints.maxWidth, 480),
margin: const EdgeInsets.symmetric(vertical: 4),
);
},
))
.toList()),
if (progress != null && progress!.isNotEmpty) if (progress != null && progress!.isNotEmpty)
Column( Column(
crossAxisAlignment: CrossAxisAlignment.stretch, crossAxisAlignment: CrossAxisAlignment.stretch,

View File

@ -0,0 +1,208 @@
import 'package:flutter/material.dart';
import 'package:gap/gap.dart';
import 'package:island/models/embed.dart';
import 'package:island/widgets/content/image.dart';
import 'package:material_symbols_icons/symbols.dart';
import 'package:url_launcher/url_launcher.dart';
class EmbedLinkWidget extends StatelessWidget {
final SnEmbedLink link;
final double? maxWidth;
final EdgeInsetsGeometry? margin;
const EmbedLinkWidget({
super.key,
required this.link,
this.maxWidth,
this.margin,
});
Future<void> _launchUrl() async {
final uri = Uri.parse(link.url);
if (await canLaunchUrl(uri)) {
await launchUrl(uri, mode: LaunchMode.externalApplication);
}
}
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
final colorScheme = theme.colorScheme;
return Container(
width: maxWidth,
margin: margin ?? const EdgeInsets.symmetric(vertical: 8),
child: Card(
margin: EdgeInsets.zero,
clipBehavior: Clip.antiAlias,
child: InkWell(
onTap: _launchUrl,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Preview Image
if (link.imageUrl != null && link.imageUrl!.isNotEmpty)
AspectRatio(
aspectRatio: 16 / 9,
child: UniversalImage(uri: link.imageUrl!, fit: BoxFit.cover),
),
// Content
Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Site info row
Row(
children: [
// Favicon
if (link.faviconUrl.isNotEmpty) ...[
ClipRRect(
borderRadius: BorderRadius.circular(4),
child: UniversalImage(
uri: link.faviconUrl,
width: 16,
height: 16,
fit: BoxFit.cover,
),
),
const Gap(8),
] else ...[
Icon(
Symbols.link,
size: 16,
color: colorScheme.onSurfaceVariant,
),
const Gap(8),
],
// Site name
Expanded(
child: Text(
link.siteName.isNotEmpty
? link.siteName
: Uri.parse(link.url).host,
style: theme.textTheme.bodySmall?.copyWith(
color: colorScheme.onSurfaceVariant,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
),
// External link icon
Icon(
Symbols.open_in_new,
size: 16,
color: colorScheme.onSurfaceVariant,
),
],
),
const Gap(8),
// Title
if (link.title.isNotEmpty) ...[
Text(
link.title,
style: theme.textTheme.titleMedium?.copyWith(
fontWeight: FontWeight.w600,
),
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
const Gap(4),
],
// Description
if (link.description != null && link.description!.isNotEmpty) ...[
Text(
link.description!,
style: theme.textTheme.bodyMedium?.copyWith(
color: colorScheme.onSurfaceVariant,
),
maxLines: 3,
overflow: TextOverflow.ellipsis,
),
const Gap(8),
],
// URL
Text(
link.url,
style: theme.textTheme.bodySmall?.copyWith(
color: colorScheme.primary,
decoration: TextDecoration.underline,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
// Author and publish date
if (link.author != null || link.publishedDate != null) ...[
const Gap(8),
Row(
children: [
if (link.author != null) ...[
Icon(
Symbols.person,
size: 14,
color: colorScheme.onSurfaceVariant,
),
const Gap(4),
Text(
link.author!,
style: theme.textTheme.bodySmall?.copyWith(
color: colorScheme.onSurfaceVariant,
),
),
],
if (link.author != null && link.publishedDate != null)
const Gap(16),
if (link.publishedDate != null) ...[
Icon(
Symbols.schedule,
size: 14,
color: colorScheme.onSurfaceVariant,
),
const Gap(4),
Text(
_formatDate(link.publishedDate!),
style: theme.textTheme.bodySmall?.copyWith(
color: colorScheme.onSurfaceVariant,
),
),
],
],
),
],
],
),
),
],
),
),
),
);
}
String _formatDate(DateTime date) {
try {
final now = DateTime.now();
final difference = now.difference(date);
if (difference.inDays == 0) {
return 'Today';
} else if (difference.inDays == 1) {
return 'Yesterday';
} else if (difference.inDays < 7) {
return '${difference.inDays} days ago';
} else {
return '${date.day}/${date.month}/${date.year}';
}
} catch (e) {
return date.toString();
}
}
}

View File

@ -0,0 +1,243 @@
# Payment Overlay Widget
A reusable payment verification overlay that supports both 6-digit PIN input and biometric authentication for secure payment processing.
## Features
- **6-digit PIN Input**: Secure numeric PIN entry with automatic focus management
- **Biometric Authentication**: Support for fingerprint and face recognition
- **Order Summary**: Display payment details including amount, description, and remarks
- **Integrated API Calls**: Automatically handles payment processing via `/orders/{orderId}/pay`
- **Error Handling**: Comprehensive error handling with user-friendly messages
- **Loading States**: Visual feedback during payment processing
- **Responsive Design**: Adapts to different screen sizes and orientations
- **Customizable**: Flexible callbacks and styling options
- **Accessibility**: Screen reader support and proper focus management
- **Localization**: Full i18n support with easy_localization
## Usage
```dart
import 'package:flutter/material.dart';
import 'package:solian/models/wallet.dart';
import 'package:solian/widgets/payment/payment_overlay.dart';
// Create an order
final order = SnWalletOrder(
id: 'order_123',
amount: 2500, // $25.00 in cents
currency: 'USD',
description: 'Premium Subscription',
remarks: 'Monthly billing',
status: 'pending',
);
// Show payment overlay
PaymentOverlay.show(
context: context,
order: order,
onPaymentSuccess: (completedOrder) {
// Handle successful payment
print('Payment completed: ${completedOrder.id}');
// Navigate to success page or update UI
},
onPaymentError: (error) {
// Handle payment error
print('Payment failed: $error');
// Show error message to user
},
onCancel: () {
Navigator.of(context).pop();
print('Payment cancelled');
},
enableBiometric: true,
);
```
### Advanced Usage with Loading States
```dart
bool isLoading = false;
PaymentOverlay.show(
context: context,
order: order,
enableBiometric: true,
isLoading: isLoading,
onPinSubmit: (String pin) async {
setState(() => isLoading = true);
try {
await processPaymentWithPin(pin);
Navigator.of(context).pop();
} catch (e) {
showErrorDialog(e.toString());
} finally {
setState(() => isLoading = false);
}
},
onBiometricAuth: () async {
setState(() => isLoading = true);
try {
final authenticated = await authenticateWithBiometrics();
if (authenticated) {
await processPaymentWithBiometrics();
Navigator.of(context).pop();
}
} catch (e) {
showErrorDialog(e.toString());
} finally {
setState(() => isLoading = false);
}
},
);
```
## Parameters
### PaymentOverlay.show()
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `context` | `BuildContext` | ✅ | The build context for showing the overlay |
| `order` | `SnWalletOrder` | ✅ | The order to be paid |
| `onPaymentSuccess` | `Function(SnWalletOrder)?` | ❌ | Callback when payment succeeds with completed order |
| `onPaymentError` | `Function(String)?` | ❌ | Callback when payment fails with error message |
| `onCancel` | `VoidCallback?` | ❌ | Callback when payment is cancelled |
| `enableBiometric` | `bool` | ❌ | Whether to show biometric option (default: true) |
## API Integration
The PaymentOverlay automatically handles payment processing by calling the `/orders/{orderId}/pay` endpoint with the following request body:
### PIN Payment
```json
{
"pin": "123456"
}
```
### Biometric Payment
```json
{
"biometric": true
}
```
### Response
The API should return the completed `SnWalletOrder` object:
```json
{
"id": "order_123",
"amount": 2500,
"currency": "USD",
"description": "Premium Subscription",
"status": "completed",
"processorReference": "txn_abc123",
// ... other order fields
}
```
### Error Handling
The widget handles common HTTP status codes:
- `401`: Invalid PIN or biometric authentication failed
- `400`: Bad request with custom error message
- Other errors: Generic payment failed message
### Implementation Example
```dart
import 'package:local_auth/local_auth.dart';
class BiometricService {
final LocalAuthentication _auth = LocalAuthentication();
Future<bool> isAvailable() async {
final isAvailable = await _auth.canCheckBiometrics;
final isDeviceSupported = await _auth.isDeviceSupported();
return isAvailable && isDeviceSupported;
}
Future<bool> authenticate() async {
try {
final bool didAuthenticate = await _auth.authenticate(
localizedReason: 'Please authenticate to complete payment',
options: const AuthenticationOptions(
biometricOnly: true,
stickyAuth: true,
),
);
return didAuthenticate;
} catch (e) {
print('Biometric authentication error: $e');
return false;
}
}
}
```
## Localization
Add these keys to your localization files:
```json
{
"paymentVerification": "Payment Verification",
"paymentSummary": "Payment Summary",
"amount": "Amount",
"description": "Description",
"pinCode": "PIN Code",
"biometric": "Biometric",
"enterPinToConfirm": "Enter your 6-digit PIN to confirm payment",
"clearPin": "Clear PIN",
"useBiometricToConfirm": "Use biometric authentication to confirm payment",
"touchSensorToAuthenticate": "Touch the sensor to authenticate",
"authenticating": "Authenticating...",
"authenticateNow": "Authenticate Now",
"confirm": "Confirm",
"cancel": "Cancel",
"paymentFailed": "Payment failed. Please try again.",
"invalidPin": "Invalid PIN. Please try again.",
"biometricAuthFailed": "Biometric authentication failed. Please try again.",
"paymentSuccess": "Payment completed successfully!",
"paymentError": "Payment failed: {error}"
}
```
## Styling
The widget automatically adapts to your app's theme. It uses:
- `Theme.of(context).colorScheme.primary` for primary elements
- `Theme.of(context).colorScheme.surface` for backgrounds
- `Theme.of(context).textTheme` for typography
## Security Considerations
1. **PIN Handling**: The PIN is passed as a string to your callback. Ensure you handle it securely and don't log it.
2. **Biometric Authentication**: Always verify biometric authentication on your backend.
3. **Network Security**: Use HTTPS for all payment-related API calls.
4. **Data Validation**: Validate all payment data on your backend before processing.
## Example Integration
See `payment_overlay_example.dart` for a complete working example that demonstrates:
- How to show the overlay
- Handling PIN and biometric authentication
- Processing payments
- Error handling
- Loading states
## Dependencies
- `flutter/material.dart` - Material Design components
- `flutter/services.dart` - Input formatters and system services
- `flutter_riverpod/flutter_riverpod.dart` - State management and dependency injection
- `gap/gap.dart` - Spacing widgets
- `material_symbols_icons/symbols.dart` - Material Symbols icons
- `easy_localization/easy_localization.dart` - Internationalization
- `dio/dio.dart` - HTTP client for API calls
- `solian/models/wallet.dart` - Wallet order model
- `solian/widgets/common/sheet_scaffold.dart` - Sheet scaffold widget
- `solian/pods/network.dart` - API client provider

View File

@ -0,0 +1,479 @@
import 'package:flutter/material.dart';
import 'package:flutter_otp_text_field/flutter_otp_text_field.dart';
import 'package:gap/gap.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:island/widgets/alert.dart';
import 'package:material_symbols_icons/symbols.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:island/models/wallet.dart';
import 'package:island/widgets/content/sheet.dart';
import 'package:island/pods/network.dart';
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;
final Function(SnWalletOrder completedOrder)? onPaymentSuccess;
final Function(String error)? onPaymentError;
final VoidCallback? onCancel;
final bool enableBiometric;
const PaymentOverlay({
super.key,
required this.order,
this.onPaymentSuccess,
this.onPaymentError,
this.onCancel,
this.enableBiometric = true,
});
@override
Widget build(BuildContext context, WidgetRef ref) {
return Container(
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.surface,
borderRadius: const BorderRadius.vertical(top: Radius.circular(16)),
),
child: Padding(
padding: EdgeInsets.only(
bottom: MediaQuery.of(context).viewInsets.bottom,
),
child: SheetScaffold(
titleText: 'Solarpay',
heightFactor: 0.7,
child: _PaymentContent(
order: order,
onPaymentSuccess: onPaymentSuccess,
onPaymentError: onPaymentError,
onCancel: onCancel,
enableBiometric: enableBiometric,
),
),
),
);
}
static Future<SnWalletOrder?> show({
required BuildContext context,
required SnWalletOrder order,
bool enableBiometric = true,
}) {
return showModalBottomSheet<SnWalletOrder>(
context: context,
isScrollControlled: true,
backgroundColor: Colors.transparent,
useSafeArea: true,
builder:
(context) => PaymentOverlay(
order: order,
enableBiometric: enableBiometric,
onPaymentSuccess: (completedOrder) {
Navigator.of(context).pop(completedOrder);
},
onPaymentError: (err) {
Navigator.of(context).pop();
showErrorAlert(err);
},
onCancel: () {
Navigator.of(context).pop();
},
),
);
}
}
class _PaymentContent extends ConsumerStatefulWidget {
final SnWalletOrder order;
final Function(SnWalletOrder)? onPaymentSuccess;
final Function(String)? onPaymentError;
final VoidCallback? onCancel;
final bool enableBiometric;
const _PaymentContent({
required this.order,
this.onPaymentSuccess,
this.onPaymentError,
this.onCancel,
this.enableBiometric = true,
});
@override
ConsumerState<_PaymentContent> createState() => _PaymentContentState();
}
class _PaymentContentState extends ConsumerState<_PaymentContent> {
static const String _pinStorageKey = 'app_pin_code';
static final _secureStorage = FlutterSecureStorage();
final LocalAuthentication _localAuth = LocalAuthentication();
String _pin = '';
bool _isPinMode = true;
bool _hasBiometricSupport = false;
bool _hasStoredPin = false;
@override
void initState() {
super.initState();
_initializeBiometric();
}
@override
void dispose() {
super.dispose();
}
Future<void> _initializeBiometric() async {
try {
// Check if biometric is available
final isAvailable = await _localAuth.isDeviceSupported();
final canCheckBiometrics = await _localAuth.canCheckBiometrics;
_hasBiometricSupport = isAvailable && canCheckBiometrics;
// Check if PIN is stored
final storedPin = await _secureStorage.read(key: _pinStorageKey);
_hasStoredPin = storedPin != null && storedPin.isNotEmpty;
// Set initial mode based on stored PIN and biometric support
if (_hasStoredPin && _hasBiometricSupport && widget.enableBiometric) {
_isPinMode = false;
// Automatically trigger biometric authentication
WidgetsBinding.instance.addPostFrameCallback((_) {
_authenticateWithBiometric();
});
} else {
_isPinMode = true;
}
if (mounted) {
setState(() {});
}
} catch (e) {
// Fallback to PIN mode if biometric setup fails
_isPinMode = true;
if (mounted) {
setState(() {});
}
}
}
void _onPinSubmit(String pin) {
_pin = pin;
if (pin.length == 6) {
_processPaymentWithPin(pin);
}
}
Future<void> _processPaymentWithPin(String pin) async {
showLoadingModal(context);
try {
// Store PIN securely for future biometric authentication
if (_hasBiometricSupport && widget.enableBiometric && !_hasStoredPin) {
await _secureStorage.write(key: _pinStorageKey, value: pin);
_hasStoredPin = true;
}
await _makePaymentRequest(pin);
} catch (err) {
widget.onPaymentError?.call(err.toString());
_pin = '';
} finally {
if (mounted) {
hideLoadingModal(context);
}
}
}
Future<void> _authenticateWithBiometric() async {
showLoadingModal(context);
try {
// Perform biometric authentication
final bool didAuthenticate = await _localAuth.authenticate(
localizedReason: 'biometricPrompt'.tr(),
options: const AuthenticationOptions(
biometricOnly: true,
stickyAuth: true,
),
);
if (didAuthenticate) {
// Retrieve stored PIN and process payment
final storedPin = await _secureStorage.read(key: _pinStorageKey);
if (storedPin != null && storedPin.isNotEmpty) {
await _makePaymentRequest(storedPin);
} else {
// Fallback to PIN mode if no stored PIN
_fallbackToPinMode('noStoredPin'.tr());
}
} else {
// Biometric authentication failed, fallback to PIN mode
_fallbackToPinMode('biometricAuthFailed'.tr());
}
} catch (err) {
// Handle biometric authentication errors
String errorMessage = 'biometricAuthFailed'.tr();
if (err is PlatformException) {
switch (err.code) {
case 'NotAvailable':
errorMessage = 'biometricNotAvailable'.tr();
break;
case 'NotEnrolled':
errorMessage = 'biometricNotEnrolled'.tr();
break;
case 'LockedOut':
case 'PermanentlyLockedOut':
errorMessage = 'biometricLockedOut'.tr();
break;
default:
errorMessage = 'biometricAuthFailed'.tr();
}
}
_fallbackToPinMode(errorMessage);
} finally {
if (mounted) {
hideLoadingModal(context);
}
}
}
/// Unified method for making payment requests with PIN
Future<void> _makePaymentRequest(String pin) async {
try {
final client = ref.read(apiClientProvider);
final response = await client.post(
'/orders/${widget.order.id}/pay',
data: {'pin_code': pin},
);
final completedOrder = SnWalletOrder.fromJson(response.data);
widget.onPaymentSuccess?.call(completedOrder);
} catch (err) {
String errorMessage = 'paymentFailed'.tr();
if (err is DioException) {
if (err.response?.statusCode == 403 ||
err.response?.statusCode == 401) {
// PIN is invalid
errorMessage = 'invalidPin'.tr();
// If this was a biometric attempt with stored PIN, remove the stored PIN
if (!_isPinMode) {
await _secureStorage.delete(key: _pinStorageKey);
_hasStoredPin = false;
_fallbackToPinMode(errorMessage);
return;
}
} else if (err.response?.statusCode == 400) {
errorMessage = err.response?.data?['error'] ?? errorMessage;
}
}
throw errorMessage;
}
}
void _fallbackToPinMode(String? message) {
setState(() {
_isPinMode = true;
});
if (message != null && message.isNotEmpty) {
showSnackBar(context, message);
}
}
String _formatCurrency(int amount, String currency) {
final value = amount / 100.0;
return '${value.toStringAsFixed(2)} $currency';
}
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(20),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Order Summary
_buildOrderSummary(),
const Gap(32),
// Authentication Content
Expanded(
child: _isPinMode ? _buildPinInput() : _buildBiometricAuth(),
),
// Action Buttons
const Gap(24),
_buildActionButtons(),
],
),
);
}
Widget _buildOrderSummary() {
return Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.surfaceContainerHighest,
borderRadius: BorderRadius.circular(12),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Icon(
Symbols.receipt,
color: Theme.of(context).colorScheme.primary,
),
const Gap(8),
Text(
'paymentSummary'.tr(),
style: Theme.of(
context,
).textTheme.titleMedium?.copyWith(fontWeight: FontWeight.w600),
),
],
),
const Gap(12),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
'amount'.tr(),
style: Theme.of(context).textTheme.bodyMedium,
),
Text(
_formatCurrency(widget.order.amount, widget.order.currency),
style: Theme.of(
context,
).textTheme.titleMedium?.copyWith(fontWeight: FontWeight.w600),
),
],
),
if (widget.order.remarks != null) ...[
const Gap(8),
Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'description'.tr(),
style: Theme.of(
context,
).textTheme.bodyMedium?.copyWith(fontWeight: FontWeight.w600),
),
const Spacer(),
Expanded(
flex: 2,
child: Text(
widget.order.remarks!,
style: Theme.of(context).textTheme.bodyMedium,
textAlign: TextAlign.end,
),
),
],
),
],
],
),
);
}
Widget _buildPinInput() {
return Column(
children: [
Text(
'enterPinToConfirm'.tr(),
style: Theme.of(
context,
).textTheme.titleMedium?.copyWith(fontWeight: FontWeight.w500),
textAlign: TextAlign.center,
),
const Gap(24),
OtpTextField(
numberOfFields: 6,
borderColor: Theme.of(context).colorScheme.outline,
focusedBorderColor: Theme.of(context).colorScheme.primary,
showFieldAsBox: true,
obscureText: true,
keyboardType: TextInputType.number,
fieldWidth: 48,
fieldHeight: 56,
borderRadius: BorderRadius.circular(8),
borderWidth: 1,
textStyle: Theme.of(
context,
).textTheme.headlineSmall?.copyWith(fontWeight: FontWeight.w600),
onSubmit: _onPinSubmit,
onCodeChanged: (String code) {
_pin = code;
setState(() {});
},
),
],
);
}
Widget _buildBiometricAuth() {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Icon(Symbols.fingerprint, size: 48),
const Gap(16),
Text(
'useBiometricToConfirm'.tr(),
style: Theme.of(
context,
).textTheme.titleMedium?.copyWith(fontWeight: FontWeight.w500),
textAlign: TextAlign.center,
),
Text(
'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,
).opacity(0.75),
const Gap(28),
ElevatedButton.icon(
onPressed: _authenticateWithBiometric,
icon: const Icon(Symbols.fingerprint),
label: Text('authenticateNow'.tr()),
style: ElevatedButton.styleFrom(
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 12),
),
),
TextButton(
onPressed: () => _fallbackToPinMode(null),
child: Text('usePinInstead'.tr()),
),
],
).center();
}
Widget _buildActionButtons() {
return Row(
children: [
Expanded(
child: OutlinedButton(
onPressed: widget.onCancel,
child: Text('cancel'.tr()),
),
),
if (_isPinMode && _pin.length == 6) ...[
const Gap(12),
Expanded(
child: ElevatedButton(
onPressed: () => _processPaymentWithPin(_pin),
child: Text('confirm'.tr()),
),
),
],
],
);
}
}

View File

@ -38,6 +38,7 @@ class ComposeLogic {
static ComposeState createState({ static ComposeState createState({
SnPost? originalPost, SnPost? originalPost,
SnPost? forwardedPost, SnPost? forwardedPost,
SnPost? repliedPost,
}) { }) {
return ComposeState( return ComposeState(
attachments: ValueNotifier<List<UniversalFile>>( attachments: ValueNotifier<List<UniversalFile>>(

View File

@ -1,5 +1,3 @@
import 'dart:math' as math;
import 'package:auto_route/auto_route.dart'; import 'package:auto_route/auto_route.dart';
import 'package:easy_localization/easy_localization.dart'; import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
@ -7,6 +5,8 @@ import 'package:flutter/services.dart';
import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:gap/gap.dart'; import 'package:gap/gap.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'dart:math' as math;
import 'package:island/models/embed.dart';
import 'package:island/models/post.dart'; import 'package:island/models/post.dart';
import 'package:island/pods/network.dart'; import 'package:island/pods/network.dart';
import 'package:island/pods/userinfo.dart'; import 'package:island/pods/userinfo.dart';
@ -18,6 +18,7 @@ import 'package:island/widgets/alert.dart';
import 'package:island/widgets/app_scaffold.dart'; import 'package:island/widgets/app_scaffold.dart';
import 'package:island/widgets/content/cloud_file_collection.dart'; import 'package:island/widgets/content/cloud_file_collection.dart';
import 'package:island/widgets/content/cloud_files.dart'; import 'package:island/widgets/content/cloud_files.dart';
import 'package:island/widgets/content/embed/link.dart';
import 'package:island/widgets/content/markdown.dart'; import 'package:island/widgets/content/markdown.dart';
import 'package:island/widgets/post/post_replies_sheet.dart'; import 'package:island/widgets/post/post_replies_sheet.dart';
import 'package:material_symbols_icons/symbols.dart'; import 'package:material_symbols_icons/symbols.dart';
@ -209,6 +210,9 @@ class PostItem extends HookConsumerWidget {
).padding(bottom: 8), ).padding(bottom: 8),
if (item.content?.isNotEmpty ?? false) if (item.content?.isNotEmpty ?? false)
MarkdownTextContent(content: item.content!), MarkdownTextContent(content: item.content!),
// Show truncation hint if post is truncated
if (item.isTruncated && !isFullPost)
_PostTruncateHint(),
if ((item.repliedPost != null || if ((item.repliedPost != null ||
item.forwardedPost != null) && item.forwardedPost != null) &&
showReferencePost) showReferencePost)
@ -225,6 +229,21 @@ class PostItem extends HookConsumerWidget {
kWideScreenWidth - 160, kWideScreenWidth - 160,
), ),
).padding(top: 4), ).padding(top: 4),
// Render embed links
if (item.meta?['embeds'] != null)
...((item.meta!['embeds'] as List<dynamic>)
.where((embed) => embed['Type'] == 'link')
.map(
(embedData) => EmbedLinkWidget(
link: SnEmbedLink.fromJson(
embedData as Map<String, dynamic>,
),
maxWidth: math.min(
MediaQuery.of(context).size.width * 0.85,
kWideScreenWidth - 160,
),
).padding(top: 4),
)),
], ],
), ),
onTap: () { onTap: () {
@ -240,7 +259,7 @@ class PostItem extends HookConsumerWidget {
children: [ children: [
// Replies count button // Replies count button
Padding( Padding(
padding: const EdgeInsets.only(left: 48, right: 12), padding: const EdgeInsets.only(left: 52, right: 12),
child: ActionChip( child: ActionChip(
avatar: Icon(Symbols.reply, size: 16), avatar: Icon(Symbols.reply, size: 16),
label: Text( label: Text(
@ -253,14 +272,14 @@ class PostItem extends HookConsumerWidget {
vertical: VisualDensity.minimumDensity, vertical: VisualDensity.minimumDensity,
), ),
onPressed: () { onPressed: () {
if (isOpenable) { if (isOpenable) {
showModalBottomSheet( showModalBottomSheet(
context: context, context: context,
isScrollControlled: true, isScrollControlled: true,
builder: (context) => PostRepliesSheet(post: item), builder: (context) => PostRepliesSheet(post: item),
); );
} }
}, },
), ),
), ),
// Reactions list // Reactions list
@ -393,6 +412,12 @@ Widget _buildReferencePost(BuildContext context, SnPost item) {
textStyle: const TextStyle(fontSize: 14), textStyle: const TextStyle(fontSize: 14),
isSelectable: false, isSelectable: false,
).padding(bottom: 4), ).padding(bottom: 4),
// Truncation hint for referenced post
if (referencePost.isTruncated)
_PostTruncateHint(
isCompact: true,
margin: const EdgeInsets.only(top: 4, bottom: 8),
),
if (referencePost.attachments.isNotEmpty) if (referencePost.attachments.isNotEmpty)
Row( Row(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
@ -637,6 +662,56 @@ class _PostReactionSheet extends StatelessWidget {
} }
} }
class _PostTruncateHint extends StatelessWidget {
final bool isCompact;
final EdgeInsets? margin;
const _PostTruncateHint({this.isCompact = false, this.margin});
@override
Widget build(BuildContext context) {
return Container(
margin: margin ?? EdgeInsets.only(top: isCompact ? 4 : 8),
padding: EdgeInsets.symmetric(
horizontal: isCompact ? 8 : 12,
vertical: isCompact ? 4 : 8,
),
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.surfaceVariant.withOpacity(0.3),
borderRadius: BorderRadius.circular(8),
border: Border.all(
color: Theme.of(context).colorScheme.outline.withOpacity(0.2),
),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(
Symbols.more_horiz,
size: isCompact ? 14 : 16,
color: Theme.of(context).colorScheme.secondary,
),
SizedBox(width: isCompact ? 4 : 6),
Text(
'postTruncated'.tr(),
style: TextStyle(
fontSize: isCompact ? 10 : 12,
color: Theme.of(context).colorScheme.secondary,
fontStyle: FontStyle.italic,
),
),
SizedBox(width: isCompact ? 3 : 4),
Icon(
Symbols.arrow_forward,
size: isCompact ? 12 : 14,
color: Theme.of(context).colorScheme.secondary,
),
],
),
);
}
}
// Helper method to get the appropriate icon for each visibility status // Helper method to get the appropriate icon for each visibility status
IconData _getVisibilityIcon(int visibility) { IconData _getVisibilityIcon(int visibility) {
switch (visibility) { switch (visibility) {

View File

@ -9,6 +9,7 @@
#include <bitsdojo_window_linux/bitsdojo_window_plugin.h> #include <bitsdojo_window_linux/bitsdojo_window_plugin.h>
#include <file_selector_linux/file_selector_plugin.h> #include <file_selector_linux/file_selector_plugin.h>
#include <flutter_platform_alert/flutter_platform_alert_plugin.h> #include <flutter_platform_alert/flutter_platform_alert_plugin.h>
#include <flutter_secure_storage/flutter_secure_storage_plugin.h>
#include <flutter_timezone/flutter_timezone_plugin.h> #include <flutter_timezone/flutter_timezone_plugin.h>
#include <flutter_udid/flutter_udid_plugin.h> #include <flutter_udid/flutter_udid_plugin.h>
#include <flutter_webrtc/flutter_web_r_t_c_plugin.h> #include <flutter_webrtc/flutter_web_r_t_c_plugin.h>
@ -32,6 +33,9 @@ void fl_register_plugins(FlPluginRegistry* registry) {
g_autoptr(FlPluginRegistrar) flutter_platform_alert_registrar = g_autoptr(FlPluginRegistrar) flutter_platform_alert_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "FlutterPlatformAlertPlugin"); fl_plugin_registry_get_registrar_for_plugin(registry, "FlutterPlatformAlertPlugin");
flutter_platform_alert_plugin_register_with_registrar(flutter_platform_alert_registrar); flutter_platform_alert_plugin_register_with_registrar(flutter_platform_alert_registrar);
g_autoptr(FlPluginRegistrar) flutter_secure_storage_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "FlutterSecureStoragePlugin");
flutter_secure_storage_plugin_register_with_registrar(flutter_secure_storage_registrar);
g_autoptr(FlPluginRegistrar) flutter_timezone_registrar = g_autoptr(FlPluginRegistrar) flutter_timezone_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "FlutterTimezonePlugin"); fl_plugin_registry_get_registrar_for_plugin(registry, "FlutterTimezonePlugin");
flutter_timezone_plugin_register_with_registrar(flutter_timezone_registrar); flutter_timezone_plugin_register_with_registrar(flutter_timezone_registrar);

View File

@ -6,6 +6,7 @@ list(APPEND FLUTTER_PLUGIN_LIST
bitsdojo_window_linux bitsdojo_window_linux
file_selector_linux file_selector_linux
flutter_platform_alert flutter_platform_alert
flutter_secure_storage
flutter_timezone flutter_timezone
flutter_udid flutter_udid
flutter_webrtc flutter_webrtc

View File

@ -20,6 +20,7 @@ import flutter_webrtc
import gal import gal
import irondash_engine_context import irondash_engine_context
import livekit_client import livekit_client
import local_auth_darwin
import media_kit_libs_macos_video import media_kit_libs_macos_video
import media_kit_video import media_kit_video
import package_info_plus import package_info_plus
@ -51,6 +52,7 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
GalPlugin.register(with: registry.registrar(forPlugin: "GalPlugin")) GalPlugin.register(with: registry.registrar(forPlugin: "GalPlugin"))
IrondashEngineContextPlugin.register(with: registry.registrar(forPlugin: "IrondashEngineContextPlugin")) IrondashEngineContextPlugin.register(with: registry.registrar(forPlugin: "IrondashEngineContextPlugin"))
LiveKitPlugin.register(with: registry.registrar(forPlugin: "LiveKitPlugin")) LiveKitPlugin.register(with: registry.registrar(forPlugin: "LiveKitPlugin"))
FLALocalAuthPlugin.register(with: registry.registrar(forPlugin: "FLALocalAuthPlugin"))
MediaKitLibsMacosVideoPlugin.register(with: registry.registrar(forPlugin: "MediaKitLibsMacosVideoPlugin")) MediaKitLibsMacosVideoPlugin.register(with: registry.registrar(forPlugin: "MediaKitLibsMacosVideoPlugin"))
MediaKitVideoPlugin.register(with: registry.registrar(forPlugin: "MediaKitVideoPlugin")) MediaKitVideoPlugin.register(with: registry.registrar(forPlugin: "MediaKitVideoPlugin"))
FPPPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FPPPackageInfoPlusPlugin")) FPPPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FPPPackageInfoPlusPlugin"))

View File

@ -149,10 +149,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: build name: build
sha256: "7cf79af8eb6023bee797a77b067fb6e63ac5650f3789546e023958098feb776e" sha256: "74273591bd8b7f82eeb1f191c1b65a6576535bbfd5ca3722778b07d5702d33cc"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.5.2" version: "2.5.3"
build_config: build_config:
dependency: transitive dependency: transitive
description: description:
@ -173,26 +173,26 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: build_resolvers name: build_resolvers
sha256: "7a507e6026abe52074836d51a945bfad456daa7493eb7a6cac565e490e7d5b54" sha256: badce70566085f2e87434531c4a6bc8e833672f755fc51146d612245947e91c9
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.5.2" version: "2.5.3"
build_runner: build_runner:
dependency: "direct dev" dependency: "direct dev"
description: description:
name: build_runner name: build_runner
sha256: "1ce1e5063b564f26c27bda54c82a3d38339df69ec58f90e0017f447de77e4839" sha256: b9070a4127033777c0e63195f6f117ed16a351ed676f6313b095cf4f328c0b82
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.5.2" version: "2.5.3"
build_runner_core: build_runner_core:
dependency: transitive dependency: transitive
description: description:
name: build_runner_core name: build_runner_core
sha256: "564230f3fd9363df7870058fef11ec5502ee620aec3b1ee8106b943be5c63a76" sha256: "1cdfece3eeb3f1263f7dbf5bcc0cba697bd0c22d2c866cb4b578c954dbb09bcf"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "9.1.0" version: "9.1.1"
built_collection: built_collection:
dependency: transitive dependency: transitive
description: description:
@ -891,6 +891,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.6.1" version: "2.6.1"
flutter_secure_storage:
dependency: "direct main"
description:
name: flutter_secure_storage
sha256: "9f3dd2ac3b6875b0fde5b04734789c3ef35ba3965c18e99dd564a7a2f8056df6"
url: "https://pub.dev"
source: hosted
version: "4.2.1"
flutter_svg: flutter_svg:
dependency: "direct main" dependency: "direct main"
description: description:
@ -1245,6 +1253,46 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.4.8" version: "2.4.8"
local_auth:
dependency: "direct main"
description:
name: local_auth
sha256: "434d854cf478f17f12ab29a76a02b3067f86a63a6d6c4eb8fbfdcfe4879c1b7b"
url: "https://pub.dev"
source: hosted
version: "2.3.0"
local_auth_android:
dependency: transitive
description:
name: local_auth_android
sha256: "63ad7ca6396290626dc0cb34725a939e4cfe965d80d36112f08d49cf13a8136e"
url: "https://pub.dev"
source: hosted
version: "1.0.49"
local_auth_darwin:
dependency: transitive
description:
name: local_auth_darwin
sha256: "630996cd7b7f28f5ab92432c4b35d055dd03a747bc319e5ffbb3c4806a3e50d2"
url: "https://pub.dev"
source: hosted
version: "1.4.3"
local_auth_platform_interface:
dependency: transitive
description:
name: local_auth_platform_interface
sha256: "1b842ff177a7068442eae093b64abe3592f816afd2a533c0ebcdbe40f9d2075a"
url: "https://pub.dev"
source: hosted
version: "1.0.10"
local_auth_windows:
dependency: transitive
description:
name: local_auth_windows
sha256: bc4e66a29b0fdf751aafbec923b5bed7ad6ed3614875d8151afe2578520b2ab5
url: "https://pub.dev"
source: hosted
version: "1.0.11"
logging: logging:
dependency: transitive dependency: transitive
description: description:

View File

@ -116,6 +116,8 @@ dependencies:
sign_in_with_apple: ^7.0.1 sign_in_with_apple: ^7.0.1
flutter_svg: ^2.1.0 flutter_svg: ^2.1.0
native_exif: ^0.6.2 native_exif: ^0.6.2
local_auth: ^2.3.0
flutter_secure_storage: ^4.2.1
dev_dependencies: dev_dependencies:
flutter_test: flutter_test:

View File

@ -18,6 +18,7 @@
#include <gal/gal_plugin_c_api.h> #include <gal/gal_plugin_c_api.h>
#include <irondash_engine_context/irondash_engine_context_plugin_c_api.h> #include <irondash_engine_context/irondash_engine_context_plugin_c_api.h>
#include <livekit_client/live_kit_plugin.h> #include <livekit_client/live_kit_plugin.h>
#include <local_auth_windows/local_auth_plugin.h>
#include <media_kit_libs_windows_video/media_kit_libs_windows_video_plugin_c_api.h> #include <media_kit_libs_windows_video/media_kit_libs_windows_video_plugin_c_api.h>
#include <media_kit_video/media_kit_video_plugin_c_api.h> #include <media_kit_video/media_kit_video_plugin_c_api.h>
#include <pasteboard/pasteboard_plugin.h> #include <pasteboard/pasteboard_plugin.h>
@ -52,6 +53,8 @@ void RegisterPlugins(flutter::PluginRegistry* registry) {
registry->GetRegistrarForPlugin("IrondashEngineContextPluginCApi")); registry->GetRegistrarForPlugin("IrondashEngineContextPluginCApi"));
LiveKitPluginRegisterWithRegistrar( LiveKitPluginRegisterWithRegistrar(
registry->GetRegistrarForPlugin("LiveKitPlugin")); registry->GetRegistrarForPlugin("LiveKitPlugin"));
LocalAuthPluginRegisterWithRegistrar(
registry->GetRegistrarForPlugin("LocalAuthPlugin"));
MediaKitLibsWindowsVideoPluginCApiRegisterWithRegistrar( MediaKitLibsWindowsVideoPluginCApiRegisterWithRegistrar(
registry->GetRegistrarForPlugin("MediaKitLibsWindowsVideoPluginCApi")); registry->GetRegistrarForPlugin("MediaKitLibsWindowsVideoPluginCApi"));
MediaKitVideoPluginCApiRegisterWithRegistrar( MediaKitVideoPluginCApiRegisterWithRegistrar(

View File

@ -15,6 +15,7 @@ list(APPEND FLUTTER_PLUGIN_LIST
gal gal
irondash_engine_context irondash_engine_context
livekit_client livekit_client
local_auth_windows
media_kit_libs_windows_video media_kit_libs_windows_video
media_kit_video media_kit_video
pasteboard pasteboard