Compare commits
	
		
			44 Commits
		
	
	
		
			3.2.0+126
			...
			06f04eb3a5
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 06f04eb3a5 | |||
| 8af97e43b4 | |||
| d1e8234b93 | |||
| a03d6015a6 | |||
| 246ac52d0a | |||
| abf395ff9a | |||
| 4fdc8eb1d0 | |||
| d7dcde898c | |||
| f85484d3ed | |||
| 5060bd30c9 | |||
| 3959f2260b | |||
| 6f4f1216ad | |||
| f401ffbf81 | |||
| 0251697951 | |||
| 178c12b893 | |||
| 4beda9200e | |||
| 7dfe411053 | |||
| 1232318a5d | |||
|  | 56f41b6c0e | ||
|  | 3ea717d25a | ||
| 1fe4889460 | |||
| cdf2722268 | |||
| a127b5bace | |||
| b2097cf044 | |||
| 701f29748d | |||
| 9e40ed4600 | |||
| c90e6fe661 | |||
| 569483300d | |||
| bab602d98b | |||
| b4f2bb803a | |||
| 03bfed6f46 | |||
| f98e5a0aec | |||
| 3d473e2fec | |||
| 0b6efa373a | |||
| 9b60e96cde | |||
| 81cd9b2082 | |||
| 923d5d7514 | |||
| 7169aff841 | |||
| fac3efb50c | |||
| e809aadaea | |||
| f33b569221 | |||
| e5f2e2d146 | |||
| 11368d064f | |||
| 246b163aec | 
										
											Binary file not shown.
										
									
								
							| Before Width: | Height: | Size: 70 KiB | 
							
								
								
									
										41
									
								
								android/app/src/main/res/drawable/ic_notification.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										41
									
								
								android/app/src/main/res/drawable/ic_notification.xml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,41 @@ | |||||||
|  | <vector xmlns:android="http://schemas.android.com/apk/res/android" | ||||||
|  |     android:width="192dp" | ||||||
|  |     android:height="192dp" | ||||||
|  |     android:viewportWidth="192" | ||||||
|  |     android:viewportHeight="192"> | ||||||
|  |   <path | ||||||
|  |       android:pathData="M54,147h86" | ||||||
|  |       android:strokeLineJoin="round" | ||||||
|  |       android:strokeWidth="12" | ||||||
|  |       android:fillColor="#00000000" | ||||||
|  |       android:strokeColor="#000" | ||||||
|  |       android:strokeLineCap="round"/> | ||||||
|  |   <path | ||||||
|  |       android:pathData="M57,111s-2,-4.5 -2,-10m22,22s-4,7 -11,4m9,-22s-2,-4.5 -2,-10" | ||||||
|  |       android:strokeLineJoin="round" | ||||||
|  |       android:strokeWidth="10" | ||||||
|  |       android:fillColor="#00000000" | ||||||
|  |       android:strokeColor="#000" | ||||||
|  |       android:strokeLineCap="round"/> | ||||||
|  |   <path | ||||||
|  |       android:pathData="M54,147a32,32 0,0 1,-12 -61.67A39,39 0,0 1,81 46m59,101a30,30 0,0 0,29.93 -28" | ||||||
|  |       android:strokeLineJoin="round" | ||||||
|  |       android:strokeWidth="12" | ||||||
|  |       android:fillColor="#00000000" | ||||||
|  |       android:strokeColor="#000" | ||||||
|  |       android:strokeLineCap="round"/> | ||||||
|  |   <path | ||||||
|  |       android:pathData="M132,75m-4,0a4,4 0,1 1,8 0a4,4 0,1 1,-8 0" | ||||||
|  |       android:strokeLineJoin="round" | ||||||
|  |       android:strokeWidth="8" | ||||||
|  |       android:fillColor="#00000000" | ||||||
|  |       android:strokeColor="#000" | ||||||
|  |       android:strokeLineCap="round"/> | ||||||
|  |   <path | ||||||
|  |       android:pathData="M112.5,41.22C100.84,47.96 93,60.56 93,75c0,6.38 1.53,12.39 4.24,17.71m69.51,-35.42A38.84,38.84 0,0 1,171 75c0,14.43 -7.84,27.03 -19.49,33.78m-0.79,-43.32A20.9,20.9 0,0 1,153 75c0,7.77 -4.22,14.56 -10.49,18.19m-21,-36.38C115.22,60.44 111,67.23 111,75a20.9,20.9 0,0 0,2.28 9.53" | ||||||
|  |       android:strokeLineJoin="round" | ||||||
|  |       android:strokeWidth="10" | ||||||
|  |       android:fillColor="#00000000" | ||||||
|  |       android:strokeColor="#000" | ||||||
|  |       android:strokeLineCap="round"/> | ||||||
|  | </vector> | ||||||
| @@ -643,6 +643,18 @@ | |||||||
|   "enrollDeveloperHint": "Enroll one of your publishers to become a developer.", |   "enrollDeveloperHint": "Enroll one of your publishers to become a developer.", | ||||||
|   "noPublishersToEnroll": "You don't have any publishers that can be enrolled as a developer.", |   "noPublishersToEnroll": "You don't have any publishers that can be enrolled as a developer.", | ||||||
|   "totalCustomApps": "Total Custom Apps", |   "totalCustomApps": "Total Custom Apps", | ||||||
|  |   "projects": "Projects", | ||||||
|  |   "noProjects": "No projects found.", | ||||||
|  |   "deleteProject": "Delete Project", | ||||||
|  |   "deleteProjectHint": "Are you sure you want to delete this project? This action cannot be undone.", | ||||||
|  |   "createProject": "Create Project", | ||||||
|  |   "editProject": "Edit Project", | ||||||
|  |   "projectDetails": "Project Details", | ||||||
|  |   "createBot": "Create Bot", | ||||||
|  |   "bots": "Bots", | ||||||
|  |   "noBots": "No bots yet.", | ||||||
|  |   "deleteBotHint": "Are you sure you want to delete this bot? This action cannot be undone.", | ||||||
|  |   "deleteBot": "Delete Bot", | ||||||
|   "customApps": "Custom Apps", |   "customApps": "Custom Apps", | ||||||
|   "noCustomApps": "No custom apps yet.", |   "noCustomApps": "No custom apps yet.", | ||||||
|   "createCustomApp": "Create Custom App", |   "createCustomApp": "Create Custom App", | ||||||
| @@ -850,5 +862,69 @@ | |||||||
|     "zero": "No invitation", |     "zero": "No invitation", | ||||||
|     "one": "{} available invitation", |     "one": "{} available invitation", | ||||||
|     "other": "{} available invitations" |     "other": "{} available invitations" | ||||||
|   } |   }, | ||||||
| } |   "failedToLoadUserInfo": "Failed to load user info", | ||||||
|  |   "failedToLoadUserInfoNetwork": "It seems be network issue, you can tap the button below to try again.", | ||||||
|  |   "failedToLoadUserInfoUnauthorized": "It seems your session has been logged out or not available anymore, you can still try agian to fetch the user info if you want.", | ||||||
|  |   "okay": "Okay", | ||||||
|  |   "postDetails": "Post Details", | ||||||
|  |   "postCount": { | ||||||
|  |     "zero": "No posts", | ||||||
|  |     "one": "{} post", | ||||||
|  |     "other": "{} posts" | ||||||
|  |   }, | ||||||
|  |   "mimeType": "MIME Type", | ||||||
|  |   "fileSize": "File Size", | ||||||
|  |   "fileHash": "File Hash", | ||||||
|  |   "exifData": "EXIF Data", | ||||||
|  |   "postShuffle": "Shuffle Posts", | ||||||
|  |   "leveling": "Leveling", | ||||||
|  |   "levelingHistory": "Leveling History", | ||||||
|  |   "stellarProgram": "Stellar Program", | ||||||
|  |   "socialCredits": "Social Credits", | ||||||
|  |   "credits": "Credits", | ||||||
|  |   "socialCreditsDescription": "Social Credit is a way for Solar Network to evaluate users. It is calculated based on their behavior and interactions. With a base score of 100, higher scores indicate a user's credibility within the community. Scores change over time to reflect a user's recent behavior. Users with higher credit ratings enjoy more benefits, while users with lower credit ratings may have some functionality restricted.", | ||||||
|  |   "socialCreditsLevelPoor": "Poor", | ||||||
|  |   "socialCreditsLevelNormal": "Normal", | ||||||
|  |   "socialCreditsLevelGood": "Good", | ||||||
|  |   "socialCreditsLevelExcellent": "Excellent", | ||||||
|  |   "orderByPopularity": "Sort by popularity", | ||||||
|  |   "orderByReleaseDate": "Sort by release date", | ||||||
|  |   "editBot": "Edit Bot", | ||||||
|  |   "botAutomatedBy": "Automated by {}", | ||||||
|  |   "botDetails": "Bot Details", | ||||||
|  |   "overview": "Overview", | ||||||
|  |   "keys": "Keys", | ||||||
|  |   "botNotFound": "Bot not found.", | ||||||
|  |   "newBotKey": "New Bot Key", | ||||||
|  |   "newBotKeyHint": "Enter a name for your new key. The key will be shown only once.", | ||||||
|  |   "revokeBotKey": "Revoke Bot Key", | ||||||
|  |   "revokeBotKeyHint": "Are you sure you want to revoke this key? This action cannot be undone and any application using this key will stop working.", | ||||||
|  |   "noBotKeys": "No bot keys yet.", | ||||||
|  |   "revoke": "Revoke", | ||||||
|  |   "keyName": "Key Name", | ||||||
|  |   "newKeyGenerated": "New Key Generated", | ||||||
|  |   "copyKeyHint": "Please copy this key and store it somewhere safe. You will not be able to see it again.", | ||||||
|  |   "rotateKey": "Rotate Key", | ||||||
|  |   "rotateBotKey": "Rotate Bot Key", | ||||||
|  |   "rotateBotKeyHint": "Are you sure you want to rotate this key? The old key will become invalid immediately. This action cannot be undone.", | ||||||
|  |   "webFeedArticleCount": { | ||||||
|  |     "zero": "No articles", | ||||||
|  |     "one": "{} article", | ||||||
|  |     "other": "{} articles" | ||||||
|  |   }, | ||||||
|  |   "webFeedSubscribed": "The feed has been subscribed", | ||||||
|  |   "webFeedUnsubscribed": "The feed has been unsubscribed", | ||||||
|  |   "appDetails": "App Details", | ||||||
|  |   "secrets": "Secrets", | ||||||
|  |   "appNotFound": "App not found.", | ||||||
|  |   "secretCopied": "Secret copied to clipboard.", | ||||||
|  |   "deleteSecret": "Delete Secret", | ||||||
|  |   "deleteSecretHint": "Are you sure you want to delete this secret? This action cannot be undone.", | ||||||
|  |   "generateSecret": "Generate New Secret", | ||||||
|  |   "createdAt": "Created at {}", | ||||||
|  |   "newSecretGenerated": "New Secret Generated", | ||||||
|  |   "copySecretHint": "Please copy this secret and store it somewhere safe. You will not be able to see it again.", | ||||||
|  |   "expiresIn": "Expires In (seconds)", | ||||||
|  |   "isOidc": "OIDC Compliant" | ||||||
|  | } | ||||||
| @@ -345,7 +345,7 @@ | |||||||
|   "accountSettingsHelpContent": "此页面允许您管理您的帐户安全性、隐私和其他设置。如果您需要帮助,请联系管理员。", |   "accountSettingsHelpContent": "此页面允许您管理您的帐户安全性、隐私和其他设置。如果您需要帮助,请联系管理员。", | ||||||
|   "unauthorized": "未授权", |   "unauthorized": "未授权", | ||||||
|   "unauthorizedHint": "您未登录或会话已过期,请重新登录。", |   "unauthorizedHint": "您未登录或会话已过期,请重新登录。", | ||||||
|   "publisherBelongsTo": "属于", |   "publisherBelongsTo": "属于 {}", | ||||||
|   "postContent": "内容", |   "postContent": "内容", | ||||||
|   "postSettings": "设置", |   "postSettings": "设置", | ||||||
|   "postPublisherUnselected": "未指定发布者", |   "postPublisherUnselected": "未指定发布者", | ||||||
| @@ -824,5 +824,36 @@ | |||||||
|     "zero": "无邀请", |     "zero": "无邀请", | ||||||
|     "one": "{} 个可用邀请", |     "one": "{} 个可用邀请", | ||||||
|     "other": "{} 个可用邀请" |     "other": "{} 个可用邀请" | ||||||
|   } |   }, | ||||||
|  |   "failedToLoadUserInfo": "加载用户信息失败", | ||||||
|  |   "failedToLoadUserInfoNetwork": "这看起来是个网络问题,你可以按下面的按钮来重试", | ||||||
|  |   "failedToLoadUserInfoUnauthorized": "看来您的会话已被注销或不再可用,如果您愿意,您仍然可以再次尝试获取用户信息。", | ||||||
|  |   "okay": "了解", | ||||||
|  |   "postDetails": "帖子详情", | ||||||
|  |   "mimeType": "类型", | ||||||
|  |   "fileSize": "大小", | ||||||
|  |   "fileHash": "哈希", | ||||||
|  |   "exifData": "EXIF 数据", | ||||||
|  |   "leveling": "等级", | ||||||
|  |   "levelingHistory": "经验记录", | ||||||
|  |   "stellarProgram": "恒星计划", | ||||||
|  |   "socialCredits": "社会信用点", | ||||||
|  |   "credits": "信用", | ||||||
|  |   "socialCreditsDescription": "社会信用是 Solar Network 评价用户的一种方式。它基于用户的行为和互动来计算。以 100 分为基准,分数越高表示用户在社区中的信誉越好。分数会随着时间的推移而变化,反映用户的最新行为。信用等级高的用户可以享受到更多的福利,反之的用户部份功能可能受到限制。", | ||||||
|  |   "socialCreditsLevelPoor": "糟糕", | ||||||
|  |   "socialCreditsLevelNormal": "正常", | ||||||
|  |   "socialCreditsLevelGood": "良好", | ||||||
|  |   "socialCreditsLevelExcellent": "优秀", | ||||||
|  |   "appDetails": "应用详情", | ||||||
|  |   "secrets": "密钥", | ||||||
|  |   "appNotFound": "应用未找到。", | ||||||
|  |   "secretCopied": "密钥已复制到剪贴板。", | ||||||
|  |   "deleteSecret": "删除密钥", | ||||||
|  |   "deleteSecretHint": "您确定要删除此密钥吗?此操作无法撤销。", | ||||||
|  |   "generateSecret": "生成新密钥", | ||||||
|  |   "createdAt": "创建于 {}", | ||||||
|  |   "newSecretGenerated": "已生成新密钥", | ||||||
|  |   "copySecretHint": "请复制此密钥并将其存放在安全的地方。您将无法再次看到它。", | ||||||
|  |   "expiresIn": "过期时间(秒)", | ||||||
|  |   "isOidc": "OIDC 兼容" | ||||||
| } | } | ||||||
|   | |||||||
| @@ -811,5 +811,17 @@ | |||||||
|     "filesListAdditional": { |     "filesListAdditional": { | ||||||
|         "one": "+{} 個文件被摺疊", |         "one": "+{} 個文件被摺疊", | ||||||
|         "other": "+{} 個文件被摺疊" |         "other": "+{} 個文件被摺疊" | ||||||
|     } |     }, | ||||||
|  |     "appDetails": "應用程式詳情", | ||||||
|  |     "secrets": "密鑰", | ||||||
|  |     "appNotFound": "找不到應用程式。", | ||||||
|  |     "secretCopied": "密鑰已複製到剪貼簿。", | ||||||
|  |     "deleteSecret": "刪除密鑰", | ||||||
|  |     "deleteSecretHint": "您確定要刪除此密鑰嗎?此操作無法復原。", | ||||||
|  |     "generateSecret": "產生新密鑰", | ||||||
|  |     "createdAt": "建立於 {}", | ||||||
|  |     "newSecretGenerated": "已產生新密鑰", | ||||||
|  |     "copySecretHint": "請複製此密鑰並將其存放在安全的地方。您將無法再次看到它。", | ||||||
|  |     "expiresIn": "過期時間(秒)", | ||||||
|  |     "isOidc": "OIDC 相容" | ||||||
| } | } | ||||||
| @@ -14,6 +14,7 @@ sealed class SnAccount with _$SnAccount { | |||||||
|     required String nick, |     required String nick, | ||||||
|     required String language, |     required String language, | ||||||
|     required bool isSuperuser, |     required bool isSuperuser, | ||||||
|  |     required String? automatedId, | ||||||
|     required SnAccountProfile profile, |     required SnAccountProfile profile, | ||||||
|     required SnWalletSubscriptionRef? perkSubscription, |     required SnWalletSubscriptionRef? perkSubscription, | ||||||
|     @Default([]) List<SnAccountBadge> badges, |     @Default([]) List<SnAccountBadge> badges, | ||||||
| @@ -208,3 +209,37 @@ sealed class SnAuthDeviceWithChallenge with _$SnAuthDeviceWithChallenge { | |||||||
|   factory SnAuthDeviceWithChallenge.fromJson(Map<String, dynamic> json) => |   factory SnAuthDeviceWithChallenge.fromJson(Map<String, dynamic> json) => | ||||||
|       _$SnAuthDeviceWithChallengeFromJson(json); |       _$SnAuthDeviceWithChallengeFromJson(json); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @freezed | ||||||
|  | sealed class SnExperienceRecord with _$SnExperienceRecord { | ||||||
|  |   const factory SnExperienceRecord({ | ||||||
|  |     required String id, | ||||||
|  |     required int delta, | ||||||
|  |     required String reasonType, | ||||||
|  |     required String reason, | ||||||
|  |     @Default(1.0) double? bonusMultiplier, | ||||||
|  |     required DateTime createdAt, | ||||||
|  |     required DateTime updatedAt, | ||||||
|  |     required DateTime? deletedAt, | ||||||
|  |   }) = _SnExperienceRecord; | ||||||
|  |  | ||||||
|  |   factory SnExperienceRecord.fromJson(Map<String, dynamic> json) => | ||||||
|  |       _$SnExperienceRecordFromJson(json); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @freezed | ||||||
|  | sealed class SnSocialCreditRecord with _$SnSocialCreditRecord { | ||||||
|  |   const factory SnSocialCreditRecord({ | ||||||
|  |     required String id, | ||||||
|  |     required double delta, | ||||||
|  |     required String reasonType, | ||||||
|  |     required String reason, | ||||||
|  |     required DateTime? expiredAt, | ||||||
|  |     required DateTime createdAt, | ||||||
|  |     required DateTime updatedAt, | ||||||
|  |     required DateTime? deletedAt, | ||||||
|  |   }) = _SnSocialCreditRecord; | ||||||
|  |  | ||||||
|  |   factory SnSocialCreditRecord.fromJson(Map<String, dynamic> json) => | ||||||
|  |       _$SnSocialCreditRecordFromJson(json); | ||||||
|  | } | ||||||
|   | |||||||
| @@ -15,7 +15,7 @@ T _$identity<T>(T value) => value; | |||||||
| /// @nodoc | /// @nodoc | ||||||
| mixin _$SnAccount { | mixin _$SnAccount { | ||||||
|  |  | ||||||
|  String get id; String get name; String get nick; String get language; bool get isSuperuser; SnAccountProfile get profile; SnWalletSubscriptionRef? get perkSubscription; List<SnAccountBadge> get badges; DateTime get createdAt; DateTime get updatedAt; DateTime? get deletedAt; |  String get id; String get name; String get nick; String get language; bool get isSuperuser; String? get automatedId; SnAccountProfile get profile; SnWalletSubscriptionRef? get perkSubscription; List<SnAccountBadge> get badges; DateTime get createdAt; DateTime get updatedAt; DateTime? get deletedAt; | ||||||
| /// Create a copy of SnAccount | /// Create a copy of SnAccount | ||||||
| /// 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) | ||||||
| @@ -28,16 +28,16 @@ $SnAccountCopyWith<SnAccount> get copyWith => _$SnAccountCopyWithImpl<SnAccount> | |||||||
|  |  | ||||||
| @override | @override | ||||||
| bool operator ==(Object other) { | bool operator ==(Object other) { | ||||||
|   return identical(this, other) || (other.runtimeType == runtimeType&&other is SnAccount&&(identical(other.id, id) || other.id == id)&&(identical(other.name, name) || other.name == name)&&(identical(other.nick, nick) || other.nick == nick)&&(identical(other.language, language) || other.language == language)&&(identical(other.isSuperuser, isSuperuser) || other.isSuperuser == isSuperuser)&&(identical(other.profile, profile) || other.profile == profile)&&(identical(other.perkSubscription, perkSubscription) || other.perkSubscription == perkSubscription)&&const DeepCollectionEquality().equals(other.badges, badges)&&(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 SnAccount&&(identical(other.id, id) || other.id == id)&&(identical(other.name, name) || other.name == name)&&(identical(other.nick, nick) || other.nick == nick)&&(identical(other.language, language) || other.language == language)&&(identical(other.isSuperuser, isSuperuser) || other.isSuperuser == isSuperuser)&&(identical(other.automatedId, automatedId) || other.automatedId == automatedId)&&(identical(other.profile, profile) || other.profile == profile)&&(identical(other.perkSubscription, perkSubscription) || other.perkSubscription == perkSubscription)&&const DeepCollectionEquality().equals(other.badges, badges)&&(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.hash(runtimeType,id,name,nick,language,isSuperuser,profile,perkSubscription,const DeepCollectionEquality().hash(badges),createdAt,updatedAt,deletedAt); | int get hashCode => Object.hash(runtimeType,id,name,nick,language,isSuperuser,automatedId,profile,perkSubscription,const DeepCollectionEquality().hash(badges),createdAt,updatedAt,deletedAt); | ||||||
|  |  | ||||||
| @override | @override | ||||||
| String toString() { | String toString() { | ||||||
|   return 'SnAccount(id: $id, name: $name, nick: $nick, language: $language, isSuperuser: $isSuperuser, profile: $profile, perkSubscription: $perkSubscription, badges: $badges, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt)'; |   return 'SnAccount(id: $id, name: $name, nick: $nick, language: $language, isSuperuser: $isSuperuser, automatedId: $automatedId, profile: $profile, perkSubscription: $perkSubscription, badges: $badges, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt)'; | ||||||
| } | } | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -48,7 +48,7 @@ abstract mixin class $SnAccountCopyWith<$Res>  { | |||||||
|   factory $SnAccountCopyWith(SnAccount value, $Res Function(SnAccount) _then) = _$SnAccountCopyWithImpl; |   factory $SnAccountCopyWith(SnAccount value, $Res Function(SnAccount) _then) = _$SnAccountCopyWithImpl; | ||||||
| @useResult | @useResult | ||||||
| $Res call({ | $Res call({ | ||||||
|  String id, String name, String nick, String language, bool isSuperuser, SnAccountProfile profile, SnWalletSubscriptionRef? perkSubscription, List<SnAccountBadge> badges, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt |  String id, String name, String nick, String language, bool isSuperuser, String? automatedId, SnAccountProfile profile, SnWalletSubscriptionRef? perkSubscription, List<SnAccountBadge> badges, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt | ||||||
| }); | }); | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -65,14 +65,15 @@ class _$SnAccountCopyWithImpl<$Res> | |||||||
|  |  | ||||||
| /// Create a copy of SnAccount | /// Create a copy of SnAccount | ||||||
| /// 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? name = null,Object? nick = null,Object? language = null,Object? isSuperuser = null,Object? profile = null,Object? perkSubscription = freezed,Object? badges = null,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,}) { | @pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? name = null,Object? nick = null,Object? language = null,Object? isSuperuser = null,Object? automatedId = freezed,Object? profile = null,Object? perkSubscription = freezed,Object? badges = null,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,name: null == name ? _self.name : name // ignore: cast_nullable_to_non_nullable | as String,name: null == name ? _self.name : name // ignore: cast_nullable_to_non_nullable | ||||||
| as String,nick: null == nick ? _self.nick : nick // ignore: cast_nullable_to_non_nullable | as String,nick: null == nick ? _self.nick : nick // ignore: cast_nullable_to_non_nullable | ||||||
| as String,language: null == language ? _self.language : language // ignore: cast_nullable_to_non_nullable | as String,language: null == language ? _self.language : language // ignore: cast_nullable_to_non_nullable | ||||||
| as String,isSuperuser: null == isSuperuser ? _self.isSuperuser : isSuperuser // ignore: cast_nullable_to_non_nullable | as String,isSuperuser: null == isSuperuser ? _self.isSuperuser : isSuperuser // ignore: cast_nullable_to_non_nullable | ||||||
| as bool,profile: null == profile ? _self.profile : profile // ignore: cast_nullable_to_non_nullable | as bool,automatedId: freezed == automatedId ? _self.automatedId : automatedId // ignore: cast_nullable_to_non_nullable | ||||||
|  | as String?,profile: null == profile ? _self.profile : profile // ignore: cast_nullable_to_non_nullable | ||||||
| as SnAccountProfile,perkSubscription: freezed == perkSubscription ? _self.perkSubscription : perkSubscription // ignore: cast_nullable_to_non_nullable | as SnAccountProfile,perkSubscription: freezed == perkSubscription ? _self.perkSubscription : perkSubscription // ignore: cast_nullable_to_non_nullable | ||||||
| as SnWalletSubscriptionRef?,badges: null == badges ? _self.badges : badges // ignore: cast_nullable_to_non_nullable | as SnWalletSubscriptionRef?,badges: null == badges ? _self.badges : badges // ignore: cast_nullable_to_non_nullable | ||||||
| as List<SnAccountBadge>,createdAt: null == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable | as List<SnAccountBadge>,createdAt: null == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable | ||||||
| @@ -181,10 +182,10 @@ return $default(_that);case _: | |||||||
| /// } | /// } | ||||||
| /// ``` | /// ``` | ||||||
|  |  | ||||||
| @optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String id,  String name,  String nick,  String language,  bool isSuperuser,  SnAccountProfile profile,  SnWalletSubscriptionRef? perkSubscription,  List<SnAccountBadge> badges,  DateTime createdAt,  DateTime updatedAt,  DateTime? deletedAt)?  $default,{required TResult orElse(),}) {final _that = this; | @optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String id,  String name,  String nick,  String language,  bool isSuperuser,  String? automatedId,  SnAccountProfile profile,  SnWalletSubscriptionRef? perkSubscription,  List<SnAccountBadge> badges,  DateTime createdAt,  DateTime updatedAt,  DateTime? deletedAt)?  $default,{required TResult orElse(),}) {final _that = this; | ||||||
| switch (_that) { | switch (_that) { | ||||||
| case _SnAccount() when $default != null: | case _SnAccount() when $default != null: | ||||||
| return $default(_that.id,_that.name,_that.nick,_that.language,_that.isSuperuser,_that.profile,_that.perkSubscription,_that.badges,_that.createdAt,_that.updatedAt,_that.deletedAt);case _: | return $default(_that.id,_that.name,_that.nick,_that.language,_that.isSuperuser,_that.automatedId,_that.profile,_that.perkSubscription,_that.badges,_that.createdAt,_that.updatedAt,_that.deletedAt);case _: | ||||||
|   return orElse(); |   return orElse(); | ||||||
|  |  | ||||||
| } | } | ||||||
| @@ -202,10 +203,10 @@ return $default(_that.id,_that.name,_that.nick,_that.language,_that.isSuperuser, | |||||||
| /// } | /// } | ||||||
| /// ``` | /// ``` | ||||||
|  |  | ||||||
| @optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String id,  String name,  String nick,  String language,  bool isSuperuser,  SnAccountProfile profile,  SnWalletSubscriptionRef? perkSubscription,  List<SnAccountBadge> badges,  DateTime createdAt,  DateTime updatedAt,  DateTime? deletedAt)  $default,) {final _that = this; | @optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String id,  String name,  String nick,  String language,  bool isSuperuser,  String? automatedId,  SnAccountProfile profile,  SnWalletSubscriptionRef? perkSubscription,  List<SnAccountBadge> badges,  DateTime createdAt,  DateTime updatedAt,  DateTime? deletedAt)  $default,) {final _that = this; | ||||||
| switch (_that) { | switch (_that) { | ||||||
| case _SnAccount(): | case _SnAccount(): | ||||||
| return $default(_that.id,_that.name,_that.nick,_that.language,_that.isSuperuser,_that.profile,_that.perkSubscription,_that.badges,_that.createdAt,_that.updatedAt,_that.deletedAt);} | return $default(_that.id,_that.name,_that.nick,_that.language,_that.isSuperuser,_that.automatedId,_that.profile,_that.perkSubscription,_that.badges,_that.createdAt,_that.updatedAt,_that.deletedAt);} | ||||||
| } | } | ||||||
| /// A variant of `when` that fallback to returning `null` | /// A variant of `when` that fallback to returning `null` | ||||||
| /// | /// | ||||||
| @@ -219,10 +220,10 @@ return $default(_that.id,_that.name,_that.nick,_that.language,_that.isSuperuser, | |||||||
| /// } | /// } | ||||||
| /// ``` | /// ``` | ||||||
|  |  | ||||||
| @optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String id,  String name,  String nick,  String language,  bool isSuperuser,  SnAccountProfile profile,  SnWalletSubscriptionRef? perkSubscription,  List<SnAccountBadge> badges,  DateTime createdAt,  DateTime updatedAt,  DateTime? deletedAt)?  $default,) {final _that = this; | @optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String id,  String name,  String nick,  String language,  bool isSuperuser,  String? automatedId,  SnAccountProfile profile,  SnWalletSubscriptionRef? perkSubscription,  List<SnAccountBadge> badges,  DateTime createdAt,  DateTime updatedAt,  DateTime? deletedAt)?  $default,) {final _that = this; | ||||||
| switch (_that) { | switch (_that) { | ||||||
| case _SnAccount() when $default != null: | case _SnAccount() when $default != null: | ||||||
| return $default(_that.id,_that.name,_that.nick,_that.language,_that.isSuperuser,_that.profile,_that.perkSubscription,_that.badges,_that.createdAt,_that.updatedAt,_that.deletedAt);case _: | return $default(_that.id,_that.name,_that.nick,_that.language,_that.isSuperuser,_that.automatedId,_that.profile,_that.perkSubscription,_that.badges,_that.createdAt,_that.updatedAt,_that.deletedAt);case _: | ||||||
|   return null; |   return null; | ||||||
|  |  | ||||||
| } | } | ||||||
| @@ -234,7 +235,7 @@ return $default(_that.id,_that.name,_that.nick,_that.language,_that.isSuperuser, | |||||||
| @JsonSerializable() | @JsonSerializable() | ||||||
|  |  | ||||||
| class _SnAccount implements SnAccount { | class _SnAccount implements SnAccount { | ||||||
|   const _SnAccount({required this.id, required this.name, required this.nick, required this.language, required this.isSuperuser, required this.profile, required this.perkSubscription, final  List<SnAccountBadge> badges = const [], required this.createdAt, required this.updatedAt, required this.deletedAt}): _badges = badges; |   const _SnAccount({required this.id, required this.name, required this.nick, required this.language, required this.isSuperuser, required this.automatedId, required this.profile, required this.perkSubscription, final  List<SnAccountBadge> badges = const [], required this.createdAt, required this.updatedAt, required this.deletedAt}): _badges = badges; | ||||||
|   factory _SnAccount.fromJson(Map<String, dynamic> json) => _$SnAccountFromJson(json); |   factory _SnAccount.fromJson(Map<String, dynamic> json) => _$SnAccountFromJson(json); | ||||||
|  |  | ||||||
| @override final  String id; | @override final  String id; | ||||||
| @@ -242,6 +243,7 @@ class _SnAccount implements SnAccount { | |||||||
| @override final  String nick; | @override final  String nick; | ||||||
| @override final  String language; | @override final  String language; | ||||||
| @override final  bool isSuperuser; | @override final  bool isSuperuser; | ||||||
|  | @override final  String? automatedId; | ||||||
| @override final  SnAccountProfile profile; | @override final  SnAccountProfile profile; | ||||||
| @override final  SnWalletSubscriptionRef? perkSubscription; | @override final  SnWalletSubscriptionRef? perkSubscription; | ||||||
|  final  List<SnAccountBadge> _badges; |  final  List<SnAccountBadge> _badges; | ||||||
| @@ -268,16 +270,16 @@ Map<String, dynamic> toJson() { | |||||||
|  |  | ||||||
| @override | @override | ||||||
| bool operator ==(Object other) { | bool operator ==(Object other) { | ||||||
|   return identical(this, other) || (other.runtimeType == runtimeType&&other is _SnAccount&&(identical(other.id, id) || other.id == id)&&(identical(other.name, name) || other.name == name)&&(identical(other.nick, nick) || other.nick == nick)&&(identical(other.language, language) || other.language == language)&&(identical(other.isSuperuser, isSuperuser) || other.isSuperuser == isSuperuser)&&(identical(other.profile, profile) || other.profile == profile)&&(identical(other.perkSubscription, perkSubscription) || other.perkSubscription == perkSubscription)&&const DeepCollectionEquality().equals(other._badges, _badges)&&(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 _SnAccount&&(identical(other.id, id) || other.id == id)&&(identical(other.name, name) || other.name == name)&&(identical(other.nick, nick) || other.nick == nick)&&(identical(other.language, language) || other.language == language)&&(identical(other.isSuperuser, isSuperuser) || other.isSuperuser == isSuperuser)&&(identical(other.automatedId, automatedId) || other.automatedId == automatedId)&&(identical(other.profile, profile) || other.profile == profile)&&(identical(other.perkSubscription, perkSubscription) || other.perkSubscription == perkSubscription)&&const DeepCollectionEquality().equals(other._badges, _badges)&&(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.hash(runtimeType,id,name,nick,language,isSuperuser,profile,perkSubscription,const DeepCollectionEquality().hash(_badges),createdAt,updatedAt,deletedAt); | int get hashCode => Object.hash(runtimeType,id,name,nick,language,isSuperuser,automatedId,profile,perkSubscription,const DeepCollectionEquality().hash(_badges),createdAt,updatedAt,deletedAt); | ||||||
|  |  | ||||||
| @override | @override | ||||||
| String toString() { | String toString() { | ||||||
|   return 'SnAccount(id: $id, name: $name, nick: $nick, language: $language, isSuperuser: $isSuperuser, profile: $profile, perkSubscription: $perkSubscription, badges: $badges, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt)'; |   return 'SnAccount(id: $id, name: $name, nick: $nick, language: $language, isSuperuser: $isSuperuser, automatedId: $automatedId, profile: $profile, perkSubscription: $perkSubscription, badges: $badges, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt)'; | ||||||
| } | } | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -288,7 +290,7 @@ abstract mixin class _$SnAccountCopyWith<$Res> implements $SnAccountCopyWith<$Re | |||||||
|   factory _$SnAccountCopyWith(_SnAccount value, $Res Function(_SnAccount) _then) = __$SnAccountCopyWithImpl; |   factory _$SnAccountCopyWith(_SnAccount value, $Res Function(_SnAccount) _then) = __$SnAccountCopyWithImpl; | ||||||
| @override @useResult | @override @useResult | ||||||
| $Res call({ | $Res call({ | ||||||
|  String id, String name, String nick, String language, bool isSuperuser, SnAccountProfile profile, SnWalletSubscriptionRef? perkSubscription, List<SnAccountBadge> badges, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt |  String id, String name, String nick, String language, bool isSuperuser, String? automatedId, SnAccountProfile profile, SnWalletSubscriptionRef? perkSubscription, List<SnAccountBadge> badges, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt | ||||||
| }); | }); | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -305,14 +307,15 @@ class __$SnAccountCopyWithImpl<$Res> | |||||||
|  |  | ||||||
| /// Create a copy of SnAccount | /// Create a copy of SnAccount | ||||||
| /// 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? name = null,Object? nick = null,Object? language = null,Object? isSuperuser = null,Object? profile = null,Object? perkSubscription = freezed,Object? badges = null,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,}) { | @override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? name = null,Object? nick = null,Object? language = null,Object? isSuperuser = null,Object? automatedId = freezed,Object? profile = null,Object? perkSubscription = freezed,Object? badges = null,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,}) { | ||||||
|   return _then(_SnAccount( |   return _then(_SnAccount( | ||||||
| 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,name: null == name ? _self.name : name // ignore: cast_nullable_to_non_nullable | as String,name: null == name ? _self.name : name // ignore: cast_nullable_to_non_nullable | ||||||
| as String,nick: null == nick ? _self.nick : nick // ignore: cast_nullable_to_non_nullable | as String,nick: null == nick ? _self.nick : nick // ignore: cast_nullable_to_non_nullable | ||||||
| as String,language: null == language ? _self.language : language // ignore: cast_nullable_to_non_nullable | as String,language: null == language ? _self.language : language // ignore: cast_nullable_to_non_nullable | ||||||
| as String,isSuperuser: null == isSuperuser ? _self.isSuperuser : isSuperuser // ignore: cast_nullable_to_non_nullable | as String,isSuperuser: null == isSuperuser ? _self.isSuperuser : isSuperuser // ignore: cast_nullable_to_non_nullable | ||||||
| as bool,profile: null == profile ? _self.profile : profile // ignore: cast_nullable_to_non_nullable | as bool,automatedId: freezed == automatedId ? _self.automatedId : automatedId // ignore: cast_nullable_to_non_nullable | ||||||
|  | as String?,profile: null == profile ? _self.profile : profile // ignore: cast_nullable_to_non_nullable | ||||||
| as SnAccountProfile,perkSubscription: freezed == perkSubscription ? _self.perkSubscription : perkSubscription // ignore: cast_nullable_to_non_nullable | as SnAccountProfile,perkSubscription: freezed == perkSubscription ? _self.perkSubscription : perkSubscription // ignore: cast_nullable_to_non_nullable | ||||||
| as SnWalletSubscriptionRef?,badges: null == badges ? _self._badges : badges // ignore: cast_nullable_to_non_nullable | as SnWalletSubscriptionRef?,badges: null == badges ? _self._badges : badges // ignore: cast_nullable_to_non_nullable | ||||||
| as List<SnAccountBadge>,createdAt: null == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable | as List<SnAccountBadge>,createdAt: null == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable | ||||||
| @@ -3018,6 +3021,562 @@ as bool, | |||||||
| } | } | ||||||
|  |  | ||||||
|  |  | ||||||
|  | } | ||||||
|  |  | ||||||
|  |  | ||||||
|  | /// @nodoc | ||||||
|  | mixin _$SnExperienceRecord { | ||||||
|  |  | ||||||
|  |  String get id; int get delta; String get reasonType; String get reason; double? get bonusMultiplier; DateTime get createdAt; DateTime get updatedAt; DateTime? get deletedAt; | ||||||
|  | /// Create a copy of SnExperienceRecord | ||||||
|  | /// with the given fields replaced by the non-null parameter values. | ||||||
|  | @JsonKey(includeFromJson: false, includeToJson: false) | ||||||
|  | @pragma('vm:prefer-inline') | ||||||
|  | $SnExperienceRecordCopyWith<SnExperienceRecord> get copyWith => _$SnExperienceRecordCopyWithImpl<SnExperienceRecord>(this as SnExperienceRecord, _$identity); | ||||||
|  |  | ||||||
|  |   /// Serializes this SnExperienceRecord to a JSON map. | ||||||
|  |   Map<String, dynamic> toJson(); | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @override | ||||||
|  | bool operator ==(Object other) { | ||||||
|  |   return identical(this, other) || (other.runtimeType == runtimeType&&other is SnExperienceRecord&&(identical(other.id, id) || other.id == id)&&(identical(other.delta, delta) || other.delta == delta)&&(identical(other.reasonType, reasonType) || other.reasonType == reasonType)&&(identical(other.reason, reason) || other.reason == reason)&&(identical(other.bonusMultiplier, bonusMultiplier) || other.bonusMultiplier == bonusMultiplier)&&(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,delta,reasonType,reason,bonusMultiplier,createdAt,updatedAt,deletedAt); | ||||||
|  |  | ||||||
|  | @override | ||||||
|  | String toString() { | ||||||
|  |   return 'SnExperienceRecord(id: $id, delta: $delta, reasonType: $reasonType, reason: $reason, bonusMultiplier: $bonusMultiplier, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt)'; | ||||||
|  | } | ||||||
|  |  | ||||||
|  |  | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /// @nodoc | ||||||
|  | abstract mixin class $SnExperienceRecordCopyWith<$Res>  { | ||||||
|  |   factory $SnExperienceRecordCopyWith(SnExperienceRecord value, $Res Function(SnExperienceRecord) _then) = _$SnExperienceRecordCopyWithImpl; | ||||||
|  | @useResult | ||||||
|  | $Res call({ | ||||||
|  |  String id, int delta, String reasonType, String reason, double? bonusMultiplier, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt | ||||||
|  | }); | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  | } | ||||||
|  | /// @nodoc | ||||||
|  | class _$SnExperienceRecordCopyWithImpl<$Res> | ||||||
|  |     implements $SnExperienceRecordCopyWith<$Res> { | ||||||
|  |   _$SnExperienceRecordCopyWithImpl(this._self, this._then); | ||||||
|  |  | ||||||
|  |   final SnExperienceRecord _self; | ||||||
|  |   final $Res Function(SnExperienceRecord) _then; | ||||||
|  |  | ||||||
|  | /// Create a copy of SnExperienceRecord | ||||||
|  | /// with the given fields replaced by the non-null parameter values. | ||||||
|  | @pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? delta = null,Object? reasonType = null,Object? reason = null,Object? bonusMultiplier = 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,delta: null == delta ? _self.delta : delta // ignore: cast_nullable_to_non_nullable | ||||||
|  | as int,reasonType: null == reasonType ? _self.reasonType : reasonType // ignore: cast_nullable_to_non_nullable | ||||||
|  | as String,reason: null == reason ? _self.reason : reason // ignore: cast_nullable_to_non_nullable | ||||||
|  | as String,bonusMultiplier: freezed == bonusMultiplier ? _self.bonusMultiplier : bonusMultiplier // 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?, | ||||||
|  |   )); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | } | ||||||
|  |  | ||||||
|  |  | ||||||
|  | /// Adds pattern-matching-related methods to [SnExperienceRecord]. | ||||||
|  | extension SnExperienceRecordPatterns on SnExperienceRecord { | ||||||
|  | /// A variant of `map` that fallback to returning `orElse`. | ||||||
|  | /// | ||||||
|  | /// It is equivalent to doing: | ||||||
|  | /// ```dart | ||||||
|  | /// switch (sealedClass) { | ||||||
|  | ///   case final Subclass value: | ||||||
|  | ///     return ...; | ||||||
|  | ///   case _: | ||||||
|  | ///     return orElse(); | ||||||
|  | /// } | ||||||
|  | /// ``` | ||||||
|  |  | ||||||
|  | @optionalTypeArgs TResult maybeMap<TResult extends Object?>(TResult Function( _SnExperienceRecord value)?  $default,{required TResult orElse(),}){ | ||||||
|  | final _that = this; | ||||||
|  | switch (_that) { | ||||||
|  | case _SnExperienceRecord() when $default != null: | ||||||
|  | return $default(_that);case _: | ||||||
|  |   return orElse(); | ||||||
|  |  | ||||||
|  | } | ||||||
|  | } | ||||||
|  | /// A `switch`-like method, using callbacks. | ||||||
|  | /// | ||||||
|  | /// Callbacks receives the raw object, upcasted. | ||||||
|  | /// It is equivalent to doing: | ||||||
|  | /// ```dart | ||||||
|  | /// switch (sealedClass) { | ||||||
|  | ///   case final Subclass value: | ||||||
|  | ///     return ...; | ||||||
|  | ///   case final Subclass2 value: | ||||||
|  | ///     return ...; | ||||||
|  | /// } | ||||||
|  | /// ``` | ||||||
|  |  | ||||||
|  | @optionalTypeArgs TResult map<TResult extends Object?>(TResult Function( _SnExperienceRecord value)  $default,){ | ||||||
|  | final _that = this; | ||||||
|  | switch (_that) { | ||||||
|  | case _SnExperienceRecord(): | ||||||
|  | return $default(_that);} | ||||||
|  | } | ||||||
|  | /// A variant of `map` that fallback to returning `null`. | ||||||
|  | /// | ||||||
|  | /// It is equivalent to doing: | ||||||
|  | /// ```dart | ||||||
|  | /// switch (sealedClass) { | ||||||
|  | ///   case final Subclass value: | ||||||
|  | ///     return ...; | ||||||
|  | ///   case _: | ||||||
|  | ///     return null; | ||||||
|  | /// } | ||||||
|  | /// ``` | ||||||
|  |  | ||||||
|  | @optionalTypeArgs TResult? mapOrNull<TResult extends Object?>(TResult? Function( _SnExperienceRecord value)?  $default,){ | ||||||
|  | final _that = this; | ||||||
|  | switch (_that) { | ||||||
|  | case _SnExperienceRecord() when $default != null: | ||||||
|  | return $default(_that);case _: | ||||||
|  |   return null; | ||||||
|  |  | ||||||
|  | } | ||||||
|  | } | ||||||
|  | /// A variant of `when` that fallback to an `orElse` callback. | ||||||
|  | /// | ||||||
|  | /// It is equivalent to doing: | ||||||
|  | /// ```dart | ||||||
|  | /// switch (sealedClass) { | ||||||
|  | ///   case Subclass(:final field): | ||||||
|  | ///     return ...; | ||||||
|  | ///   case _: | ||||||
|  | ///     return orElse(); | ||||||
|  | /// } | ||||||
|  | /// ``` | ||||||
|  |  | ||||||
|  | @optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String id,  int delta,  String reasonType,  String reason,  double? bonusMultiplier,  DateTime createdAt,  DateTime updatedAt,  DateTime? deletedAt)?  $default,{required TResult orElse(),}) {final _that = this; | ||||||
|  | switch (_that) { | ||||||
|  | case _SnExperienceRecord() when $default != null: | ||||||
|  | return $default(_that.id,_that.delta,_that.reasonType,_that.reason,_that.bonusMultiplier,_that.createdAt,_that.updatedAt,_that.deletedAt);case _: | ||||||
|  |   return orElse(); | ||||||
|  |  | ||||||
|  | } | ||||||
|  | } | ||||||
|  | /// A `switch`-like method, using callbacks. | ||||||
|  | /// | ||||||
|  | /// As opposed to `map`, this offers destructuring. | ||||||
|  | /// It is equivalent to doing: | ||||||
|  | /// ```dart | ||||||
|  | /// switch (sealedClass) { | ||||||
|  | ///   case Subclass(:final field): | ||||||
|  | ///     return ...; | ||||||
|  | ///   case Subclass2(:final field2): | ||||||
|  | ///     return ...; | ||||||
|  | /// } | ||||||
|  | /// ``` | ||||||
|  |  | ||||||
|  | @optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String id,  int delta,  String reasonType,  String reason,  double? bonusMultiplier,  DateTime createdAt,  DateTime updatedAt,  DateTime? deletedAt)  $default,) {final _that = this; | ||||||
|  | switch (_that) { | ||||||
|  | case _SnExperienceRecord(): | ||||||
|  | return $default(_that.id,_that.delta,_that.reasonType,_that.reason,_that.bonusMultiplier,_that.createdAt,_that.updatedAt,_that.deletedAt);} | ||||||
|  | } | ||||||
|  | /// A variant of `when` that fallback to returning `null` | ||||||
|  | /// | ||||||
|  | /// It is equivalent to doing: | ||||||
|  | /// ```dart | ||||||
|  | /// switch (sealedClass) { | ||||||
|  | ///   case Subclass(:final field): | ||||||
|  | ///     return ...; | ||||||
|  | ///   case _: | ||||||
|  | ///     return null; | ||||||
|  | /// } | ||||||
|  | /// ``` | ||||||
|  |  | ||||||
|  | @optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String id,  int delta,  String reasonType,  String reason,  double? bonusMultiplier,  DateTime createdAt,  DateTime updatedAt,  DateTime? deletedAt)?  $default,) {final _that = this; | ||||||
|  | switch (_that) { | ||||||
|  | case _SnExperienceRecord() when $default != null: | ||||||
|  | return $default(_that.id,_that.delta,_that.reasonType,_that.reason,_that.bonusMultiplier,_that.createdAt,_that.updatedAt,_that.deletedAt);case _: | ||||||
|  |   return null; | ||||||
|  |  | ||||||
|  | } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /// @nodoc | ||||||
|  | @JsonSerializable() | ||||||
|  |  | ||||||
|  | class _SnExperienceRecord implements SnExperienceRecord { | ||||||
|  |   const _SnExperienceRecord({required this.id, required this.delta, required this.reasonType, required this.reason, this.bonusMultiplier = 1.0, required this.createdAt, required this.updatedAt, required this.deletedAt}); | ||||||
|  |   factory _SnExperienceRecord.fromJson(Map<String, dynamic> json) => _$SnExperienceRecordFromJson(json); | ||||||
|  |  | ||||||
|  | @override final  String id; | ||||||
|  | @override final  int delta; | ||||||
|  | @override final  String reasonType; | ||||||
|  | @override final  String reason; | ||||||
|  | @override@JsonKey() final  double? bonusMultiplier; | ||||||
|  | @override final  DateTime createdAt; | ||||||
|  | @override final  DateTime updatedAt; | ||||||
|  | @override final  DateTime? deletedAt; | ||||||
|  |  | ||||||
|  | /// Create a copy of SnExperienceRecord | ||||||
|  | /// with the given fields replaced by the non-null parameter values. | ||||||
|  | @override @JsonKey(includeFromJson: false, includeToJson: false) | ||||||
|  | @pragma('vm:prefer-inline') | ||||||
|  | _$SnExperienceRecordCopyWith<_SnExperienceRecord> get copyWith => __$SnExperienceRecordCopyWithImpl<_SnExperienceRecord>(this, _$identity); | ||||||
|  |  | ||||||
|  | @override | ||||||
|  | Map<String, dynamic> toJson() { | ||||||
|  |   return _$SnExperienceRecordToJson(this, ); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @override | ||||||
|  | bool operator ==(Object other) { | ||||||
|  |   return identical(this, other) || (other.runtimeType == runtimeType&&other is _SnExperienceRecord&&(identical(other.id, id) || other.id == id)&&(identical(other.delta, delta) || other.delta == delta)&&(identical(other.reasonType, reasonType) || other.reasonType == reasonType)&&(identical(other.reason, reason) || other.reason == reason)&&(identical(other.bonusMultiplier, bonusMultiplier) || other.bonusMultiplier == bonusMultiplier)&&(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,delta,reasonType,reason,bonusMultiplier,createdAt,updatedAt,deletedAt); | ||||||
|  |  | ||||||
|  | @override | ||||||
|  | String toString() { | ||||||
|  |   return 'SnExperienceRecord(id: $id, delta: $delta, reasonType: $reasonType, reason: $reason, bonusMultiplier: $bonusMultiplier, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt)'; | ||||||
|  | } | ||||||
|  |  | ||||||
|  |  | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /// @nodoc | ||||||
|  | abstract mixin class _$SnExperienceRecordCopyWith<$Res> implements $SnExperienceRecordCopyWith<$Res> { | ||||||
|  |   factory _$SnExperienceRecordCopyWith(_SnExperienceRecord value, $Res Function(_SnExperienceRecord) _then) = __$SnExperienceRecordCopyWithImpl; | ||||||
|  | @override @useResult | ||||||
|  | $Res call({ | ||||||
|  |  String id, int delta, String reasonType, String reason, double? bonusMultiplier, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt | ||||||
|  | }); | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  | } | ||||||
|  | /// @nodoc | ||||||
|  | class __$SnExperienceRecordCopyWithImpl<$Res> | ||||||
|  |     implements _$SnExperienceRecordCopyWith<$Res> { | ||||||
|  |   __$SnExperienceRecordCopyWithImpl(this._self, this._then); | ||||||
|  |  | ||||||
|  |   final _SnExperienceRecord _self; | ||||||
|  |   final $Res Function(_SnExperienceRecord) _then; | ||||||
|  |  | ||||||
|  | /// Create a copy of SnExperienceRecord | ||||||
|  | /// with the given fields replaced by the non-null parameter values. | ||||||
|  | @override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? delta = null,Object? reasonType = null,Object? reason = null,Object? bonusMultiplier = freezed,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,}) { | ||||||
|  |   return _then(_SnExperienceRecord( | ||||||
|  | id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable | ||||||
|  | as String,delta: null == delta ? _self.delta : delta // ignore: cast_nullable_to_non_nullable | ||||||
|  | as int,reasonType: null == reasonType ? _self.reasonType : reasonType // ignore: cast_nullable_to_non_nullable | ||||||
|  | as String,reason: null == reason ? _self.reason : reason // ignore: cast_nullable_to_non_nullable | ||||||
|  | as String,bonusMultiplier: freezed == bonusMultiplier ? _self.bonusMultiplier : bonusMultiplier // 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?, | ||||||
|  |   )); | ||||||
|  | } | ||||||
|  |  | ||||||
|  |  | ||||||
|  | } | ||||||
|  |  | ||||||
|  |  | ||||||
|  | /// @nodoc | ||||||
|  | mixin _$SnSocialCreditRecord { | ||||||
|  |  | ||||||
|  |  String get id; double get delta; String get reasonType; String get reason; DateTime? get expiredAt; DateTime get createdAt; DateTime get updatedAt; DateTime? get deletedAt; | ||||||
|  | /// Create a copy of SnSocialCreditRecord | ||||||
|  | /// with the given fields replaced by the non-null parameter values. | ||||||
|  | @JsonKey(includeFromJson: false, includeToJson: false) | ||||||
|  | @pragma('vm:prefer-inline') | ||||||
|  | $SnSocialCreditRecordCopyWith<SnSocialCreditRecord> get copyWith => _$SnSocialCreditRecordCopyWithImpl<SnSocialCreditRecord>(this as SnSocialCreditRecord, _$identity); | ||||||
|  |  | ||||||
|  |   /// Serializes this SnSocialCreditRecord to a JSON map. | ||||||
|  |   Map<String, dynamic> toJson(); | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @override | ||||||
|  | bool operator ==(Object other) { | ||||||
|  |   return identical(this, other) || (other.runtimeType == runtimeType&&other is SnSocialCreditRecord&&(identical(other.id, id) || other.id == id)&&(identical(other.delta, delta) || other.delta == delta)&&(identical(other.reasonType, reasonType) || other.reasonType == reasonType)&&(identical(other.reason, reason) || other.reason == reason)&&(identical(other.expiredAt, expiredAt) || other.expiredAt == expiredAt)&&(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,delta,reasonType,reason,expiredAt,createdAt,updatedAt,deletedAt); | ||||||
|  |  | ||||||
|  | @override | ||||||
|  | String toString() { | ||||||
|  |   return 'SnSocialCreditRecord(id: $id, delta: $delta, reasonType: $reasonType, reason: $reason, expiredAt: $expiredAt, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt)'; | ||||||
|  | } | ||||||
|  |  | ||||||
|  |  | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /// @nodoc | ||||||
|  | abstract mixin class $SnSocialCreditRecordCopyWith<$Res>  { | ||||||
|  |   factory $SnSocialCreditRecordCopyWith(SnSocialCreditRecord value, $Res Function(SnSocialCreditRecord) _then) = _$SnSocialCreditRecordCopyWithImpl; | ||||||
|  | @useResult | ||||||
|  | $Res call({ | ||||||
|  |  String id, double delta, String reasonType, String reason, DateTime? expiredAt, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt | ||||||
|  | }); | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  | } | ||||||
|  | /// @nodoc | ||||||
|  | class _$SnSocialCreditRecordCopyWithImpl<$Res> | ||||||
|  |     implements $SnSocialCreditRecordCopyWith<$Res> { | ||||||
|  |   _$SnSocialCreditRecordCopyWithImpl(this._self, this._then); | ||||||
|  |  | ||||||
|  |   final SnSocialCreditRecord _self; | ||||||
|  |   final $Res Function(SnSocialCreditRecord) _then; | ||||||
|  |  | ||||||
|  | /// Create a copy of SnSocialCreditRecord | ||||||
|  | /// with the given fields replaced by the non-null parameter values. | ||||||
|  | @pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? delta = null,Object? reasonType = null,Object? reason = null,Object? expiredAt = 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,delta: null == delta ? _self.delta : delta // ignore: cast_nullable_to_non_nullable | ||||||
|  | as double,reasonType: null == reasonType ? _self.reasonType : reasonType // ignore: cast_nullable_to_non_nullable | ||||||
|  | as String,reason: null == reason ? _self.reason : reason // ignore: cast_nullable_to_non_nullable | ||||||
|  | as String,expiredAt: freezed == expiredAt ? _self.expiredAt : expiredAt // ignore: cast_nullable_to_non_nullable | ||||||
|  | as DateTime?,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?, | ||||||
|  |   )); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | } | ||||||
|  |  | ||||||
|  |  | ||||||
|  | /// Adds pattern-matching-related methods to [SnSocialCreditRecord]. | ||||||
|  | extension SnSocialCreditRecordPatterns on SnSocialCreditRecord { | ||||||
|  | /// A variant of `map` that fallback to returning `orElse`. | ||||||
|  | /// | ||||||
|  | /// It is equivalent to doing: | ||||||
|  | /// ```dart | ||||||
|  | /// switch (sealedClass) { | ||||||
|  | ///   case final Subclass value: | ||||||
|  | ///     return ...; | ||||||
|  | ///   case _: | ||||||
|  | ///     return orElse(); | ||||||
|  | /// } | ||||||
|  | /// ``` | ||||||
|  |  | ||||||
|  | @optionalTypeArgs TResult maybeMap<TResult extends Object?>(TResult Function( _SnSocialCreditRecord value)?  $default,{required TResult orElse(),}){ | ||||||
|  | final _that = this; | ||||||
|  | switch (_that) { | ||||||
|  | case _SnSocialCreditRecord() when $default != null: | ||||||
|  | return $default(_that);case _: | ||||||
|  |   return orElse(); | ||||||
|  |  | ||||||
|  | } | ||||||
|  | } | ||||||
|  | /// A `switch`-like method, using callbacks. | ||||||
|  | /// | ||||||
|  | /// Callbacks receives the raw object, upcasted. | ||||||
|  | /// It is equivalent to doing: | ||||||
|  | /// ```dart | ||||||
|  | /// switch (sealedClass) { | ||||||
|  | ///   case final Subclass value: | ||||||
|  | ///     return ...; | ||||||
|  | ///   case final Subclass2 value: | ||||||
|  | ///     return ...; | ||||||
|  | /// } | ||||||
|  | /// ``` | ||||||
|  |  | ||||||
|  | @optionalTypeArgs TResult map<TResult extends Object?>(TResult Function( _SnSocialCreditRecord value)  $default,){ | ||||||
|  | final _that = this; | ||||||
|  | switch (_that) { | ||||||
|  | case _SnSocialCreditRecord(): | ||||||
|  | return $default(_that);} | ||||||
|  | } | ||||||
|  | /// A variant of `map` that fallback to returning `null`. | ||||||
|  | /// | ||||||
|  | /// It is equivalent to doing: | ||||||
|  | /// ```dart | ||||||
|  | /// switch (sealedClass) { | ||||||
|  | ///   case final Subclass value: | ||||||
|  | ///     return ...; | ||||||
|  | ///   case _: | ||||||
|  | ///     return null; | ||||||
|  | /// } | ||||||
|  | /// ``` | ||||||
|  |  | ||||||
|  | @optionalTypeArgs TResult? mapOrNull<TResult extends Object?>(TResult? Function( _SnSocialCreditRecord value)?  $default,){ | ||||||
|  | final _that = this; | ||||||
|  | switch (_that) { | ||||||
|  | case _SnSocialCreditRecord() when $default != null: | ||||||
|  | return $default(_that);case _: | ||||||
|  |   return null; | ||||||
|  |  | ||||||
|  | } | ||||||
|  | } | ||||||
|  | /// A variant of `when` that fallback to an `orElse` callback. | ||||||
|  | /// | ||||||
|  | /// It is equivalent to doing: | ||||||
|  | /// ```dart | ||||||
|  | /// switch (sealedClass) { | ||||||
|  | ///   case Subclass(:final field): | ||||||
|  | ///     return ...; | ||||||
|  | ///   case _: | ||||||
|  | ///     return orElse(); | ||||||
|  | /// } | ||||||
|  | /// ``` | ||||||
|  |  | ||||||
|  | @optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String id,  double delta,  String reasonType,  String reason,  DateTime? expiredAt,  DateTime createdAt,  DateTime updatedAt,  DateTime? deletedAt)?  $default,{required TResult orElse(),}) {final _that = this; | ||||||
|  | switch (_that) { | ||||||
|  | case _SnSocialCreditRecord() when $default != null: | ||||||
|  | return $default(_that.id,_that.delta,_that.reasonType,_that.reason,_that.expiredAt,_that.createdAt,_that.updatedAt,_that.deletedAt);case _: | ||||||
|  |   return orElse(); | ||||||
|  |  | ||||||
|  | } | ||||||
|  | } | ||||||
|  | /// A `switch`-like method, using callbacks. | ||||||
|  | /// | ||||||
|  | /// As opposed to `map`, this offers destructuring. | ||||||
|  | /// It is equivalent to doing: | ||||||
|  | /// ```dart | ||||||
|  | /// switch (sealedClass) { | ||||||
|  | ///   case Subclass(:final field): | ||||||
|  | ///     return ...; | ||||||
|  | ///   case Subclass2(:final field2): | ||||||
|  | ///     return ...; | ||||||
|  | /// } | ||||||
|  | /// ``` | ||||||
|  |  | ||||||
|  | @optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String id,  double delta,  String reasonType,  String reason,  DateTime? expiredAt,  DateTime createdAt,  DateTime updatedAt,  DateTime? deletedAt)  $default,) {final _that = this; | ||||||
|  | switch (_that) { | ||||||
|  | case _SnSocialCreditRecord(): | ||||||
|  | return $default(_that.id,_that.delta,_that.reasonType,_that.reason,_that.expiredAt,_that.createdAt,_that.updatedAt,_that.deletedAt);} | ||||||
|  | } | ||||||
|  | /// A variant of `when` that fallback to returning `null` | ||||||
|  | /// | ||||||
|  | /// It is equivalent to doing: | ||||||
|  | /// ```dart | ||||||
|  | /// switch (sealedClass) { | ||||||
|  | ///   case Subclass(:final field): | ||||||
|  | ///     return ...; | ||||||
|  | ///   case _: | ||||||
|  | ///     return null; | ||||||
|  | /// } | ||||||
|  | /// ``` | ||||||
|  |  | ||||||
|  | @optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String id,  double delta,  String reasonType,  String reason,  DateTime? expiredAt,  DateTime createdAt,  DateTime updatedAt,  DateTime? deletedAt)?  $default,) {final _that = this; | ||||||
|  | switch (_that) { | ||||||
|  | case _SnSocialCreditRecord() when $default != null: | ||||||
|  | return $default(_that.id,_that.delta,_that.reasonType,_that.reason,_that.expiredAt,_that.createdAt,_that.updatedAt,_that.deletedAt);case _: | ||||||
|  |   return null; | ||||||
|  |  | ||||||
|  | } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /// @nodoc | ||||||
|  | @JsonSerializable() | ||||||
|  |  | ||||||
|  | class _SnSocialCreditRecord implements SnSocialCreditRecord { | ||||||
|  |   const _SnSocialCreditRecord({required this.id, required this.delta, required this.reasonType, required this.reason, required this.expiredAt, required this.createdAt, required this.updatedAt, required this.deletedAt}); | ||||||
|  |   factory _SnSocialCreditRecord.fromJson(Map<String, dynamic> json) => _$SnSocialCreditRecordFromJson(json); | ||||||
|  |  | ||||||
|  | @override final  String id; | ||||||
|  | @override final  double delta; | ||||||
|  | @override final  String reasonType; | ||||||
|  | @override final  String reason; | ||||||
|  | @override final  DateTime? expiredAt; | ||||||
|  | @override final  DateTime createdAt; | ||||||
|  | @override final  DateTime updatedAt; | ||||||
|  | @override final  DateTime? deletedAt; | ||||||
|  |  | ||||||
|  | /// Create a copy of SnSocialCreditRecord | ||||||
|  | /// with the given fields replaced by the non-null parameter values. | ||||||
|  | @override @JsonKey(includeFromJson: false, includeToJson: false) | ||||||
|  | @pragma('vm:prefer-inline') | ||||||
|  | _$SnSocialCreditRecordCopyWith<_SnSocialCreditRecord> get copyWith => __$SnSocialCreditRecordCopyWithImpl<_SnSocialCreditRecord>(this, _$identity); | ||||||
|  |  | ||||||
|  | @override | ||||||
|  | Map<String, dynamic> toJson() { | ||||||
|  |   return _$SnSocialCreditRecordToJson(this, ); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @override | ||||||
|  | bool operator ==(Object other) { | ||||||
|  |   return identical(this, other) || (other.runtimeType == runtimeType&&other is _SnSocialCreditRecord&&(identical(other.id, id) || other.id == id)&&(identical(other.delta, delta) || other.delta == delta)&&(identical(other.reasonType, reasonType) || other.reasonType == reasonType)&&(identical(other.reason, reason) || other.reason == reason)&&(identical(other.expiredAt, expiredAt) || other.expiredAt == expiredAt)&&(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,delta,reasonType,reason,expiredAt,createdAt,updatedAt,deletedAt); | ||||||
|  |  | ||||||
|  | @override | ||||||
|  | String toString() { | ||||||
|  |   return 'SnSocialCreditRecord(id: $id, delta: $delta, reasonType: $reasonType, reason: $reason, expiredAt: $expiredAt, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt)'; | ||||||
|  | } | ||||||
|  |  | ||||||
|  |  | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /// @nodoc | ||||||
|  | abstract mixin class _$SnSocialCreditRecordCopyWith<$Res> implements $SnSocialCreditRecordCopyWith<$Res> { | ||||||
|  |   factory _$SnSocialCreditRecordCopyWith(_SnSocialCreditRecord value, $Res Function(_SnSocialCreditRecord) _then) = __$SnSocialCreditRecordCopyWithImpl; | ||||||
|  | @override @useResult | ||||||
|  | $Res call({ | ||||||
|  |  String id, double delta, String reasonType, String reason, DateTime? expiredAt, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt | ||||||
|  | }); | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  | } | ||||||
|  | /// @nodoc | ||||||
|  | class __$SnSocialCreditRecordCopyWithImpl<$Res> | ||||||
|  |     implements _$SnSocialCreditRecordCopyWith<$Res> { | ||||||
|  |   __$SnSocialCreditRecordCopyWithImpl(this._self, this._then); | ||||||
|  |  | ||||||
|  |   final _SnSocialCreditRecord _self; | ||||||
|  |   final $Res Function(_SnSocialCreditRecord) _then; | ||||||
|  |  | ||||||
|  | /// Create a copy of SnSocialCreditRecord | ||||||
|  | /// with the given fields replaced by the non-null parameter values. | ||||||
|  | @override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? delta = null,Object? reasonType = null,Object? reason = null,Object? expiredAt = freezed,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,}) { | ||||||
|  |   return _then(_SnSocialCreditRecord( | ||||||
|  | id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable | ||||||
|  | as String,delta: null == delta ? _self.delta : delta // ignore: cast_nullable_to_non_nullable | ||||||
|  | as double,reasonType: null == reasonType ? _self.reasonType : reasonType // ignore: cast_nullable_to_non_nullable | ||||||
|  | as String,reason: null == reason ? _self.reason : reason // ignore: cast_nullable_to_non_nullable | ||||||
|  | as String,expiredAt: freezed == expiredAt ? _self.expiredAt : expiredAt // ignore: cast_nullable_to_non_nullable | ||||||
|  | as DateTime?,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?, | ||||||
|  |   )); | ||||||
|  | } | ||||||
|  |  | ||||||
|  |  | ||||||
| } | } | ||||||
|  |  | ||||||
| // dart format on | // dart format on | ||||||
|   | |||||||
| @@ -12,6 +12,7 @@ _SnAccount _$SnAccountFromJson(Map<String, dynamic> json) => _SnAccount( | |||||||
|   nick: json['nick'] as String, |   nick: json['nick'] as String, | ||||||
|   language: json['language'] as String, |   language: json['language'] as String, | ||||||
|   isSuperuser: json['is_superuser'] as bool, |   isSuperuser: json['is_superuser'] as bool, | ||||||
|  |   automatedId: json['automated_id'] as String?, | ||||||
|   profile: SnAccountProfile.fromJson(json['profile'] as Map<String, dynamic>), |   profile: SnAccountProfile.fromJson(json['profile'] as Map<String, dynamic>), | ||||||
|   perkSubscription: |   perkSubscription: | ||||||
|       json['perk_subscription'] == null |       json['perk_subscription'] == null | ||||||
| @@ -39,6 +40,7 @@ Map<String, dynamic> _$SnAccountToJson(_SnAccount instance) => | |||||||
|       'nick': instance.nick, |       'nick': instance.nick, | ||||||
|       'language': instance.language, |       'language': instance.language, | ||||||
|       'is_superuser': instance.isSuperuser, |       'is_superuser': instance.isSuperuser, | ||||||
|  |       'automated_id': instance.automatedId, | ||||||
|       'profile': instance.profile.toJson(), |       'profile': instance.profile.toJson(), | ||||||
|       'perk_subscription': instance.perkSubscription?.toJson(), |       'perk_subscription': instance.perkSubscription?.toJson(), | ||||||
|       'badges': instance.badges.map((e) => e.toJson()).toList(), |       'badges': instance.badges.map((e) => e.toJson()).toList(), | ||||||
| @@ -348,3 +350,62 @@ Map<String, dynamic> _$SnAuthDeviceWithChallengeeToJson( | |||||||
|   'challenges': instance.challenges.map((e) => e.toJson()).toList(), |   'challenges': instance.challenges.map((e) => e.toJson()).toList(), | ||||||
|   'is_current': instance.isCurrent, |   'is_current': instance.isCurrent, | ||||||
| }; | }; | ||||||
|  |  | ||||||
|  | _SnExperienceRecord _$SnExperienceRecordFromJson(Map<String, dynamic> json) => | ||||||
|  |     _SnExperienceRecord( | ||||||
|  |       id: json['id'] as String, | ||||||
|  |       delta: (json['delta'] as num).toInt(), | ||||||
|  |       reasonType: json['reason_type'] as String, | ||||||
|  |       reason: json['reason'] as String, | ||||||
|  |       bonusMultiplier: (json['bonus_multiplier'] as num?)?.toDouble() ?? 1.0, | ||||||
|  |       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> _$SnExperienceRecordToJson(_SnExperienceRecord instance) => | ||||||
|  |     <String, dynamic>{ | ||||||
|  |       'id': instance.id, | ||||||
|  |       'delta': instance.delta, | ||||||
|  |       'reason_type': instance.reasonType, | ||||||
|  |       'reason': instance.reason, | ||||||
|  |       'bonus_multiplier': instance.bonusMultiplier, | ||||||
|  |       'created_at': instance.createdAt.toIso8601String(), | ||||||
|  |       'updated_at': instance.updatedAt.toIso8601String(), | ||||||
|  |       'deleted_at': instance.deletedAt?.toIso8601String(), | ||||||
|  |     }; | ||||||
|  |  | ||||||
|  | _SnSocialCreditRecord _$SnSocialCreditRecordFromJson( | ||||||
|  |   Map<String, dynamic> json, | ||||||
|  | ) => _SnSocialCreditRecord( | ||||||
|  |   id: json['id'] as String, | ||||||
|  |   delta: (json['delta'] as num).toDouble(), | ||||||
|  |   reasonType: json['reason_type'] as String, | ||||||
|  |   reason: json['reason'] as String, | ||||||
|  |   expiredAt: | ||||||
|  |       json['expired_at'] == null | ||||||
|  |           ? null | ||||||
|  |           : DateTime.parse(json['expired_at'] as String), | ||||||
|  |   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> _$SnSocialCreditRecordToJson( | ||||||
|  |   _SnSocialCreditRecord instance, | ||||||
|  | ) => <String, dynamic>{ | ||||||
|  |   'id': instance.id, | ||||||
|  |   'delta': instance.delta, | ||||||
|  |   'reason_type': instance.reasonType, | ||||||
|  |   'reason': instance.reason, | ||||||
|  |   'expired_at': instance.expiredAt?.toIso8601String(), | ||||||
|  |   'created_at': instance.createdAt.toIso8601String(), | ||||||
|  |   'updated_at': instance.updatedAt.toIso8601String(), | ||||||
|  |   'deleted_at': instance.deletedAt?.toIso8601String(), | ||||||
|  | }; | ||||||
|   | |||||||
							
								
								
									
										63
									
								
								lib/models/bot.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										63
									
								
								lib/models/bot.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,63 @@ | |||||||
|  | import 'package:freezed_annotation/freezed_annotation.dart'; | ||||||
|  | import 'package:island/models/account.dart'; | ||||||
|  | import 'package:island/models/developer.dart'; | ||||||
|  |  | ||||||
|  | part 'bot.freezed.dart'; | ||||||
|  | part 'bot.g.dart'; | ||||||
|  |  | ||||||
|  | @freezed | ||||||
|  | sealed class Bot with _$Bot { | ||||||
|  |   const factory Bot({ | ||||||
|  |     required String id, | ||||||
|  |     required String slug, | ||||||
|  |     required bool isActive, | ||||||
|  |     required String projectId, | ||||||
|  |     required DateTime createdAt, | ||||||
|  |     required DateTime updatedAt, | ||||||
|  |     required SnAccount account, | ||||||
|  |     SnDeveloper? developer, | ||||||
|  |   }) = _Bot; | ||||||
|  |  | ||||||
|  |   factory Bot.fromJson(Map<String, dynamic> json) => _$BotFromJson(json); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @freezed | ||||||
|  | sealed class BotConfig with _$BotConfig { | ||||||
|  |   const factory BotConfig({ | ||||||
|  |     @Default(false) bool isPublic, | ||||||
|  |     @Default(false) bool isInteractive, | ||||||
|  |     @Default([]) List<String> allowedRealms, | ||||||
|  |     @Default([]) List<String> allowedChatTypes, | ||||||
|  |     @Default({}) Map<String, dynamic> metadata, | ||||||
|  |   }) = _BotConfig; | ||||||
|  |  | ||||||
|  |   factory BotConfig.fromJson(Map<String, dynamic> json) => | ||||||
|  |       _$BotConfigFromJson(json); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @freezed | ||||||
|  | sealed class BotLinks with _$BotLinks { | ||||||
|  |   const factory BotLinks({ | ||||||
|  |     String? website, | ||||||
|  |     String? documentation, | ||||||
|  |     String? privacyPolicy, | ||||||
|  |     String? termsOfService, | ||||||
|  |   }) = _BotLinks; | ||||||
|  |  | ||||||
|  |   factory BotLinks.fromJson(Map<String, dynamic> json) => | ||||||
|  |       _$BotLinksFromJson(json); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @freezed | ||||||
|  | sealed class BotSecret with _$BotSecret { | ||||||
|  |   const factory BotSecret({ | ||||||
|  |     @Default('') String id, | ||||||
|  |     @Default('') String secret, | ||||||
|  |     String? description, | ||||||
|  |     DateTime? expiredAt, | ||||||
|  |     @Default('') String botId, | ||||||
|  |   }) = _BotSecret; | ||||||
|  |  | ||||||
|  |   factory BotSecret.fromJson(Map<String, dynamic> json) => | ||||||
|  |       _$BotSecretFromJson(json); | ||||||
|  | } | ||||||
							
								
								
									
										1156
									
								
								lib/models/bot.freezed.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1156
									
								
								lib/models/bot.freezed.dart
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										91
									
								
								lib/models/bot.g.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										91
									
								
								lib/models/bot.g.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,91 @@ | |||||||
|  | // GENERATED CODE - DO NOT MODIFY BY HAND | ||||||
|  |  | ||||||
|  | part of 'bot.dart'; | ||||||
|  |  | ||||||
|  | // ************************************************************************** | ||||||
|  | // JsonSerializableGenerator | ||||||
|  | // ************************************************************************** | ||||||
|  |  | ||||||
|  | _Bot _$BotFromJson(Map<String, dynamic> json) => _Bot( | ||||||
|  |   id: json['id'] as String, | ||||||
|  |   slug: json['slug'] as String, | ||||||
|  |   isActive: json['is_active'] as bool, | ||||||
|  |   projectId: json['project_id'] as String, | ||||||
|  |   createdAt: DateTime.parse(json['created_at'] as String), | ||||||
|  |   updatedAt: DateTime.parse(json['updated_at'] as String), | ||||||
|  |   account: SnAccount.fromJson(json['account'] as Map<String, dynamic>), | ||||||
|  |   developer: | ||||||
|  |       json['developer'] == null | ||||||
|  |           ? null | ||||||
|  |           : SnDeveloper.fromJson(json['developer'] as Map<String, dynamic>), | ||||||
|  | ); | ||||||
|  |  | ||||||
|  | Map<String, dynamic> _$BotToJson(_Bot instance) => <String, dynamic>{ | ||||||
|  |   'id': instance.id, | ||||||
|  |   'slug': instance.slug, | ||||||
|  |   'is_active': instance.isActive, | ||||||
|  |   'project_id': instance.projectId, | ||||||
|  |   'created_at': instance.createdAt.toIso8601String(), | ||||||
|  |   'updated_at': instance.updatedAt.toIso8601String(), | ||||||
|  |   'account': instance.account.toJson(), | ||||||
|  |   'developer': instance.developer?.toJson(), | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | _BotConfig _$BotConfigFromJson(Map<String, dynamic> json) => _BotConfig( | ||||||
|  |   isPublic: json['is_public'] as bool? ?? false, | ||||||
|  |   isInteractive: json['is_interactive'] as bool? ?? false, | ||||||
|  |   allowedRealms: | ||||||
|  |       (json['allowed_realms'] as List<dynamic>?) | ||||||
|  |           ?.map((e) => e as String) | ||||||
|  |           .toList() ?? | ||||||
|  |       const [], | ||||||
|  |   allowedChatTypes: | ||||||
|  |       (json['allowed_chat_types'] as List<dynamic>?) | ||||||
|  |           ?.map((e) => e as String) | ||||||
|  |           .toList() ?? | ||||||
|  |       const [], | ||||||
|  |   metadata: json['metadata'] as Map<String, dynamic>? ?? const {}, | ||||||
|  | ); | ||||||
|  |  | ||||||
|  | Map<String, dynamic> _$BotConfigToJson(_BotConfig instance) => | ||||||
|  |     <String, dynamic>{ | ||||||
|  |       'is_public': instance.isPublic, | ||||||
|  |       'is_interactive': instance.isInteractive, | ||||||
|  |       'allowed_realms': instance.allowedRealms, | ||||||
|  |       'allowed_chat_types': instance.allowedChatTypes, | ||||||
|  |       'metadata': instance.metadata, | ||||||
|  |     }; | ||||||
|  |  | ||||||
|  | _BotLinks _$BotLinksFromJson(Map<String, dynamic> json) => _BotLinks( | ||||||
|  |   website: json['website'] as String?, | ||||||
|  |   documentation: json['documentation'] as String?, | ||||||
|  |   privacyPolicy: json['privacy_policy'] as String?, | ||||||
|  |   termsOfService: json['terms_of_service'] as String?, | ||||||
|  | ); | ||||||
|  |  | ||||||
|  | Map<String, dynamic> _$BotLinksToJson(_BotLinks instance) => <String, dynamic>{ | ||||||
|  |   'website': instance.website, | ||||||
|  |   'documentation': instance.documentation, | ||||||
|  |   'privacy_policy': instance.privacyPolicy, | ||||||
|  |   'terms_of_service': instance.termsOfService, | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | _BotSecret _$BotSecretFromJson(Map<String, dynamic> json) => _BotSecret( | ||||||
|  |   id: json['id'] as String? ?? '', | ||||||
|  |   secret: json['secret'] as String? ?? '', | ||||||
|  |   description: json['description'] as String?, | ||||||
|  |   expiredAt: | ||||||
|  |       json['expired_at'] == null | ||||||
|  |           ? null | ||||||
|  |           : DateTime.parse(json['expired_at'] as String), | ||||||
|  |   botId: json['bot_id'] as String? ?? '', | ||||||
|  | ); | ||||||
|  |  | ||||||
|  | Map<String, dynamic> _$BotSecretToJson(_BotSecret instance) => | ||||||
|  |     <String, dynamic>{ | ||||||
|  |       'id': instance.id, | ||||||
|  |       'secret': instance.secret, | ||||||
|  |       'description': instance.description, | ||||||
|  |       'expired_at': instance.expiredAt?.toIso8601String(), | ||||||
|  |       'bot_id': instance.botId, | ||||||
|  |     }; | ||||||
							
								
								
									
										20
									
								
								lib/models/bot_key.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								lib/models/bot_key.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,20 @@ | |||||||
|  | import 'package:freezed_annotation/freezed_annotation.dart'; | ||||||
|  |  | ||||||
|  | part 'bot_key.freezed.dart'; | ||||||
|  | part 'bot_key.g.dart'; | ||||||
|  |  | ||||||
|  | @freezed | ||||||
|  | sealed class SnAccountApiKey with _$SnAccountApiKey { | ||||||
|  |   const factory SnAccountApiKey({ | ||||||
|  |     required String id, | ||||||
|  |     required String label, | ||||||
|  |     required String accountId, | ||||||
|  |     required String sessionId, | ||||||
|  |     required DateTime createdAt, | ||||||
|  |     required DateTime updatedAt, | ||||||
|  |     String? key, | ||||||
|  |   }) = _SnAccountApiKey; | ||||||
|  |  | ||||||
|  |   factory SnAccountApiKey.fromJson(Map<String, dynamic> json) => | ||||||
|  |       _$SnAccountApiKeyFromJson(json); | ||||||
|  | } | ||||||
							
								
								
									
										289
									
								
								lib/models/bot_key.freezed.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										289
									
								
								lib/models/bot_key.freezed.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,289 @@ | |||||||
|  | // GENERATED CODE - DO NOT MODIFY BY HAND | ||||||
|  | // coverage:ignore-file | ||||||
|  | // 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 'bot_key.dart'; | ||||||
|  |  | ||||||
|  | // ************************************************************************** | ||||||
|  | // FreezedGenerator | ||||||
|  | // ************************************************************************** | ||||||
|  |  | ||||||
|  | // dart format off | ||||||
|  | T _$identity<T>(T value) => value; | ||||||
|  |  | ||||||
|  | /// @nodoc | ||||||
|  | mixin _$SnAccountApiKey { | ||||||
|  |  | ||||||
|  |  String get id; String get label; String get accountId; String get sessionId; DateTime get createdAt; DateTime get updatedAt; String? get key; | ||||||
|  | /// Create a copy of SnAccountApiKey | ||||||
|  | /// with the given fields replaced by the non-null parameter values. | ||||||
|  | @JsonKey(includeFromJson: false, includeToJson: false) | ||||||
|  | @pragma('vm:prefer-inline') | ||||||
|  | $SnAccountApiKeyCopyWith<SnAccountApiKey> get copyWith => _$SnAccountApiKeyCopyWithImpl<SnAccountApiKey>(this as SnAccountApiKey, _$identity); | ||||||
|  |  | ||||||
|  |   /// Serializes this SnAccountApiKey to a JSON map. | ||||||
|  |   Map<String, dynamic> toJson(); | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @override | ||||||
|  | bool operator ==(Object other) { | ||||||
|  |   return identical(this, other) || (other.runtimeType == runtimeType&&other is SnAccountApiKey&&(identical(other.id, id) || other.id == id)&&(identical(other.label, label) || other.label == label)&&(identical(other.accountId, accountId) || other.accountId == accountId)&&(identical(other.sessionId, sessionId) || other.sessionId == sessionId)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.key, key) || other.key == key)); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @JsonKey(includeFromJson: false, includeToJson: false) | ||||||
|  | @override | ||||||
|  | int get hashCode => Object.hash(runtimeType,id,label,accountId,sessionId,createdAt,updatedAt,key); | ||||||
|  |  | ||||||
|  | @override | ||||||
|  | String toString() { | ||||||
|  |   return 'SnAccountApiKey(id: $id, label: $label, accountId: $accountId, sessionId: $sessionId, createdAt: $createdAt, updatedAt: $updatedAt, key: $key)'; | ||||||
|  | } | ||||||
|  |  | ||||||
|  |  | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /// @nodoc | ||||||
|  | abstract mixin class $SnAccountApiKeyCopyWith<$Res>  { | ||||||
|  |   factory $SnAccountApiKeyCopyWith(SnAccountApiKey value, $Res Function(SnAccountApiKey) _then) = _$SnAccountApiKeyCopyWithImpl; | ||||||
|  | @useResult | ||||||
|  | $Res call({ | ||||||
|  |  String id, String label, String accountId, String sessionId, DateTime createdAt, DateTime updatedAt, String? key | ||||||
|  | }); | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  | } | ||||||
|  | /// @nodoc | ||||||
|  | class _$SnAccountApiKeyCopyWithImpl<$Res> | ||||||
|  |     implements $SnAccountApiKeyCopyWith<$Res> { | ||||||
|  |   _$SnAccountApiKeyCopyWithImpl(this._self, this._then); | ||||||
|  |  | ||||||
|  |   final SnAccountApiKey _self; | ||||||
|  |   final $Res Function(SnAccountApiKey) _then; | ||||||
|  |  | ||||||
|  | /// Create a copy of SnAccountApiKey | ||||||
|  | /// with the given fields replaced by the non-null parameter values. | ||||||
|  | @pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? label = null,Object? accountId = null,Object? sessionId = null,Object? createdAt = null,Object? updatedAt = null,Object? key = freezed,}) { | ||||||
|  |   return _then(_self.copyWith( | ||||||
|  | id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable | ||||||
|  | as String,label: null == label ? _self.label : label // ignore: cast_nullable_to_non_nullable | ||||||
|  | as String,accountId: null == accountId ? _self.accountId : accountId // ignore: cast_nullable_to_non_nullable | ||||||
|  | as String,sessionId: null == sessionId ? _self.sessionId : sessionId // ignore: cast_nullable_to_non_nullable | ||||||
|  | as String,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,key: freezed == key ? _self.key : key // ignore: cast_nullable_to_non_nullable | ||||||
|  | as String?, | ||||||
|  |   )); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | } | ||||||
|  |  | ||||||
|  |  | ||||||
|  | /// Adds pattern-matching-related methods to [SnAccountApiKey]. | ||||||
|  | extension SnAccountApiKeyPatterns on SnAccountApiKey { | ||||||
|  | /// A variant of `map` that fallback to returning `orElse`. | ||||||
|  | /// | ||||||
|  | /// It is equivalent to doing: | ||||||
|  | /// ```dart | ||||||
|  | /// switch (sealedClass) { | ||||||
|  | ///   case final Subclass value: | ||||||
|  | ///     return ...; | ||||||
|  | ///   case _: | ||||||
|  | ///     return orElse(); | ||||||
|  | /// } | ||||||
|  | /// ``` | ||||||
|  |  | ||||||
|  | @optionalTypeArgs TResult maybeMap<TResult extends Object?>(TResult Function( _SnAccountApiKey value)?  $default,{required TResult orElse(),}){ | ||||||
|  | final _that = this; | ||||||
|  | switch (_that) { | ||||||
|  | case _SnAccountApiKey() when $default != null: | ||||||
|  | return $default(_that);case _: | ||||||
|  |   return orElse(); | ||||||
|  |  | ||||||
|  | } | ||||||
|  | } | ||||||
|  | /// A `switch`-like method, using callbacks. | ||||||
|  | /// | ||||||
|  | /// Callbacks receives the raw object, upcasted. | ||||||
|  | /// It is equivalent to doing: | ||||||
|  | /// ```dart | ||||||
|  | /// switch (sealedClass) { | ||||||
|  | ///   case final Subclass value: | ||||||
|  | ///     return ...; | ||||||
|  | ///   case final Subclass2 value: | ||||||
|  | ///     return ...; | ||||||
|  | /// } | ||||||
|  | /// ``` | ||||||
|  |  | ||||||
|  | @optionalTypeArgs TResult map<TResult extends Object?>(TResult Function( _SnAccountApiKey value)  $default,){ | ||||||
|  | final _that = this; | ||||||
|  | switch (_that) { | ||||||
|  | case _SnAccountApiKey(): | ||||||
|  | return $default(_that);} | ||||||
|  | } | ||||||
|  | /// A variant of `map` that fallback to returning `null`. | ||||||
|  | /// | ||||||
|  | /// It is equivalent to doing: | ||||||
|  | /// ```dart | ||||||
|  | /// switch (sealedClass) { | ||||||
|  | ///   case final Subclass value: | ||||||
|  | ///     return ...; | ||||||
|  | ///   case _: | ||||||
|  | ///     return null; | ||||||
|  | /// } | ||||||
|  | /// ``` | ||||||
|  |  | ||||||
|  | @optionalTypeArgs TResult? mapOrNull<TResult extends Object?>(TResult? Function( _SnAccountApiKey value)?  $default,){ | ||||||
|  | final _that = this; | ||||||
|  | switch (_that) { | ||||||
|  | case _SnAccountApiKey() when $default != null: | ||||||
|  | return $default(_that);case _: | ||||||
|  |   return null; | ||||||
|  |  | ||||||
|  | } | ||||||
|  | } | ||||||
|  | /// A variant of `when` that fallback to an `orElse` callback. | ||||||
|  | /// | ||||||
|  | /// It is equivalent to doing: | ||||||
|  | /// ```dart | ||||||
|  | /// switch (sealedClass) { | ||||||
|  | ///   case Subclass(:final field): | ||||||
|  | ///     return ...; | ||||||
|  | ///   case _: | ||||||
|  | ///     return orElse(); | ||||||
|  | /// } | ||||||
|  | /// ``` | ||||||
|  |  | ||||||
|  | @optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String id,  String label,  String accountId,  String sessionId,  DateTime createdAt,  DateTime updatedAt,  String? key)?  $default,{required TResult orElse(),}) {final _that = this; | ||||||
|  | switch (_that) { | ||||||
|  | case _SnAccountApiKey() when $default != null: | ||||||
|  | return $default(_that.id,_that.label,_that.accountId,_that.sessionId,_that.createdAt,_that.updatedAt,_that.key);case _: | ||||||
|  |   return orElse(); | ||||||
|  |  | ||||||
|  | } | ||||||
|  | } | ||||||
|  | /// A `switch`-like method, using callbacks. | ||||||
|  | /// | ||||||
|  | /// As opposed to `map`, this offers destructuring. | ||||||
|  | /// It is equivalent to doing: | ||||||
|  | /// ```dart | ||||||
|  | /// switch (sealedClass) { | ||||||
|  | ///   case Subclass(:final field): | ||||||
|  | ///     return ...; | ||||||
|  | ///   case Subclass2(:final field2): | ||||||
|  | ///     return ...; | ||||||
|  | /// } | ||||||
|  | /// ``` | ||||||
|  |  | ||||||
|  | @optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String id,  String label,  String accountId,  String sessionId,  DateTime createdAt,  DateTime updatedAt,  String? key)  $default,) {final _that = this; | ||||||
|  | switch (_that) { | ||||||
|  | case _SnAccountApiKey(): | ||||||
|  | return $default(_that.id,_that.label,_that.accountId,_that.sessionId,_that.createdAt,_that.updatedAt,_that.key);} | ||||||
|  | } | ||||||
|  | /// A variant of `when` that fallback to returning `null` | ||||||
|  | /// | ||||||
|  | /// It is equivalent to doing: | ||||||
|  | /// ```dart | ||||||
|  | /// switch (sealedClass) { | ||||||
|  | ///   case Subclass(:final field): | ||||||
|  | ///     return ...; | ||||||
|  | ///   case _: | ||||||
|  | ///     return null; | ||||||
|  | /// } | ||||||
|  | /// ``` | ||||||
|  |  | ||||||
|  | @optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String id,  String label,  String accountId,  String sessionId,  DateTime createdAt,  DateTime updatedAt,  String? key)?  $default,) {final _that = this; | ||||||
|  | switch (_that) { | ||||||
|  | case _SnAccountApiKey() when $default != null: | ||||||
|  | return $default(_that.id,_that.label,_that.accountId,_that.sessionId,_that.createdAt,_that.updatedAt,_that.key);case _: | ||||||
|  |   return null; | ||||||
|  |  | ||||||
|  | } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /// @nodoc | ||||||
|  | @JsonSerializable() | ||||||
|  |  | ||||||
|  | class _SnAccountApiKey implements SnAccountApiKey { | ||||||
|  |   const _SnAccountApiKey({required this.id, required this.label, required this.accountId, required this.sessionId, required this.createdAt, required this.updatedAt, this.key}); | ||||||
|  |   factory _SnAccountApiKey.fromJson(Map<String, dynamic> json) => _$SnAccountApiKeyFromJson(json); | ||||||
|  |  | ||||||
|  | @override final  String id; | ||||||
|  | @override final  String label; | ||||||
|  | @override final  String accountId; | ||||||
|  | @override final  String sessionId; | ||||||
|  | @override final  DateTime createdAt; | ||||||
|  | @override final  DateTime updatedAt; | ||||||
|  | @override final  String? key; | ||||||
|  |  | ||||||
|  | /// Create a copy of SnAccountApiKey | ||||||
|  | /// with the given fields replaced by the non-null parameter values. | ||||||
|  | @override @JsonKey(includeFromJson: false, includeToJson: false) | ||||||
|  | @pragma('vm:prefer-inline') | ||||||
|  | _$SnAccountApiKeyCopyWith<_SnAccountApiKey> get copyWith => __$SnAccountApiKeyCopyWithImpl<_SnAccountApiKey>(this, _$identity); | ||||||
|  |  | ||||||
|  | @override | ||||||
|  | Map<String, dynamic> toJson() { | ||||||
|  |   return _$SnAccountApiKeyToJson(this, ); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @override | ||||||
|  | bool operator ==(Object other) { | ||||||
|  |   return identical(this, other) || (other.runtimeType == runtimeType&&other is _SnAccountApiKey&&(identical(other.id, id) || other.id == id)&&(identical(other.label, label) || other.label == label)&&(identical(other.accountId, accountId) || other.accountId == accountId)&&(identical(other.sessionId, sessionId) || other.sessionId == sessionId)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.key, key) || other.key == key)); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @JsonKey(includeFromJson: false, includeToJson: false) | ||||||
|  | @override | ||||||
|  | int get hashCode => Object.hash(runtimeType,id,label,accountId,sessionId,createdAt,updatedAt,key); | ||||||
|  |  | ||||||
|  | @override | ||||||
|  | String toString() { | ||||||
|  |   return 'SnAccountApiKey(id: $id, label: $label, accountId: $accountId, sessionId: $sessionId, createdAt: $createdAt, updatedAt: $updatedAt, key: $key)'; | ||||||
|  | } | ||||||
|  |  | ||||||
|  |  | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /// @nodoc | ||||||
|  | abstract mixin class _$SnAccountApiKeyCopyWith<$Res> implements $SnAccountApiKeyCopyWith<$Res> { | ||||||
|  |   factory _$SnAccountApiKeyCopyWith(_SnAccountApiKey value, $Res Function(_SnAccountApiKey) _then) = __$SnAccountApiKeyCopyWithImpl; | ||||||
|  | @override @useResult | ||||||
|  | $Res call({ | ||||||
|  |  String id, String label, String accountId, String sessionId, DateTime createdAt, DateTime updatedAt, String? key | ||||||
|  | }); | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  | } | ||||||
|  | /// @nodoc | ||||||
|  | class __$SnAccountApiKeyCopyWithImpl<$Res> | ||||||
|  |     implements _$SnAccountApiKeyCopyWith<$Res> { | ||||||
|  |   __$SnAccountApiKeyCopyWithImpl(this._self, this._then); | ||||||
|  |  | ||||||
|  |   final _SnAccountApiKey _self; | ||||||
|  |   final $Res Function(_SnAccountApiKey) _then; | ||||||
|  |  | ||||||
|  | /// Create a copy of SnAccountApiKey | ||||||
|  | /// with the given fields replaced by the non-null parameter values. | ||||||
|  | @override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? label = null,Object? accountId = null,Object? sessionId = null,Object? createdAt = null,Object? updatedAt = null,Object? key = freezed,}) { | ||||||
|  |   return _then(_SnAccountApiKey( | ||||||
|  | id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable | ||||||
|  | as String,label: null == label ? _self.label : label // ignore: cast_nullable_to_non_nullable | ||||||
|  | as String,accountId: null == accountId ? _self.accountId : accountId // ignore: cast_nullable_to_non_nullable | ||||||
|  | as String,sessionId: null == sessionId ? _self.sessionId : sessionId // ignore: cast_nullable_to_non_nullable | ||||||
|  | as String,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,key: freezed == key ? _self.key : key // ignore: cast_nullable_to_non_nullable | ||||||
|  | as String?, | ||||||
|  |   )); | ||||||
|  | } | ||||||
|  |  | ||||||
|  |  | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // dart format on | ||||||
							
								
								
									
										29
									
								
								lib/models/bot_key.g.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								lib/models/bot_key.g.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,29 @@ | |||||||
|  | // GENERATED CODE - DO NOT MODIFY BY HAND | ||||||
|  |  | ||||||
|  | part of 'bot_key.dart'; | ||||||
|  |  | ||||||
|  | // ************************************************************************** | ||||||
|  | // JsonSerializableGenerator | ||||||
|  | // ************************************************************************** | ||||||
|  |  | ||||||
|  | _SnAccountApiKey _$SnAccountApiKeyFromJson(Map<String, dynamic> json) => | ||||||
|  |     _SnAccountApiKey( | ||||||
|  |       id: json['id'] as String, | ||||||
|  |       label: json['label'] as String, | ||||||
|  |       accountId: json['account_id'] as String, | ||||||
|  |       sessionId: json['session_id'] as String, | ||||||
|  |       createdAt: DateTime.parse(json['created_at'] as String), | ||||||
|  |       updatedAt: DateTime.parse(json['updated_at'] as String), | ||||||
|  |       key: json['key'] as String?, | ||||||
|  |     ); | ||||||
|  |  | ||||||
|  | Map<String, dynamic> _$SnAccountApiKeyToJson(_SnAccountApiKey instance) => | ||||||
|  |     <String, dynamic>{ | ||||||
|  |       'id': instance.id, | ||||||
|  |       'label': instance.label, | ||||||
|  |       'account_id': instance.accountId, | ||||||
|  |       'session_id': instance.sessionId, | ||||||
|  |       'created_at': instance.createdAt.toIso8601String(), | ||||||
|  |       'updated_at': instance.updatedAt.toIso8601String(), | ||||||
|  |       'key': instance.key, | ||||||
|  |     }; | ||||||
| @@ -104,7 +104,7 @@ sealed class SnChatMember with _$SnChatMember { | |||||||
| sealed class SnChatSummary with _$SnChatSummary { | sealed class SnChatSummary with _$SnChatSummary { | ||||||
|   const factory SnChatSummary({ |   const factory SnChatSummary({ | ||||||
|     required int unreadCount, |     required int unreadCount, | ||||||
|     required SnChatMessage lastMessage, |     required SnChatMessage? lastMessage, | ||||||
|   }) = _SnChatSummary; |   }) = _SnChatSummary; | ||||||
|  |  | ||||||
|   factory SnChatSummary.fromJson(Map<String, dynamic> json) => |   factory SnChatSummary.fromJson(Map<String, dynamic> json) => | ||||||
|   | |||||||
| @@ -1410,7 +1410,7 @@ $SnAccountStatusCopyWith<$Res>? get status { | |||||||
| /// @nodoc | /// @nodoc | ||||||
| mixin _$SnChatSummary { | mixin _$SnChatSummary { | ||||||
|  |  | ||||||
|  int get unreadCount; SnChatMessage get lastMessage; |  int get unreadCount; SnChatMessage? get lastMessage; | ||||||
| /// Create a copy of SnChatSummary | /// Create a copy of SnChatSummary | ||||||
| /// 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) | ||||||
| @@ -1443,11 +1443,11 @@ abstract mixin class $SnChatSummaryCopyWith<$Res>  { | |||||||
|   factory $SnChatSummaryCopyWith(SnChatSummary value, $Res Function(SnChatSummary) _then) = _$SnChatSummaryCopyWithImpl; |   factory $SnChatSummaryCopyWith(SnChatSummary value, $Res Function(SnChatSummary) _then) = _$SnChatSummaryCopyWithImpl; | ||||||
| @useResult | @useResult | ||||||
| $Res call({ | $Res call({ | ||||||
|  int unreadCount, SnChatMessage lastMessage |  int unreadCount, SnChatMessage? lastMessage | ||||||
| }); | }); | ||||||
|  |  | ||||||
|  |  | ||||||
| $SnChatMessageCopyWith<$Res> get lastMessage; | $SnChatMessageCopyWith<$Res>? get lastMessage; | ||||||
|  |  | ||||||
| } | } | ||||||
| /// @nodoc | /// @nodoc | ||||||
| @@ -1460,20 +1460,23 @@ class _$SnChatSummaryCopyWithImpl<$Res> | |||||||
|  |  | ||||||
| /// Create a copy of SnChatSummary | /// Create a copy of SnChatSummary | ||||||
| /// 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? unreadCount = null,Object? lastMessage = null,}) { | @pragma('vm:prefer-inline') @override $Res call({Object? unreadCount = null,Object? lastMessage = freezed,}) { | ||||||
|   return _then(_self.copyWith( |   return _then(_self.copyWith( | ||||||
| unreadCount: null == unreadCount ? _self.unreadCount : unreadCount // ignore: cast_nullable_to_non_nullable | unreadCount: null == unreadCount ? _self.unreadCount : unreadCount // ignore: cast_nullable_to_non_nullable | ||||||
| as int,lastMessage: null == lastMessage ? _self.lastMessage : lastMessage // ignore: cast_nullable_to_non_nullable | as int,lastMessage: freezed == lastMessage ? _self.lastMessage : lastMessage // ignore: cast_nullable_to_non_nullable | ||||||
| as SnChatMessage, | as SnChatMessage?, | ||||||
|   )); |   )); | ||||||
| } | } | ||||||
| /// Create a copy of SnChatSummary | /// Create a copy of SnChatSummary | ||||||
| /// with the given fields replaced by the non-null parameter values. | /// with the given fields replaced by the non-null parameter values. | ||||||
| @override | @override | ||||||
| @pragma('vm:prefer-inline') | @pragma('vm:prefer-inline') | ||||||
| $SnChatMessageCopyWith<$Res> get lastMessage { | $SnChatMessageCopyWith<$Res>? get lastMessage { | ||||||
|    |     if (_self.lastMessage == null) { | ||||||
|   return $SnChatMessageCopyWith<$Res>(_self.lastMessage, (value) { |     return null; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   return $SnChatMessageCopyWith<$Res>(_self.lastMessage!, (value) { | ||||||
|     return _then(_self.copyWith(lastMessage: value)); |     return _then(_self.copyWith(lastMessage: value)); | ||||||
|   }); |   }); | ||||||
| } | } | ||||||
| @@ -1555,7 +1558,7 @@ return $default(_that);case _: | |||||||
| /// } | /// } | ||||||
| /// ``` | /// ``` | ||||||
|  |  | ||||||
| @optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( int unreadCount,  SnChatMessage lastMessage)?  $default,{required TResult orElse(),}) {final _that = this; | @optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( int unreadCount,  SnChatMessage? lastMessage)?  $default,{required TResult orElse(),}) {final _that = this; | ||||||
| switch (_that) { | switch (_that) { | ||||||
| case _SnChatSummary() when $default != null: | case _SnChatSummary() when $default != null: | ||||||
| return $default(_that.unreadCount,_that.lastMessage);case _: | return $default(_that.unreadCount,_that.lastMessage);case _: | ||||||
| @@ -1576,7 +1579,7 @@ return $default(_that.unreadCount,_that.lastMessage);case _: | |||||||
| /// } | /// } | ||||||
| /// ``` | /// ``` | ||||||
|  |  | ||||||
| @optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( int unreadCount,  SnChatMessage lastMessage)  $default,) {final _that = this; | @optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( int unreadCount,  SnChatMessage? lastMessage)  $default,) {final _that = this; | ||||||
| switch (_that) { | switch (_that) { | ||||||
| case _SnChatSummary(): | case _SnChatSummary(): | ||||||
| return $default(_that.unreadCount,_that.lastMessage);} | return $default(_that.unreadCount,_that.lastMessage);} | ||||||
| @@ -1593,7 +1596,7 @@ return $default(_that.unreadCount,_that.lastMessage);} | |||||||
| /// } | /// } | ||||||
| /// ``` | /// ``` | ||||||
|  |  | ||||||
| @optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( int unreadCount,  SnChatMessage lastMessage)?  $default,) {final _that = this; | @optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( int unreadCount,  SnChatMessage? lastMessage)?  $default,) {final _that = this; | ||||||
| switch (_that) { | switch (_that) { | ||||||
| case _SnChatSummary() when $default != null: | case _SnChatSummary() when $default != null: | ||||||
| return $default(_that.unreadCount,_that.lastMessage);case _: | return $default(_that.unreadCount,_that.lastMessage);case _: | ||||||
| @@ -1612,7 +1615,7 @@ class _SnChatSummary implements SnChatSummary { | |||||||
|   factory _SnChatSummary.fromJson(Map<String, dynamic> json) => _$SnChatSummaryFromJson(json); |   factory _SnChatSummary.fromJson(Map<String, dynamic> json) => _$SnChatSummaryFromJson(json); | ||||||
|  |  | ||||||
| @override final  int unreadCount; | @override final  int unreadCount; | ||||||
| @override final  SnChatMessage lastMessage; | @override final  SnChatMessage? lastMessage; | ||||||
|  |  | ||||||
| /// Create a copy of SnChatSummary | /// Create a copy of SnChatSummary | ||||||
| /// with the given fields replaced by the non-null parameter values. | /// with the given fields replaced by the non-null parameter values. | ||||||
| @@ -1647,11 +1650,11 @@ abstract mixin class _$SnChatSummaryCopyWith<$Res> implements $SnChatSummaryCopy | |||||||
|   factory _$SnChatSummaryCopyWith(_SnChatSummary value, $Res Function(_SnChatSummary) _then) = __$SnChatSummaryCopyWithImpl; |   factory _$SnChatSummaryCopyWith(_SnChatSummary value, $Res Function(_SnChatSummary) _then) = __$SnChatSummaryCopyWithImpl; | ||||||
| @override @useResult | @override @useResult | ||||||
| $Res call({ | $Res call({ | ||||||
|  int unreadCount, SnChatMessage lastMessage |  int unreadCount, SnChatMessage? lastMessage | ||||||
| }); | }); | ||||||
|  |  | ||||||
|  |  | ||||||
| @override $SnChatMessageCopyWith<$Res> get lastMessage; | @override $SnChatMessageCopyWith<$Res>? get lastMessage; | ||||||
|  |  | ||||||
| } | } | ||||||
| /// @nodoc | /// @nodoc | ||||||
| @@ -1664,11 +1667,11 @@ class __$SnChatSummaryCopyWithImpl<$Res> | |||||||
|  |  | ||||||
| /// Create a copy of SnChatSummary | /// Create a copy of SnChatSummary | ||||||
| /// 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? unreadCount = null,Object? lastMessage = null,}) { | @override @pragma('vm:prefer-inline') $Res call({Object? unreadCount = null,Object? lastMessage = freezed,}) { | ||||||
|   return _then(_SnChatSummary( |   return _then(_SnChatSummary( | ||||||
| unreadCount: null == unreadCount ? _self.unreadCount : unreadCount // ignore: cast_nullable_to_non_nullable | unreadCount: null == unreadCount ? _self.unreadCount : unreadCount // ignore: cast_nullable_to_non_nullable | ||||||
| as int,lastMessage: null == lastMessage ? _self.lastMessage : lastMessage // ignore: cast_nullable_to_non_nullable | as int,lastMessage: freezed == lastMessage ? _self.lastMessage : lastMessage // ignore: cast_nullable_to_non_nullable | ||||||
| as SnChatMessage, | as SnChatMessage?, | ||||||
|   )); |   )); | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -1676,9 +1679,12 @@ as SnChatMessage, | |||||||
| /// with the given fields replaced by the non-null parameter values. | /// with the given fields replaced by the non-null parameter values. | ||||||
| @override | @override | ||||||
| @pragma('vm:prefer-inline') | @pragma('vm:prefer-inline') | ||||||
| $SnChatMessageCopyWith<$Res> get lastMessage { | $SnChatMessageCopyWith<$Res>? get lastMessage { | ||||||
|    |     if (_self.lastMessage == null) { | ||||||
|   return $SnChatMessageCopyWith<$Res>(_self.lastMessage, (value) { |     return null; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   return $SnChatMessageCopyWith<$Res>(_self.lastMessage!, (value) { | ||||||
|     return _then(_self.copyWith(lastMessage: value)); |     return _then(_self.copyWith(lastMessage: value)); | ||||||
|   }); |   }); | ||||||
| } | } | ||||||
|   | |||||||
| @@ -213,15 +213,18 @@ Map<String, dynamic> _$SnChatMemberToJson(_SnChatMember instance) => | |||||||
| _SnChatSummary _$SnChatSummaryFromJson(Map<String, dynamic> json) => | _SnChatSummary _$SnChatSummaryFromJson(Map<String, dynamic> json) => | ||||||
|     _SnChatSummary( |     _SnChatSummary( | ||||||
|       unreadCount: (json['unread_count'] as num).toInt(), |       unreadCount: (json['unread_count'] as num).toInt(), | ||||||
|       lastMessage: SnChatMessage.fromJson( |       lastMessage: | ||||||
|         json['last_message'] as Map<String, dynamic>, |           json['last_message'] == null | ||||||
|       ), |               ? null | ||||||
|  |               : SnChatMessage.fromJson( | ||||||
|  |                 json['last_message'] as Map<String, dynamic>, | ||||||
|  |               ), | ||||||
|     ); |     ); | ||||||
|  |  | ||||||
| Map<String, dynamic> _$SnChatSummaryToJson(_SnChatSummary instance) => | Map<String, dynamic> _$SnChatSummaryToJson(_SnChatSummary instance) => | ||||||
|     <String, dynamic>{ |     <String, dynamic>{ | ||||||
|       'unread_count': instance.unreadCount, |       'unread_count': instance.unreadCount, | ||||||
|       'last_message': instance.lastMessage.toJson(), |       'last_message': instance.lastMessage?.toJson(), | ||||||
|     }; |     }; | ||||||
|  |  | ||||||
| _MessageChange _$MessageChangeFromJson(Map<String, dynamic> json) => | _MessageChange _$MessageChangeFromJson(Map<String, dynamic> json) => | ||||||
|   | |||||||
							
								
								
									
										19
									
								
								lib/models/custom_app_secret.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								lib/models/custom_app_secret.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,19 @@ | |||||||
|  | import 'package:freezed_annotation/freezed_annotation.dart'; | ||||||
|  |  | ||||||
|  | part 'custom_app_secret.freezed.dart'; | ||||||
|  | part 'custom_app_secret.g.dart'; | ||||||
|  |  | ||||||
|  | @freezed | ||||||
|  | sealed class CustomAppSecret with _$CustomAppSecret { | ||||||
|  |   const factory CustomAppSecret({ | ||||||
|  |     required String id, | ||||||
|  |     required String? secret, | ||||||
|  |     required DateTime createdAt, | ||||||
|  |     String? description, | ||||||
|  |     int? expiresIn, | ||||||
|  |     bool? isOidc, | ||||||
|  |   }) = _CustomAppSecret; | ||||||
|  |  | ||||||
|  |   factory CustomAppSecret.fromJson(Map<String, dynamic> json) => | ||||||
|  |       _$CustomAppSecretFromJson(json); | ||||||
|  | } | ||||||
							
								
								
									
										286
									
								
								lib/models/custom_app_secret.freezed.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										286
									
								
								lib/models/custom_app_secret.freezed.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,286 @@ | |||||||
|  | // GENERATED CODE - DO NOT MODIFY BY HAND | ||||||
|  | // coverage:ignore-file | ||||||
|  | // 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 'custom_app_secret.dart'; | ||||||
|  |  | ||||||
|  | // ************************************************************************** | ||||||
|  | // FreezedGenerator | ||||||
|  | // ************************************************************************** | ||||||
|  |  | ||||||
|  | // dart format off | ||||||
|  | T _$identity<T>(T value) => value; | ||||||
|  |  | ||||||
|  | /// @nodoc | ||||||
|  | mixin _$CustomAppSecret { | ||||||
|  |  | ||||||
|  |  String get id; String? get secret; DateTime get createdAt; String? get description; int? get expiresIn; bool? get isOidc; | ||||||
|  | /// Create a copy of CustomAppSecret | ||||||
|  | /// with the given fields replaced by the non-null parameter values. | ||||||
|  | @JsonKey(includeFromJson: false, includeToJson: false) | ||||||
|  | @pragma('vm:prefer-inline') | ||||||
|  | $CustomAppSecretCopyWith<CustomAppSecret> get copyWith => _$CustomAppSecretCopyWithImpl<CustomAppSecret>(this as CustomAppSecret, _$identity); | ||||||
|  |  | ||||||
|  |   /// Serializes this CustomAppSecret to a JSON map. | ||||||
|  |   Map<String, dynamic> toJson(); | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @override | ||||||
|  | bool operator ==(Object other) { | ||||||
|  |   return identical(this, other) || (other.runtimeType == runtimeType&&other is CustomAppSecret&&(identical(other.id, id) || other.id == id)&&(identical(other.secret, secret) || other.secret == secret)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.description, description) || other.description == description)&&(identical(other.expiresIn, expiresIn) || other.expiresIn == expiresIn)&&(identical(other.isOidc, isOidc) || other.isOidc == isOidc)); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @JsonKey(includeFromJson: false, includeToJson: false) | ||||||
|  | @override | ||||||
|  | int get hashCode => Object.hash(runtimeType,id,secret,createdAt,description,expiresIn,isOidc); | ||||||
|  |  | ||||||
|  | @override | ||||||
|  | String toString() { | ||||||
|  |   return 'CustomAppSecret(id: $id, secret: $secret, createdAt: $createdAt, description: $description, expiresIn: $expiresIn, isOidc: $isOidc)'; | ||||||
|  | } | ||||||
|  |  | ||||||
|  |  | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /// @nodoc | ||||||
|  | abstract mixin class $CustomAppSecretCopyWith<$Res>  { | ||||||
|  |   factory $CustomAppSecretCopyWith(CustomAppSecret value, $Res Function(CustomAppSecret) _then) = _$CustomAppSecretCopyWithImpl; | ||||||
|  | @useResult | ||||||
|  | $Res call({ | ||||||
|  |  String id, String? secret, DateTime createdAt, String? description, int? expiresIn, bool? isOidc | ||||||
|  | }); | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  | } | ||||||
|  | /// @nodoc | ||||||
|  | class _$CustomAppSecretCopyWithImpl<$Res> | ||||||
|  |     implements $CustomAppSecretCopyWith<$Res> { | ||||||
|  |   _$CustomAppSecretCopyWithImpl(this._self, this._then); | ||||||
|  |  | ||||||
|  |   final CustomAppSecret _self; | ||||||
|  |   final $Res Function(CustomAppSecret) _then; | ||||||
|  |  | ||||||
|  | /// Create a copy of CustomAppSecret | ||||||
|  | /// with the given fields replaced by the non-null parameter values. | ||||||
|  | @pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? secret = freezed,Object? createdAt = null,Object? description = freezed,Object? expiresIn = freezed,Object? isOidc = freezed,}) { | ||||||
|  |   return _then(_self.copyWith( | ||||||
|  | id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable | ||||||
|  | as String,secret: freezed == secret ? _self.secret : secret // ignore: cast_nullable_to_non_nullable | ||||||
|  | as String?,createdAt: null == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable | ||||||
|  | as DateTime,description: freezed == description ? _self.description : description // ignore: cast_nullable_to_non_nullable | ||||||
|  | as String?,expiresIn: freezed == expiresIn ? _self.expiresIn : expiresIn // ignore: cast_nullable_to_non_nullable | ||||||
|  | as int?,isOidc: freezed == isOidc ? _self.isOidc : isOidc // ignore: cast_nullable_to_non_nullable | ||||||
|  | as bool?, | ||||||
|  |   )); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | } | ||||||
|  |  | ||||||
|  |  | ||||||
|  | /// Adds pattern-matching-related methods to [CustomAppSecret]. | ||||||
|  | extension CustomAppSecretPatterns on CustomAppSecret { | ||||||
|  | /// A variant of `map` that fallback to returning `orElse`. | ||||||
|  | /// | ||||||
|  | /// It is equivalent to doing: | ||||||
|  | /// ```dart | ||||||
|  | /// switch (sealedClass) { | ||||||
|  | ///   case final Subclass value: | ||||||
|  | ///     return ...; | ||||||
|  | ///   case _: | ||||||
|  | ///     return orElse(); | ||||||
|  | /// } | ||||||
|  | /// ``` | ||||||
|  |  | ||||||
|  | @optionalTypeArgs TResult maybeMap<TResult extends Object?>(TResult Function( _CustomAppSecret value)?  $default,{required TResult orElse(),}){ | ||||||
|  | final _that = this; | ||||||
|  | switch (_that) { | ||||||
|  | case _CustomAppSecret() when $default != null: | ||||||
|  | return $default(_that);case _: | ||||||
|  |   return orElse(); | ||||||
|  |  | ||||||
|  | } | ||||||
|  | } | ||||||
|  | /// A `switch`-like method, using callbacks. | ||||||
|  | /// | ||||||
|  | /// Callbacks receives the raw object, upcasted. | ||||||
|  | /// It is equivalent to doing: | ||||||
|  | /// ```dart | ||||||
|  | /// switch (sealedClass) { | ||||||
|  | ///   case final Subclass value: | ||||||
|  | ///     return ...; | ||||||
|  | ///   case final Subclass2 value: | ||||||
|  | ///     return ...; | ||||||
|  | /// } | ||||||
|  | /// ``` | ||||||
|  |  | ||||||
|  | @optionalTypeArgs TResult map<TResult extends Object?>(TResult Function( _CustomAppSecret value)  $default,){ | ||||||
|  | final _that = this; | ||||||
|  | switch (_that) { | ||||||
|  | case _CustomAppSecret(): | ||||||
|  | return $default(_that);} | ||||||
|  | } | ||||||
|  | /// A variant of `map` that fallback to returning `null`. | ||||||
|  | /// | ||||||
|  | /// It is equivalent to doing: | ||||||
|  | /// ```dart | ||||||
|  | /// switch (sealedClass) { | ||||||
|  | ///   case final Subclass value: | ||||||
|  | ///     return ...; | ||||||
|  | ///   case _: | ||||||
|  | ///     return null; | ||||||
|  | /// } | ||||||
|  | /// ``` | ||||||
|  |  | ||||||
|  | @optionalTypeArgs TResult? mapOrNull<TResult extends Object?>(TResult? Function( _CustomAppSecret value)?  $default,){ | ||||||
|  | final _that = this; | ||||||
|  | switch (_that) { | ||||||
|  | case _CustomAppSecret() when $default != null: | ||||||
|  | return $default(_that);case _: | ||||||
|  |   return null; | ||||||
|  |  | ||||||
|  | } | ||||||
|  | } | ||||||
|  | /// A variant of `when` that fallback to an `orElse` callback. | ||||||
|  | /// | ||||||
|  | /// It is equivalent to doing: | ||||||
|  | /// ```dart | ||||||
|  | /// switch (sealedClass) { | ||||||
|  | ///   case Subclass(:final field): | ||||||
|  | ///     return ...; | ||||||
|  | ///   case _: | ||||||
|  | ///     return orElse(); | ||||||
|  | /// } | ||||||
|  | /// ``` | ||||||
|  |  | ||||||
|  | @optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String id,  String? secret,  DateTime createdAt,  String? description,  int? expiresIn,  bool? isOidc)?  $default,{required TResult orElse(),}) {final _that = this; | ||||||
|  | switch (_that) { | ||||||
|  | case _CustomAppSecret() when $default != null: | ||||||
|  | return $default(_that.id,_that.secret,_that.createdAt,_that.description,_that.expiresIn,_that.isOidc);case _: | ||||||
|  |   return orElse(); | ||||||
|  |  | ||||||
|  | } | ||||||
|  | } | ||||||
|  | /// A `switch`-like method, using callbacks. | ||||||
|  | /// | ||||||
|  | /// As opposed to `map`, this offers destructuring. | ||||||
|  | /// It is equivalent to doing: | ||||||
|  | /// ```dart | ||||||
|  | /// switch (sealedClass) { | ||||||
|  | ///   case Subclass(:final field): | ||||||
|  | ///     return ...; | ||||||
|  | ///   case Subclass2(:final field2): | ||||||
|  | ///     return ...; | ||||||
|  | /// } | ||||||
|  | /// ``` | ||||||
|  |  | ||||||
|  | @optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String id,  String? secret,  DateTime createdAt,  String? description,  int? expiresIn,  bool? isOidc)  $default,) {final _that = this; | ||||||
|  | switch (_that) { | ||||||
|  | case _CustomAppSecret(): | ||||||
|  | return $default(_that.id,_that.secret,_that.createdAt,_that.description,_that.expiresIn,_that.isOidc);} | ||||||
|  | } | ||||||
|  | /// A variant of `when` that fallback to returning `null` | ||||||
|  | /// | ||||||
|  | /// It is equivalent to doing: | ||||||
|  | /// ```dart | ||||||
|  | /// switch (sealedClass) { | ||||||
|  | ///   case Subclass(:final field): | ||||||
|  | ///     return ...; | ||||||
|  | ///   case _: | ||||||
|  | ///     return null; | ||||||
|  | /// } | ||||||
|  | /// ``` | ||||||
|  |  | ||||||
|  | @optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String id,  String? secret,  DateTime createdAt,  String? description,  int? expiresIn,  bool? isOidc)?  $default,) {final _that = this; | ||||||
|  | switch (_that) { | ||||||
|  | case _CustomAppSecret() when $default != null: | ||||||
|  | return $default(_that.id,_that.secret,_that.createdAt,_that.description,_that.expiresIn,_that.isOidc);case _: | ||||||
|  |   return null; | ||||||
|  |  | ||||||
|  | } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /// @nodoc | ||||||
|  | @JsonSerializable() | ||||||
|  |  | ||||||
|  | class _CustomAppSecret implements CustomAppSecret { | ||||||
|  |   const _CustomAppSecret({required this.id, required this.secret, required this.createdAt, this.description, this.expiresIn, this.isOidc}); | ||||||
|  |   factory _CustomAppSecret.fromJson(Map<String, dynamic> json) => _$CustomAppSecretFromJson(json); | ||||||
|  |  | ||||||
|  | @override final  String id; | ||||||
|  | @override final  String? secret; | ||||||
|  | @override final  DateTime createdAt; | ||||||
|  | @override final  String? description; | ||||||
|  | @override final  int? expiresIn; | ||||||
|  | @override final  bool? isOidc; | ||||||
|  |  | ||||||
|  | /// Create a copy of CustomAppSecret | ||||||
|  | /// with the given fields replaced by the non-null parameter values. | ||||||
|  | @override @JsonKey(includeFromJson: false, includeToJson: false) | ||||||
|  | @pragma('vm:prefer-inline') | ||||||
|  | _$CustomAppSecretCopyWith<_CustomAppSecret> get copyWith => __$CustomAppSecretCopyWithImpl<_CustomAppSecret>(this, _$identity); | ||||||
|  |  | ||||||
|  | @override | ||||||
|  | Map<String, dynamic> toJson() { | ||||||
|  |   return _$CustomAppSecretToJson(this, ); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @override | ||||||
|  | bool operator ==(Object other) { | ||||||
|  |   return identical(this, other) || (other.runtimeType == runtimeType&&other is _CustomAppSecret&&(identical(other.id, id) || other.id == id)&&(identical(other.secret, secret) || other.secret == secret)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.description, description) || other.description == description)&&(identical(other.expiresIn, expiresIn) || other.expiresIn == expiresIn)&&(identical(other.isOidc, isOidc) || other.isOidc == isOidc)); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @JsonKey(includeFromJson: false, includeToJson: false) | ||||||
|  | @override | ||||||
|  | int get hashCode => Object.hash(runtimeType,id,secret,createdAt,description,expiresIn,isOidc); | ||||||
|  |  | ||||||
|  | @override | ||||||
|  | String toString() { | ||||||
|  |   return 'CustomAppSecret(id: $id, secret: $secret, createdAt: $createdAt, description: $description, expiresIn: $expiresIn, isOidc: $isOidc)'; | ||||||
|  | } | ||||||
|  |  | ||||||
|  |  | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /// @nodoc | ||||||
|  | abstract mixin class _$CustomAppSecretCopyWith<$Res> implements $CustomAppSecretCopyWith<$Res> { | ||||||
|  |   factory _$CustomAppSecretCopyWith(_CustomAppSecret value, $Res Function(_CustomAppSecret) _then) = __$CustomAppSecretCopyWithImpl; | ||||||
|  | @override @useResult | ||||||
|  | $Res call({ | ||||||
|  |  String id, String? secret, DateTime createdAt, String? description, int? expiresIn, bool? isOidc | ||||||
|  | }); | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  | } | ||||||
|  | /// @nodoc | ||||||
|  | class __$CustomAppSecretCopyWithImpl<$Res> | ||||||
|  |     implements _$CustomAppSecretCopyWith<$Res> { | ||||||
|  |   __$CustomAppSecretCopyWithImpl(this._self, this._then); | ||||||
|  |  | ||||||
|  |   final _CustomAppSecret _self; | ||||||
|  |   final $Res Function(_CustomAppSecret) _then; | ||||||
|  |  | ||||||
|  | /// Create a copy of CustomAppSecret | ||||||
|  | /// with the given fields replaced by the non-null parameter values. | ||||||
|  | @override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? secret = freezed,Object? createdAt = null,Object? description = freezed,Object? expiresIn = freezed,Object? isOidc = freezed,}) { | ||||||
|  |   return _then(_CustomAppSecret( | ||||||
|  | id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable | ||||||
|  | as String,secret: freezed == secret ? _self.secret : secret // ignore: cast_nullable_to_non_nullable | ||||||
|  | as String?,createdAt: null == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable | ||||||
|  | as DateTime,description: freezed == description ? _self.description : description // ignore: cast_nullable_to_non_nullable | ||||||
|  | as String?,expiresIn: freezed == expiresIn ? _self.expiresIn : expiresIn // ignore: cast_nullable_to_non_nullable | ||||||
|  | as int?,isOidc: freezed == isOidc ? _self.isOidc : isOidc // ignore: cast_nullable_to_non_nullable | ||||||
|  | as bool?, | ||||||
|  |   )); | ||||||
|  | } | ||||||
|  |  | ||||||
|  |  | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // dart format on | ||||||
							
								
								
									
										27
									
								
								lib/models/custom_app_secret.g.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								lib/models/custom_app_secret.g.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,27 @@ | |||||||
|  | // GENERATED CODE - DO NOT MODIFY BY HAND | ||||||
|  |  | ||||||
|  | part of 'custom_app_secret.dart'; | ||||||
|  |  | ||||||
|  | // ************************************************************************** | ||||||
|  | // JsonSerializableGenerator | ||||||
|  | // ************************************************************************** | ||||||
|  |  | ||||||
|  | _CustomAppSecret _$CustomAppSecretFromJson(Map<String, dynamic> json) => | ||||||
|  |     _CustomAppSecret( | ||||||
|  |       id: json['id'] as String, | ||||||
|  |       secret: json['secret'] as String?, | ||||||
|  |       createdAt: DateTime.parse(json['created_at'] as String), | ||||||
|  |       description: json['description'] as String?, | ||||||
|  |       expiresIn: (json['expires_in'] as num?)?.toInt(), | ||||||
|  |       isOidc: json['is_oidc'] as bool?, | ||||||
|  |     ); | ||||||
|  |  | ||||||
|  | Map<String, dynamic> _$CustomAppSecretToJson(_CustomAppSecret instance) => | ||||||
|  |     <String, dynamic>{ | ||||||
|  |       'id': instance.id, | ||||||
|  |       'secret': instance.secret, | ||||||
|  |       'created_at': instance.createdAt.toIso8601String(), | ||||||
|  |       'description': instance.description, | ||||||
|  |       'expires_in': instance.expiresIn, | ||||||
|  |       'is_oidc': instance.isOidc, | ||||||
|  |     }; | ||||||
							
								
								
									
										23
									
								
								lib/models/dev_project.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								lib/models/dev_project.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,23 @@ | |||||||
|  |  | ||||||
|  | class DevProject { | ||||||
|  |   final String id; | ||||||
|  |   final String slug; | ||||||
|  |   final String name; | ||||||
|  |   final String? description; | ||||||
|  |  | ||||||
|  |   DevProject({ | ||||||
|  |     required this.id, | ||||||
|  |     required this.slug, | ||||||
|  |     required this.name, | ||||||
|  |     this.description, | ||||||
|  |   }); | ||||||
|  |  | ||||||
|  |   factory DevProject.fromJson(Map<String, dynamic> json) { | ||||||
|  |     return DevProject( | ||||||
|  |       id: json['id'], | ||||||
|  |       slug: json['slug'], | ||||||
|  |       name: json['name'], | ||||||
|  |       description: json['description'], | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  | } | ||||||
| @@ -15,6 +15,7 @@ sealed class SnPostCategory with _$SnPostCategory { | |||||||
|     required String slug, |     required String slug, | ||||||
|     String? name, |     String? name, | ||||||
|     @Default([]) List<SnPost> posts, |     @Default([]) List<SnPost> posts, | ||||||
|  |     @Default(0) int usage, | ||||||
|   }) = _SnPostCategory; |   }) = _SnPostCategory; | ||||||
|  |  | ||||||
|   factory SnPostCategory.fromJson(Map<String, dynamic> json) => |   factory SnPostCategory.fromJson(Map<String, dynamic> json) => | ||||||
|   | |||||||
| @@ -15,7 +15,7 @@ T _$identity<T>(T value) => value; | |||||||
| /// @nodoc | /// @nodoc | ||||||
| mixin _$SnPostCategory { | mixin _$SnPostCategory { | ||||||
|  |  | ||||||
|  String get id; String get slug; String? get name; List<SnPost> get posts; |  String get id; String get slug; String? get name; List<SnPost> get posts; int get usage; | ||||||
| /// Create a copy of SnPostCategory | /// Create a copy of SnPostCategory | ||||||
| /// 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) | ||||||
| @@ -28,16 +28,16 @@ $SnPostCategoryCopyWith<SnPostCategory> get copyWith => _$SnPostCategoryCopyWith | |||||||
|  |  | ||||||
| @override | @override | ||||||
| bool operator ==(Object other) { | bool operator ==(Object other) { | ||||||
|   return identical(this, other) || (other.runtimeType == runtimeType&&other is SnPostCategory&&(identical(other.id, id) || other.id == id)&&(identical(other.slug, slug) || other.slug == slug)&&(identical(other.name, name) || other.name == name)&&const DeepCollectionEquality().equals(other.posts, posts)); |   return identical(this, other) || (other.runtimeType == runtimeType&&other is SnPostCategory&&(identical(other.id, id) || other.id == id)&&(identical(other.slug, slug) || other.slug == slug)&&(identical(other.name, name) || other.name == name)&&const DeepCollectionEquality().equals(other.posts, posts)&&(identical(other.usage, usage) || other.usage == usage)); | ||||||
| } | } | ||||||
|  |  | ||||||
| @JsonKey(includeFromJson: false, includeToJson: false) | @JsonKey(includeFromJson: false, includeToJson: false) | ||||||
| @override | @override | ||||||
| int get hashCode => Object.hash(runtimeType,id,slug,name,const DeepCollectionEquality().hash(posts)); | int get hashCode => Object.hash(runtimeType,id,slug,name,const DeepCollectionEquality().hash(posts),usage); | ||||||
|  |  | ||||||
| @override | @override | ||||||
| String toString() { | String toString() { | ||||||
|   return 'SnPostCategory(id: $id, slug: $slug, name: $name, posts: $posts)'; |   return 'SnPostCategory(id: $id, slug: $slug, name: $name, posts: $posts, usage: $usage)'; | ||||||
| } | } | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -48,7 +48,7 @@ abstract mixin class $SnPostCategoryCopyWith<$Res>  { | |||||||
|   factory $SnPostCategoryCopyWith(SnPostCategory value, $Res Function(SnPostCategory) _then) = _$SnPostCategoryCopyWithImpl; |   factory $SnPostCategoryCopyWith(SnPostCategory value, $Res Function(SnPostCategory) _then) = _$SnPostCategoryCopyWithImpl; | ||||||
| @useResult | @useResult | ||||||
| $Res call({ | $Res call({ | ||||||
|  String id, String slug, String? name, List<SnPost> posts |  String id, String slug, String? name, List<SnPost> posts, int usage | ||||||
| }); | }); | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -65,13 +65,14 @@ class _$SnPostCategoryCopyWithImpl<$Res> | |||||||
|  |  | ||||||
| /// Create a copy of SnPostCategory | /// Create a copy of SnPostCategory | ||||||
| /// 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? slug = null,Object? name = freezed,Object? posts = null,}) { | @pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? slug = null,Object? name = freezed,Object? posts = null,Object? usage = 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,slug: null == slug ? _self.slug : slug // ignore: cast_nullable_to_non_nullable | as String,slug: null == slug ? _self.slug : slug // ignore: cast_nullable_to_non_nullable | ||||||
| as String,name: freezed == name ? _self.name : name // ignore: cast_nullable_to_non_nullable | as String,name: freezed == name ? _self.name : name // ignore: cast_nullable_to_non_nullable | ||||||
| as String?,posts: null == posts ? _self.posts : posts // ignore: cast_nullable_to_non_nullable | as String?,posts: null == posts ? _self.posts : posts // ignore: cast_nullable_to_non_nullable | ||||||
| as List<SnPost>, | as List<SnPost>,usage: null == usage ? _self.usage : usage // ignore: cast_nullable_to_non_nullable | ||||||
|  | as int, | ||||||
|   )); |   )); | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -153,10 +154,10 @@ return $default(_that);case _: | |||||||
| /// } | /// } | ||||||
| /// ``` | /// ``` | ||||||
|  |  | ||||||
| @optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String id,  String slug,  String? name,  List<SnPost> posts)?  $default,{required TResult orElse(),}) {final _that = this; | @optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String id,  String slug,  String? name,  List<SnPost> posts,  int usage)?  $default,{required TResult orElse(),}) {final _that = this; | ||||||
| switch (_that) { | switch (_that) { | ||||||
| case _SnPostCategory() when $default != null: | case _SnPostCategory() when $default != null: | ||||||
| return $default(_that.id,_that.slug,_that.name,_that.posts);case _: | return $default(_that.id,_that.slug,_that.name,_that.posts,_that.usage);case _: | ||||||
|   return orElse(); |   return orElse(); | ||||||
|  |  | ||||||
| } | } | ||||||
| @@ -174,10 +175,10 @@ return $default(_that.id,_that.slug,_that.name,_that.posts);case _: | |||||||
| /// } | /// } | ||||||
| /// ``` | /// ``` | ||||||
|  |  | ||||||
| @optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String id,  String slug,  String? name,  List<SnPost> posts)  $default,) {final _that = this; | @optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String id,  String slug,  String? name,  List<SnPost> posts,  int usage)  $default,) {final _that = this; | ||||||
| switch (_that) { | switch (_that) { | ||||||
| case _SnPostCategory(): | case _SnPostCategory(): | ||||||
| return $default(_that.id,_that.slug,_that.name,_that.posts);} | return $default(_that.id,_that.slug,_that.name,_that.posts,_that.usage);} | ||||||
| } | } | ||||||
| /// A variant of `when` that fallback to returning `null` | /// A variant of `when` that fallback to returning `null` | ||||||
| /// | /// | ||||||
| @@ -191,10 +192,10 @@ return $default(_that.id,_that.slug,_that.name,_that.posts);} | |||||||
| /// } | /// } | ||||||
| /// ``` | /// ``` | ||||||
|  |  | ||||||
| @optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String id,  String slug,  String? name,  List<SnPost> posts)?  $default,) {final _that = this; | @optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String id,  String slug,  String? name,  List<SnPost> posts,  int usage)?  $default,) {final _that = this; | ||||||
| switch (_that) { | switch (_that) { | ||||||
| case _SnPostCategory() when $default != null: | case _SnPostCategory() when $default != null: | ||||||
| return $default(_that.id,_that.slug,_that.name,_that.posts);case _: | return $default(_that.id,_that.slug,_that.name,_that.posts,_that.usage);case _: | ||||||
|   return null; |   return null; | ||||||
|  |  | ||||||
| } | } | ||||||
| @@ -206,7 +207,7 @@ return $default(_that.id,_that.slug,_that.name,_that.posts);case _: | |||||||
| @JsonSerializable() | @JsonSerializable() | ||||||
|  |  | ||||||
| class _SnPostCategory extends SnPostCategory { | class _SnPostCategory extends SnPostCategory { | ||||||
|   const _SnPostCategory({required this.id, required this.slug, this.name, final  List<SnPost> posts = const []}): _posts = posts,super._(); |   const _SnPostCategory({required this.id, required this.slug, this.name, final  List<SnPost> posts = const [], this.usage = 0}): _posts = posts,super._(); | ||||||
|   factory _SnPostCategory.fromJson(Map<String, dynamic> json) => _$SnPostCategoryFromJson(json); |   factory _SnPostCategory.fromJson(Map<String, dynamic> json) => _$SnPostCategoryFromJson(json); | ||||||
|  |  | ||||||
| @override final  String id; | @override final  String id; | ||||||
| @@ -219,6 +220,7 @@ class _SnPostCategory extends SnPostCategory { | |||||||
|   return EqualUnmodifiableListView(_posts); |   return EqualUnmodifiableListView(_posts); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @override@JsonKey() final  int usage; | ||||||
|  |  | ||||||
| /// Create a copy of SnPostCategory | /// Create a copy of SnPostCategory | ||||||
| /// with the given fields replaced by the non-null parameter values. | /// with the given fields replaced by the non-null parameter values. | ||||||
| @@ -233,16 +235,16 @@ Map<String, dynamic> toJson() { | |||||||
|  |  | ||||||
| @override | @override | ||||||
| bool operator ==(Object other) { | bool operator ==(Object other) { | ||||||
|   return identical(this, other) || (other.runtimeType == runtimeType&&other is _SnPostCategory&&(identical(other.id, id) || other.id == id)&&(identical(other.slug, slug) || other.slug == slug)&&(identical(other.name, name) || other.name == name)&&const DeepCollectionEquality().equals(other._posts, _posts)); |   return identical(this, other) || (other.runtimeType == runtimeType&&other is _SnPostCategory&&(identical(other.id, id) || other.id == id)&&(identical(other.slug, slug) || other.slug == slug)&&(identical(other.name, name) || other.name == name)&&const DeepCollectionEquality().equals(other._posts, _posts)&&(identical(other.usage, usage) || other.usage == usage)); | ||||||
| } | } | ||||||
|  |  | ||||||
| @JsonKey(includeFromJson: false, includeToJson: false) | @JsonKey(includeFromJson: false, includeToJson: false) | ||||||
| @override | @override | ||||||
| int get hashCode => Object.hash(runtimeType,id,slug,name,const DeepCollectionEquality().hash(_posts)); | int get hashCode => Object.hash(runtimeType,id,slug,name,const DeepCollectionEquality().hash(_posts),usage); | ||||||
|  |  | ||||||
| @override | @override | ||||||
| String toString() { | String toString() { | ||||||
|   return 'SnPostCategory(id: $id, slug: $slug, name: $name, posts: $posts)'; |   return 'SnPostCategory(id: $id, slug: $slug, name: $name, posts: $posts, usage: $usage)'; | ||||||
| } | } | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -253,7 +255,7 @@ abstract mixin class _$SnPostCategoryCopyWith<$Res> implements $SnPostCategoryCo | |||||||
|   factory _$SnPostCategoryCopyWith(_SnPostCategory value, $Res Function(_SnPostCategory) _then) = __$SnPostCategoryCopyWithImpl; |   factory _$SnPostCategoryCopyWith(_SnPostCategory value, $Res Function(_SnPostCategory) _then) = __$SnPostCategoryCopyWithImpl; | ||||||
| @override @useResult | @override @useResult | ||||||
| $Res call({ | $Res call({ | ||||||
|  String id, String slug, String? name, List<SnPost> posts |  String id, String slug, String? name, List<SnPost> posts, int usage | ||||||
| }); | }); | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -270,13 +272,14 @@ class __$SnPostCategoryCopyWithImpl<$Res> | |||||||
|  |  | ||||||
| /// Create a copy of SnPostCategory | /// Create a copy of SnPostCategory | ||||||
| /// 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? slug = null,Object? name = freezed,Object? posts = null,}) { | @override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? slug = null,Object? name = freezed,Object? posts = null,Object? usage = null,}) { | ||||||
|   return _then(_SnPostCategory( |   return _then(_SnPostCategory( | ||||||
| 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,slug: null == slug ? _self.slug : slug // ignore: cast_nullable_to_non_nullable | as String,slug: null == slug ? _self.slug : slug // ignore: cast_nullable_to_non_nullable | ||||||
| as String,name: freezed == name ? _self.name : name // ignore: cast_nullable_to_non_nullable | as String,name: freezed == name ? _self.name : name // ignore: cast_nullable_to_non_nullable | ||||||
| as String?,posts: null == posts ? _self._posts : posts // ignore: cast_nullable_to_non_nullable | as String?,posts: null == posts ? _self._posts : posts // ignore: cast_nullable_to_non_nullable | ||||||
| as List<SnPost>, | as List<SnPost>,usage: null == usage ? _self.usage : usage // ignore: cast_nullable_to_non_nullable | ||||||
|  | as int, | ||||||
|   )); |   )); | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -16,6 +16,7 @@ _SnPostCategory _$SnPostCategoryFromJson(Map<String, dynamic> json) => | |||||||
|               ?.map((e) => SnPost.fromJson(e as Map<String, dynamic>)) |               ?.map((e) => SnPost.fromJson(e as Map<String, dynamic>)) | ||||||
|               .toList() ?? |               .toList() ?? | ||||||
|           const [], |           const [], | ||||||
|  |       usage: (json['usage'] as num?)?.toInt() ?? 0, | ||||||
|     ); |     ); | ||||||
|  |  | ||||||
| Map<String, dynamic> _$SnPostCategoryToJson(_SnPostCategory instance) => | Map<String, dynamic> _$SnPostCategoryToJson(_SnPostCategory instance) => | ||||||
| @@ -24,4 +25,5 @@ Map<String, dynamic> _$SnPostCategoryToJson(_SnPostCategory instance) => | |||||||
|       'slug': instance.slug, |       'slug': instance.slug, | ||||||
|       'name': instance.name, |       'name': instance.name, | ||||||
|       'posts': instance.posts.map((e) => e.toJson()).toList(), |       'posts': instance.posts.map((e) => e.toJson()).toList(), | ||||||
|  |       'usage': instance.usage, | ||||||
|     }; |     }; | ||||||
|   | |||||||
| @@ -11,6 +11,7 @@ sealed class SnPostTag with _$SnPostTag { | |||||||
|     required String slug, |     required String slug, | ||||||
|     String? name, |     String? name, | ||||||
|     @Default([]) List<SnPost> posts, |     @Default([]) List<SnPost> posts, | ||||||
|  |     @Default(0) int usage, | ||||||
|   }) = _SnPostTag; |   }) = _SnPostTag; | ||||||
|  |  | ||||||
|   factory SnPostTag.fromJson(Map<String, dynamic> json) => |   factory SnPostTag.fromJson(Map<String, dynamic> json) => | ||||||
|   | |||||||
| @@ -15,7 +15,7 @@ T _$identity<T>(T value) => value; | |||||||
| /// @nodoc | /// @nodoc | ||||||
| mixin _$SnPostTag { | mixin _$SnPostTag { | ||||||
|  |  | ||||||
|  String get id; String get slug; String? get name; List<SnPost> get posts; |  String get id; String get slug; String? get name; List<SnPost> get posts; int get usage; | ||||||
| /// Create a copy of SnPostTag | /// Create a copy of SnPostTag | ||||||
| /// 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) | ||||||
| @@ -28,16 +28,16 @@ $SnPostTagCopyWith<SnPostTag> get copyWith => _$SnPostTagCopyWithImpl<SnPostTag> | |||||||
|  |  | ||||||
| @override | @override | ||||||
| bool operator ==(Object other) { | bool operator ==(Object other) { | ||||||
|   return identical(this, other) || (other.runtimeType == runtimeType&&other is SnPostTag&&(identical(other.id, id) || other.id == id)&&(identical(other.slug, slug) || other.slug == slug)&&(identical(other.name, name) || other.name == name)&&const DeepCollectionEquality().equals(other.posts, posts)); |   return identical(this, other) || (other.runtimeType == runtimeType&&other is SnPostTag&&(identical(other.id, id) || other.id == id)&&(identical(other.slug, slug) || other.slug == slug)&&(identical(other.name, name) || other.name == name)&&const DeepCollectionEquality().equals(other.posts, posts)&&(identical(other.usage, usage) || other.usage == usage)); | ||||||
| } | } | ||||||
|  |  | ||||||
| @JsonKey(includeFromJson: false, includeToJson: false) | @JsonKey(includeFromJson: false, includeToJson: false) | ||||||
| @override | @override | ||||||
| int get hashCode => Object.hash(runtimeType,id,slug,name,const DeepCollectionEquality().hash(posts)); | int get hashCode => Object.hash(runtimeType,id,slug,name,const DeepCollectionEquality().hash(posts),usage); | ||||||
|  |  | ||||||
| @override | @override | ||||||
| String toString() { | String toString() { | ||||||
|   return 'SnPostTag(id: $id, slug: $slug, name: $name, posts: $posts)'; |   return 'SnPostTag(id: $id, slug: $slug, name: $name, posts: $posts, usage: $usage)'; | ||||||
| } | } | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -48,7 +48,7 @@ abstract mixin class $SnPostTagCopyWith<$Res>  { | |||||||
|   factory $SnPostTagCopyWith(SnPostTag value, $Res Function(SnPostTag) _then) = _$SnPostTagCopyWithImpl; |   factory $SnPostTagCopyWith(SnPostTag value, $Res Function(SnPostTag) _then) = _$SnPostTagCopyWithImpl; | ||||||
| @useResult | @useResult | ||||||
| $Res call({ | $Res call({ | ||||||
|  String id, String slug, String? name, List<SnPost> posts |  String id, String slug, String? name, List<SnPost> posts, int usage | ||||||
| }); | }); | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -65,13 +65,14 @@ class _$SnPostTagCopyWithImpl<$Res> | |||||||
|  |  | ||||||
| /// Create a copy of SnPostTag | /// Create a copy of SnPostTag | ||||||
| /// 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? slug = null,Object? name = freezed,Object? posts = null,}) { | @pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? slug = null,Object? name = freezed,Object? posts = null,Object? usage = 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,slug: null == slug ? _self.slug : slug // ignore: cast_nullable_to_non_nullable | as String,slug: null == slug ? _self.slug : slug // ignore: cast_nullable_to_non_nullable | ||||||
| as String,name: freezed == name ? _self.name : name // ignore: cast_nullable_to_non_nullable | as String,name: freezed == name ? _self.name : name // ignore: cast_nullable_to_non_nullable | ||||||
| as String?,posts: null == posts ? _self.posts : posts // ignore: cast_nullable_to_non_nullable | as String?,posts: null == posts ? _self.posts : posts // ignore: cast_nullable_to_non_nullable | ||||||
| as List<SnPost>, | as List<SnPost>,usage: null == usage ? _self.usage : usage // ignore: cast_nullable_to_non_nullable | ||||||
|  | as int, | ||||||
|   )); |   )); | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -153,10 +154,10 @@ return $default(_that);case _: | |||||||
| /// } | /// } | ||||||
| /// ``` | /// ``` | ||||||
|  |  | ||||||
| @optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String id,  String slug,  String? name,  List<SnPost> posts)?  $default,{required TResult orElse(),}) {final _that = this; | @optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String id,  String slug,  String? name,  List<SnPost> posts,  int usage)?  $default,{required TResult orElse(),}) {final _that = this; | ||||||
| switch (_that) { | switch (_that) { | ||||||
| case _SnPostTag() when $default != null: | case _SnPostTag() when $default != null: | ||||||
| return $default(_that.id,_that.slug,_that.name,_that.posts);case _: | return $default(_that.id,_that.slug,_that.name,_that.posts,_that.usage);case _: | ||||||
|   return orElse(); |   return orElse(); | ||||||
|  |  | ||||||
| } | } | ||||||
| @@ -174,10 +175,10 @@ return $default(_that.id,_that.slug,_that.name,_that.posts);case _: | |||||||
| /// } | /// } | ||||||
| /// ``` | /// ``` | ||||||
|  |  | ||||||
| @optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String id,  String slug,  String? name,  List<SnPost> posts)  $default,) {final _that = this; | @optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String id,  String slug,  String? name,  List<SnPost> posts,  int usage)  $default,) {final _that = this; | ||||||
| switch (_that) { | switch (_that) { | ||||||
| case _SnPostTag(): | case _SnPostTag(): | ||||||
| return $default(_that.id,_that.slug,_that.name,_that.posts);} | return $default(_that.id,_that.slug,_that.name,_that.posts,_that.usage);} | ||||||
| } | } | ||||||
| /// A variant of `when` that fallback to returning `null` | /// A variant of `when` that fallback to returning `null` | ||||||
| /// | /// | ||||||
| @@ -191,10 +192,10 @@ return $default(_that.id,_that.slug,_that.name,_that.posts);} | |||||||
| /// } | /// } | ||||||
| /// ``` | /// ``` | ||||||
|  |  | ||||||
| @optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String id,  String slug,  String? name,  List<SnPost> posts)?  $default,) {final _that = this; | @optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String id,  String slug,  String? name,  List<SnPost> posts,  int usage)?  $default,) {final _that = this; | ||||||
| switch (_that) { | switch (_that) { | ||||||
| case _SnPostTag() when $default != null: | case _SnPostTag() when $default != null: | ||||||
| return $default(_that.id,_that.slug,_that.name,_that.posts);case _: | return $default(_that.id,_that.slug,_that.name,_that.posts,_that.usage);case _: | ||||||
|   return null; |   return null; | ||||||
|  |  | ||||||
| } | } | ||||||
| @@ -206,7 +207,7 @@ return $default(_that.id,_that.slug,_that.name,_that.posts);case _: | |||||||
| @JsonSerializable() | @JsonSerializable() | ||||||
|  |  | ||||||
| class _SnPostTag implements SnPostTag { | class _SnPostTag implements SnPostTag { | ||||||
|   const _SnPostTag({required this.id, required this.slug, this.name, final  List<SnPost> posts = const []}): _posts = posts; |   const _SnPostTag({required this.id, required this.slug, this.name, final  List<SnPost> posts = const [], this.usage = 0}): _posts = posts; | ||||||
|   factory _SnPostTag.fromJson(Map<String, dynamic> json) => _$SnPostTagFromJson(json); |   factory _SnPostTag.fromJson(Map<String, dynamic> json) => _$SnPostTagFromJson(json); | ||||||
|  |  | ||||||
| @override final  String id; | @override final  String id; | ||||||
| @@ -219,6 +220,7 @@ class _SnPostTag implements SnPostTag { | |||||||
|   return EqualUnmodifiableListView(_posts); |   return EqualUnmodifiableListView(_posts); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @override@JsonKey() final  int usage; | ||||||
|  |  | ||||||
| /// Create a copy of SnPostTag | /// Create a copy of SnPostTag | ||||||
| /// with the given fields replaced by the non-null parameter values. | /// with the given fields replaced by the non-null parameter values. | ||||||
| @@ -233,16 +235,16 @@ Map<String, dynamic> toJson() { | |||||||
|  |  | ||||||
| @override | @override | ||||||
| bool operator ==(Object other) { | bool operator ==(Object other) { | ||||||
|   return identical(this, other) || (other.runtimeType == runtimeType&&other is _SnPostTag&&(identical(other.id, id) || other.id == id)&&(identical(other.slug, slug) || other.slug == slug)&&(identical(other.name, name) || other.name == name)&&const DeepCollectionEquality().equals(other._posts, _posts)); |   return identical(this, other) || (other.runtimeType == runtimeType&&other is _SnPostTag&&(identical(other.id, id) || other.id == id)&&(identical(other.slug, slug) || other.slug == slug)&&(identical(other.name, name) || other.name == name)&&const DeepCollectionEquality().equals(other._posts, _posts)&&(identical(other.usage, usage) || other.usage == usage)); | ||||||
| } | } | ||||||
|  |  | ||||||
| @JsonKey(includeFromJson: false, includeToJson: false) | @JsonKey(includeFromJson: false, includeToJson: false) | ||||||
| @override | @override | ||||||
| int get hashCode => Object.hash(runtimeType,id,slug,name,const DeepCollectionEquality().hash(_posts)); | int get hashCode => Object.hash(runtimeType,id,slug,name,const DeepCollectionEquality().hash(_posts),usage); | ||||||
|  |  | ||||||
| @override | @override | ||||||
| String toString() { | String toString() { | ||||||
|   return 'SnPostTag(id: $id, slug: $slug, name: $name, posts: $posts)'; |   return 'SnPostTag(id: $id, slug: $slug, name: $name, posts: $posts, usage: $usage)'; | ||||||
| } | } | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -253,7 +255,7 @@ abstract mixin class _$SnPostTagCopyWith<$Res> implements $SnPostTagCopyWith<$Re | |||||||
|   factory _$SnPostTagCopyWith(_SnPostTag value, $Res Function(_SnPostTag) _then) = __$SnPostTagCopyWithImpl; |   factory _$SnPostTagCopyWith(_SnPostTag value, $Res Function(_SnPostTag) _then) = __$SnPostTagCopyWithImpl; | ||||||
| @override @useResult | @override @useResult | ||||||
| $Res call({ | $Res call({ | ||||||
|  String id, String slug, String? name, List<SnPost> posts |  String id, String slug, String? name, List<SnPost> posts, int usage | ||||||
| }); | }); | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -270,13 +272,14 @@ class __$SnPostTagCopyWithImpl<$Res> | |||||||
|  |  | ||||||
| /// Create a copy of SnPostTag | /// Create a copy of SnPostTag | ||||||
| /// 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? slug = null,Object? name = freezed,Object? posts = null,}) { | @override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? slug = null,Object? name = freezed,Object? posts = null,Object? usage = null,}) { | ||||||
|   return _then(_SnPostTag( |   return _then(_SnPostTag( | ||||||
| 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,slug: null == slug ? _self.slug : slug // ignore: cast_nullable_to_non_nullable | as String,slug: null == slug ? _self.slug : slug // ignore: cast_nullable_to_non_nullable | ||||||
| as String,name: freezed == name ? _self.name : name // ignore: cast_nullable_to_non_nullable | as String,name: freezed == name ? _self.name : name // ignore: cast_nullable_to_non_nullable | ||||||
| as String?,posts: null == posts ? _self._posts : posts // ignore: cast_nullable_to_non_nullable | as String?,posts: null == posts ? _self._posts : posts // ignore: cast_nullable_to_non_nullable | ||||||
| as List<SnPost>, | as List<SnPost>,usage: null == usage ? _self.usage : usage // ignore: cast_nullable_to_non_nullable | ||||||
|  | as int, | ||||||
|   )); |   )); | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -15,6 +15,7 @@ _SnPostTag _$SnPostTagFromJson(Map<String, dynamic> json) => _SnPostTag( | |||||||
|           ?.map((e) => SnPost.fromJson(e as Map<String, dynamic>)) |           ?.map((e) => SnPost.fromJson(e as Map<String, dynamic>)) | ||||||
|           .toList() ?? |           .toList() ?? | ||||||
|       const [], |       const [], | ||||||
|  |   usage: (json['usage'] as num?)?.toInt() ?? 0, | ||||||
| ); | ); | ||||||
|  |  | ||||||
| Map<String, dynamic> _$SnPostTagToJson(_SnPostTag instance) => | Map<String, dynamic> _$SnPostTagToJson(_SnPostTag instance) => | ||||||
| @@ -23,4 +24,5 @@ Map<String, dynamic> _$SnPostTagToJson(_SnPostTag instance) => | |||||||
|       'slug': instance.slug, |       'slug': instance.slug, | ||||||
|       'name': instance.name, |       'name': instance.name, | ||||||
|       'posts': instance.posts.map((e) => e.toJson()).toList(), |       'posts': instance.posts.map((e) => e.toJson()).toList(), | ||||||
|  |       'usage': instance.usage, | ||||||
|     }; |     }; | ||||||
|   | |||||||
| @@ -1,6 +1,12 @@ | |||||||
|  | import 'dart:convert'; | ||||||
| import 'dart:developer'; | import 'dart:developer'; | ||||||
|  | import 'dart:io' show Platform; | ||||||
|  |  | ||||||
|  | import 'package:dio/dio.dart'; | ||||||
|  | import 'package:easy_localization/easy_localization.dart'; | ||||||
| import 'package:firebase_analytics/firebase_analytics.dart'; | import 'package:firebase_analytics/firebase_analytics.dart'; | ||||||
|  | import 'package:flutter/foundation.dart'; | ||||||
|  | import 'package:flutter_platform_alert/flutter_platform_alert.dart'; | ||||||
| import 'package:flutter_riverpod/flutter_riverpod.dart'; | import 'package:flutter_riverpod/flutter_riverpod.dart'; | ||||||
| import 'package:hooks_riverpod/hooks_riverpod.dart'; | import 'package:hooks_riverpod/hooks_riverpod.dart'; | ||||||
| import 'package:island/models/account.dart'; | import 'package:island/models/account.dart'; | ||||||
| @@ -13,13 +19,59 @@ class UserInfoNotifier extends StateNotifier<AsyncValue<SnAccount?>> { | |||||||
|   UserInfoNotifier(this._ref) : super(const AsyncValue.data(null)); |   UserInfoNotifier(this._ref) : super(const AsyncValue.data(null)); | ||||||
|  |  | ||||||
|   Future<void> fetchUser() async { |   Future<void> fetchUser() async { | ||||||
|  |     final token = _ref.watch(tokenProvider); | ||||||
|  |     if (token == null) { | ||||||
|  |       log('[UserInfo] No token found, not going to fetch...'); | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|     try { |     try { | ||||||
|       final client = _ref.read(apiClientProvider); |       final client = _ref.read(apiClientProvider); | ||||||
|       final response = await client.get('/id/accounts/me'); |       final response = await client.get('/id/accounts/me'); | ||||||
|       final user = SnAccount.fromJson(response.data); |       final user = SnAccount.fromJson(response.data); | ||||||
|       state = AsyncValue.data(user); |       state = AsyncValue.data(user); | ||||||
|       FirebaseAnalytics.instance.setUserId(id: user.id); |  | ||||||
|  |       if (kIsWeb || !Platform.isLinux) { | ||||||
|  |         FirebaseAnalytics.instance.setUserId(id: user.id); | ||||||
|  |       } | ||||||
|     } catch (error, stackTrace) { |     } catch (error, stackTrace) { | ||||||
|  |       if (!kIsWeb) { | ||||||
|  |         if (error is DioException) { | ||||||
|  |           FlutterPlatformAlert.showCustomAlert( | ||||||
|  |             windowTitle: 'failedToLoadUserInfo'.tr(), | ||||||
|  |             text: [ | ||||||
|  |               (error.response?.statusCode == 401 | ||||||
|  |                       ? 'failedToLoadUserInfoUnauthorized' | ||||||
|  |                       : 'failedToLoadUserInfoNetwork') | ||||||
|  |                   .tr() | ||||||
|  |                   .trim(), | ||||||
|  |               '${error.response!.statusCode}\n${error.response?.headers}', | ||||||
|  |               jsonEncode(error.response?.data), | ||||||
|  |             ].join('\n\n'), | ||||||
|  |             iconStyle: IconStyle.error, | ||||||
|  |             neutralButtonTitle: 'retry'.tr(), | ||||||
|  |             negativeButtonTitle: 'okay'.tr(), | ||||||
|  |           ).then((value) { | ||||||
|  |             if (value == CustomButton.neutralButton) { | ||||||
|  |               fetchUser(); | ||||||
|  |             } | ||||||
|  |           }); | ||||||
|  |         } | ||||||
|  |         FlutterPlatformAlert.showCustomAlert( | ||||||
|  |           windowTitle: 'failedToLoadUserInfo'.tr(), | ||||||
|  |           text: | ||||||
|  |               [ | ||||||
|  |                 'failedToLoadUserInfoNetwork'.tr(), | ||||||
|  |                 error.toString(), | ||||||
|  |               ].join('\n\n').trim(), | ||||||
|  |           iconStyle: IconStyle.error, | ||||||
|  |           neutralButtonTitle: 'retry'.tr(), | ||||||
|  |           negativeButtonTitle: 'okay'.tr(), | ||||||
|  |         ).then((value) { | ||||||
|  |           if (value == CustomButton.neutralButton) { | ||||||
|  |             fetchUser(); | ||||||
|  |           } | ||||||
|  |         }); | ||||||
|  |       } | ||||||
|       log( |       log( | ||||||
|         "[UserInfo] Failed to fetch user info...", |         "[UserInfo] Failed to fetch user info...", | ||||||
|         name: 'UserInfoNotifier', |         name: 'UserInfoNotifier', | ||||||
| @@ -35,7 +87,9 @@ class UserInfoNotifier extends StateNotifier<AsyncValue<SnAccount?>> { | |||||||
|     final prefs = _ref.read(sharedPreferencesProvider); |     final prefs = _ref.read(sharedPreferencesProvider); | ||||||
|     await prefs.remove(kTokenPairStoreKey); |     await prefs.remove(kTokenPairStoreKey); | ||||||
|     _ref.invalidate(tokenProvider); |     _ref.invalidate(tokenProvider); | ||||||
|     FirebaseAnalytics.instance.setUserId(id: null); |     if (kIsWeb || !Platform.isLinux) { | ||||||
|  |       FirebaseAnalytics.instance.setUserId(id: null); | ||||||
|  |     } | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
							
								
								
									
										151
									
								
								lib/route.dart
									
									
									
									
									
								
							
							
						
						
									
										151
									
								
								lib/route.dart
									
									
									
									
									
								
							| @@ -6,11 +6,20 @@ import 'package:flutter/foundation.dart' show kIsWeb; | |||||||
| import 'package:go_router/go_router.dart'; | import 'package:go_router/go_router.dart'; | ||||||
| import 'package:hooks_riverpod/hooks_riverpod.dart'; | import 'package:hooks_riverpod/hooks_riverpod.dart'; | ||||||
| import 'package:island/screens/about.dart'; | import 'package:island/screens/about.dart'; | ||||||
| import 'package:island/screens/developers/apps.dart'; | import 'package:island/screens/account/credits.dart'; | ||||||
|  | import 'package:island/screens/developers/app_detail.dart'; | ||||||
|  | import 'package:island/screens/developers/bot_detail.dart'; | ||||||
| import 'package:island/screens/developers/edit_app.dart'; | import 'package:island/screens/developers/edit_app.dart'; | ||||||
|  | import 'package:island/screens/developers/edit_bot.dart'; | ||||||
| import 'package:island/screens/developers/new_app.dart'; | import 'package:island/screens/developers/new_app.dart'; | ||||||
| import 'package:island/screens/developers/hub.dart'; | import 'package:island/screens/developers/hub.dart'; | ||||||
|  | import 'package:island/screens/developers/new_bot.dart'; | ||||||
|  | import 'package:island/screens/developers/projects.dart'; | ||||||
|  | import 'package:island/screens/developers/edit_project.dart'; | ||||||
|  | import 'package:island/screens/developers/new_project.dart'; | ||||||
|  | import 'package:island/screens/developers/project_detail.dart'; | ||||||
| import 'package:island/screens/discovery/articles.dart'; | import 'package:island/screens/discovery/articles.dart'; | ||||||
|  | import 'package:island/screens/posts/post_categories_list.dart'; | ||||||
| import 'package:island/screens/posts/post_category_detail.dart'; | import 'package:island/screens/posts/post_category_detail.dart'; | ||||||
| import 'package:island/screens/posts/post_search.dart'; | import 'package:island/screens/posts/post_search.dart'; | ||||||
| import 'package:island/widgets/app_wrapper.dart'; | import 'package:island/widgets/app_wrapper.dart'; | ||||||
| @@ -33,8 +42,10 @@ import 'package:island/screens/creators/hub.dart'; | |||||||
| import 'package:island/screens/creators/posts/post_manage_list.dart'; | import 'package:island/screens/creators/posts/post_manage_list.dart'; | ||||||
| import 'package:island/screens/creators/stickers/stickers.dart'; | import 'package:island/screens/creators/stickers/stickers.dart'; | ||||||
| import 'package:island/screens/creators/stickers/pack_detail.dart'; | import 'package:island/screens/creators/stickers/pack_detail.dart'; | ||||||
| import 'package:island/screens/stickers/marketplace.dart'; | import 'package:island/screens/stickers/sticker_marketplace.dart'; | ||||||
| import 'package:island/screens/stickers/pack_detail.dart'; | import 'package:island/screens/stickers/pack_detail.dart'; | ||||||
|  | import 'package:island/screens/discovery/feeds/feed_marketplace.dart'; | ||||||
|  | import 'package:island/screens/discovery/feeds/feed_detail.dart'; | ||||||
| import 'package:island/screens/creators/poll/poll_list.dart'; | import 'package:island/screens/creators/poll/poll_list.dart'; | ||||||
| import 'package:island/screens/creators/publishers.dart'; | import 'package:island/screens/creators/publishers.dart'; | ||||||
| import 'package:island/screens/creators/webfeed/webfeed_list.dart'; | import 'package:island/screens/creators/webfeed/webfeed_list.dart'; | ||||||
| @@ -52,6 +63,7 @@ import 'package:island/screens/account/event_calendar.dart'; | |||||||
| import 'package:island/screens/discovery/realms.dart'; | import 'package:island/screens/discovery/realms.dart'; | ||||||
| import 'package:island/screens/reports/report_detail.dart'; | import 'package:island/screens/reports/report_detail.dart'; | ||||||
| import 'package:island/screens/reports/report_list.dart'; | import 'package:island/screens/reports/report_list.dart'; | ||||||
|  | import 'package:island/widgets/post/post_shuffle.dart'; | ||||||
|  |  | ||||||
| // Shell route keys for nested navigation | // Shell route keys for nested navigation | ||||||
| final rootNavigatorKey = GlobalKey<NavigatorState>(); | final rootNavigatorKey = GlobalKey<NavigatorState>(); | ||||||
| @@ -286,30 +298,99 @@ final routerProvider = Provider<GoRouter>((ref) { | |||||||
|                 builder: (context, state) => const DeveloperHubScreen(), |                 builder: (context, state) => const DeveloperHubScreen(), | ||||||
|               ), |               ), | ||||||
|               GoRoute( |               GoRoute( | ||||||
|                 name: 'developerApps', |                 name: 'developerProjects', | ||||||
|                 path: '/developers/:name/apps', |                 path: '/developers/:name/projects', | ||||||
|                 builder: |                 builder: | ||||||
|                     (context, state) => CustomAppsScreen( |                     (context, state) => DevProjectsScreen( | ||||||
|                       publisherName: state.pathParameters['name']!, |                       publisherName: state.pathParameters['name']!, | ||||||
|                     ), |                     ), | ||||||
|               ), |               ), | ||||||
|               GoRoute( |               GoRoute( | ||||||
|                 name: 'developerAppNew', |                 name: 'developerProjectNew', | ||||||
|                 path: '/developers/:name/apps/new', |                 path: '/developers/:name/projects/new', | ||||||
|                 builder: |                 builder: | ||||||
|                     (context, state) => NewCustomAppScreen( |                     (context, state) => NewProjectScreen( | ||||||
|                       publisherName: state.pathParameters['name']!, |                       publisherName: state.pathParameters['name']!, | ||||||
|                     ), |                     ), | ||||||
|               ), |               ), | ||||||
|               GoRoute( |               GoRoute( | ||||||
|                 name: 'developerAppEdit', |                 name: 'developerProjectEdit', | ||||||
|                 path: '/developers/:name/apps/:id', |                 path: '/developers/:name/projects/:id/edit', | ||||||
|                 builder: |                 builder: | ||||||
|                     (context, state) => EditAppScreen( |                     (context, state) => EditProjectScreen( | ||||||
|                       publisherName: state.pathParameters['name']!, |                       publisherName: state.pathParameters['name']!, | ||||||
|                       id: state.pathParameters['id']!, |                       id: state.pathParameters['id']!, | ||||||
|                     ), |                     ), | ||||||
|               ), |               ), | ||||||
|  |               GoRoute( | ||||||
|  |                 name: 'developerProjectDetail', | ||||||
|  |                 path: '/developers/:name/projects/:projectId', | ||||||
|  |                 builder: | ||||||
|  |                     (context, state) => ProjectDetailScreen( | ||||||
|  |                       publisherName: state.pathParameters['name']!, | ||||||
|  |                       projectId: state.pathParameters['projectId']!, | ||||||
|  |                     ), | ||||||
|  |                 routes: [ | ||||||
|  |                   GoRoute( | ||||||
|  |                     name: 'developerAppNew', | ||||||
|  |                     path: 'apps/new', | ||||||
|  |                     builder: | ||||||
|  |                         (context, state) => NewCustomAppScreen( | ||||||
|  |                           publisherName: state.pathParameters['name']!, | ||||||
|  |                           projectId: state.pathParameters['projectId']!, | ||||||
|  |                         ), | ||||||
|  |                   ), | ||||||
|  |                   GoRoute( | ||||||
|  |                     name: 'developerAppEdit', | ||||||
|  |                     path: 'apps/:id/edit', | ||||||
|  |                     builder: | ||||||
|  |                         (context, state) => EditAppScreen( | ||||||
|  |                           publisherName: state.pathParameters['name']!, | ||||||
|  |                           projectId: state.pathParameters['projectId']!, | ||||||
|  |                           id: state.pathParameters['id']!, | ||||||
|  |                         ), | ||||||
|  |                   ), | ||||||
|  |                   GoRoute( | ||||||
|  |                     name: 'developerAppDetail', | ||||||
|  |                     path: 'apps/:appId', | ||||||
|  |                     builder: | ||||||
|  |                         (context, state) => AppDetailScreen( | ||||||
|  |                           publisherName: state.pathParameters['name']!, | ||||||
|  |                           projectId: state.pathParameters['projectId']!, | ||||||
|  |                           appId: state.pathParameters['appId']!, | ||||||
|  |                         ), | ||||||
|  |                   ), | ||||||
|  |                   GoRoute( | ||||||
|  |                     name: 'developerBotDetail', | ||||||
|  |                     path: 'bots/:botId', | ||||||
|  |                     builder: | ||||||
|  |                         (context, state) => BotDetailScreen( | ||||||
|  |                           publisherName: state.pathParameters['name']!, | ||||||
|  |                           projectId: state.pathParameters['projectId']!, | ||||||
|  |                           botId: state.pathParameters['botId']!, | ||||||
|  |                         ), | ||||||
|  |                   ), | ||||||
|  |                   GoRoute( | ||||||
|  |                     name: 'developerBotNew', | ||||||
|  |                     path: 'bots/new', | ||||||
|  |                     builder: | ||||||
|  |                         (context, state) => NewBotScreen( | ||||||
|  |                           publisherName: state.pathParameters['name']!, | ||||||
|  |                           projectId: state.pathParameters['projectId']!, | ||||||
|  |                         ), | ||||||
|  |                   ), | ||||||
|  |                   GoRoute( | ||||||
|  |                     name: 'developerBotEdit', | ||||||
|  |                     path: 'bots/:id/edit', | ||||||
|  |                     builder: | ||||||
|  |                         (context, state) => EditBotScreen( | ||||||
|  |                           publisherName: state.pathParameters['name']!, | ||||||
|  |                           projectId: state.pathParameters['projectId']!, | ||||||
|  |                           id: state.pathParameters['id']!, | ||||||
|  |                         ), | ||||||
|  |                   ), | ||||||
|  |                 ], | ||||||
|  |               ), | ||||||
|             ], |             ], | ||||||
|           ), |           ), | ||||||
|  |  | ||||||
| @@ -376,12 +457,14 @@ final routerProvider = Provider<GoRouter>((ref) { | |||||||
|                 builder: (context, state) => const PostSearchScreen(), |                 builder: (context, state) => const PostSearchScreen(), | ||||||
|               ), |               ), | ||||||
|               GoRoute( |               GoRoute( | ||||||
|                 name: 'postDetail', |                 name: 'postShuffle', | ||||||
|                 path: '/posts/:id', |                 path: '/posts/shuffle', | ||||||
|                 builder: (context, state) { |                 builder: (context, state) => const PostShuffleScreen(), | ||||||
|                   final id = state.pathParameters['id']!; |               ), | ||||||
|                   return PostDetailScreen(id: id); |               GoRoute( | ||||||
|                 }, |                 name: 'postCategories', | ||||||
|  |                 path: '/posts/categories', | ||||||
|  |                 builder: (context, state) => const PostCategoriesListScreen(), | ||||||
|               ), |               ), | ||||||
|               GoRoute( |               GoRoute( | ||||||
|                 name: 'postCategoryDetail', |                 name: 'postCategoryDetail', | ||||||
| @@ -391,6 +474,11 @@ final routerProvider = Provider<GoRouter>((ref) { | |||||||
|                   return PostCategoryDetailScreen(slug: slug, isCategory: true); |                   return PostCategoryDetailScreen(slug: slug, isCategory: true); | ||||||
|                 }, |                 }, | ||||||
|               ), |               ), | ||||||
|  |               GoRoute( | ||||||
|  |                 name: 'postTags', | ||||||
|  |                 path: '/posts/tags', | ||||||
|  |                 builder: (context, state) => const PostTagsListScreen(), | ||||||
|  |               ), | ||||||
|               GoRoute( |               GoRoute( | ||||||
|                 name: 'postTagDetail', |                 name: 'postTagDetail', | ||||||
|                 path: '/posts/tags/:slug', |                 path: '/posts/tags/:slug', | ||||||
| @@ -402,6 +490,14 @@ final routerProvider = Provider<GoRouter>((ref) { | |||||||
|                   ); |                   ); | ||||||
|                 }, |                 }, | ||||||
|               ), |               ), | ||||||
|  |               GoRoute( | ||||||
|  |                 name: 'postDetail', | ||||||
|  |                 path: '/posts/:id', | ||||||
|  |                 builder: (context, state) { | ||||||
|  |                   final id = state.pathParameters['id']!; | ||||||
|  |                   return PostDetailScreen(id: id); | ||||||
|  |                 }, | ||||||
|  |               ), | ||||||
|               GoRoute( |               GoRoute( | ||||||
|                 name: 'publisherProfile', |                 name: 'publisherProfile', | ||||||
|                 path: '/publishers/:name', |                 path: '/publishers/:name', | ||||||
| @@ -528,6 +624,22 @@ final routerProvider = Provider<GoRouter>((ref) { | |||||||
|                       ), |                       ), | ||||||
|                     ], |                     ], | ||||||
|                   ), |                   ), | ||||||
|  |                   GoRoute( | ||||||
|  |                     name: 'webFeedMarketplace', | ||||||
|  |                     path: '/feeds', | ||||||
|  |                     builder: | ||||||
|  |                         (context, state) => const MarketplaceWebFeedsScreen(), | ||||||
|  |                     routes: [ | ||||||
|  |                       GoRoute( | ||||||
|  |                         name: 'webFeedDetail', | ||||||
|  |                         path: ':feedId', | ||||||
|  |                         builder: (context, state) { | ||||||
|  |                           final feedId = state.pathParameters['feedId']!; | ||||||
|  |                           return MarketplaceWebFeedDetailScreen(id: feedId); | ||||||
|  |                         }, | ||||||
|  |                       ), | ||||||
|  |                     ], | ||||||
|  |                   ), | ||||||
|                   GoRoute( |                   GoRoute( | ||||||
|                     name: 'notifications', |                     name: 'notifications', | ||||||
|                     path: '/account/notifications', |                     path: '/account/notifications', | ||||||
| @@ -538,6 +650,11 @@ final routerProvider = Provider<GoRouter>((ref) { | |||||||
|                     path: '/account/wallet', |                     path: '/account/wallet', | ||||||
|                     builder: (context, state) => const WalletScreen(), |                     builder: (context, state) => const WalletScreen(), | ||||||
|                   ), |                   ), | ||||||
|  |                   GoRoute( | ||||||
|  |                     name: 'socialCredits', | ||||||
|  |                     path: '/account/credits', | ||||||
|  |                     builder: (context, state) => const SocialCreditsScreen(), | ||||||
|  |                   ), | ||||||
|                   GoRoute( |                   GoRoute( | ||||||
|                     name: 'relationships', |                     name: 'relationships', | ||||||
|                     path: '/account/relationships', |                     path: '/account/relationships', | ||||||
|   | |||||||
| @@ -236,6 +236,26 @@ class AccountScreen extends HookConsumerWidget { | |||||||
|                 context.pushNamed('stickerMarketplace'); |                 context.pushNamed('stickerMarketplace'); | ||||||
|               }, |               }, | ||||||
|             ), |             ), | ||||||
|  |             ListTile( | ||||||
|  |               minTileHeight: 48, | ||||||
|  |               leading: const Icon(Symbols.rss_feed), | ||||||
|  |               trailing: const Icon(Symbols.chevron_right), | ||||||
|  |               contentPadding: EdgeInsets.symmetric(horizontal: 24), | ||||||
|  |               title: Text('webFeeds').tr(), | ||||||
|  |               onTap: () { | ||||||
|  |                 context.pushNamed('webFeedMarketplace'); | ||||||
|  |               }, | ||||||
|  |             ), | ||||||
|  |             ListTile( | ||||||
|  |               minTileHeight: 48, | ||||||
|  |               leading: const Icon(Symbols.star), | ||||||
|  |               trailing: const Icon(Symbols.chevron_right), | ||||||
|  |               contentPadding: EdgeInsets.symmetric(horizontal: 24), | ||||||
|  |               title: Text('credits').tr(), | ||||||
|  |               onTap: () { | ||||||
|  |                 context.pushNamed('socialCredits'); | ||||||
|  |               }, | ||||||
|  |             ), | ||||||
|             ListTile( |             ListTile( | ||||||
|               minTileHeight: 48, |               minTileHeight: 48, | ||||||
|               title: Text('abuseReport').tr(), |               title: Text('abuseReport').tr(), | ||||||
| @@ -389,6 +409,15 @@ class _UnauthorizedAccountScreen extends StatelessWidget { | |||||||
|                       }, |                       }, | ||||||
|                       child: Text('about').tr(), |                       child: Text('about').tr(), | ||||||
|                     ), |                     ), | ||||||
|  |                     TextButton( | ||||||
|  |                       child: Text('debugOptions').tr(), | ||||||
|  |                       onPressed: () { | ||||||
|  |                         showModalBottomSheet( | ||||||
|  |                           context: context, | ||||||
|  |                           builder: (context) => DebugSheet(), | ||||||
|  |                         ); | ||||||
|  |                       }, | ||||||
|  |                     ), | ||||||
|                     TextButton( |                     TextButton( | ||||||
|                       onPressed: () { |                       onPressed: () { | ||||||
|                         context.pushNamed('settings'); |                         context.pushNamed('settings'); | ||||||
|   | |||||||
							
								
								
									
										152
									
								
								lib/screens/account/credits.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										152
									
								
								lib/screens/account/credits.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,152 @@ | |||||||
|  | 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/models/account.dart'; | ||||||
|  | import 'package:island/pods/network.dart'; | ||||||
|  | import 'package:island/widgets/app_scaffold.dart'; | ||||||
|  | import 'package:material_symbols_icons/material_symbols_icons.dart'; | ||||||
|  | import 'package:riverpod_annotation/riverpod_annotation.dart'; | ||||||
|  | import 'package:riverpod_paging_utils/riverpod_paging_utils.dart'; | ||||||
|  | import 'package:styled_widget/styled_widget.dart'; | ||||||
|  |  | ||||||
|  | part 'credits.g.dart'; | ||||||
|  |  | ||||||
|  | @riverpod | ||||||
|  | Future<double> socialCredits(Ref ref) async { | ||||||
|  |   final client = ref.watch(apiClientProvider); | ||||||
|  |   final response = await client.get('/id/accounts/me/credits'); | ||||||
|  |   if (response.statusCode != 200) { | ||||||
|  |     throw Exception('Failed to load social credits'); | ||||||
|  |   } | ||||||
|  |   return response.data?.toDouble() ?? 0.0; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @riverpod | ||||||
|  | class SocialCreditHistoryNotifier extends _$SocialCreditHistoryNotifier | ||||||
|  |     with CursorPagingNotifierMixin<SnSocialCreditRecord> { | ||||||
|  |   static const int _pageSize = 20; | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   Future<CursorPagingData<SnSocialCreditRecord>> build() => fetch(cursor: null); | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   Future<CursorPagingData<SnSocialCreditRecord>> fetch({ | ||||||
|  |     required String? cursor, | ||||||
|  |   }) async { | ||||||
|  |     final client = ref.read(apiClientProvider); | ||||||
|  |     final offset = cursor == null ? 0 : int.parse(cursor); | ||||||
|  |  | ||||||
|  |     final queryParams = {'offset': offset, 'take': _pageSize}; | ||||||
|  |  | ||||||
|  |     final response = await client.get( | ||||||
|  |       '/id/accounts/me/credits/history', | ||||||
|  |       queryParameters: queryParams, | ||||||
|  |     ); | ||||||
|  |     final total = int.parse(response.headers.value('X-Total') ?? '0'); | ||||||
|  |     final List<dynamic> data = response.data; | ||||||
|  |     final records = | ||||||
|  |         data.map((json) => SnSocialCreditRecord.fromJson(json)).toList(); | ||||||
|  |  | ||||||
|  |     final hasMore = offset + records.length < total; | ||||||
|  |     final nextCursor = hasMore ? (offset + records.length).toString() : null; | ||||||
|  |  | ||||||
|  |     return CursorPagingData( | ||||||
|  |       items: records, | ||||||
|  |       hasMore: hasMore, | ||||||
|  |       nextCursor: nextCursor, | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | class SocialCreditsScreen extends HookConsumerWidget { | ||||||
|  |   const SocialCreditsScreen({super.key}); | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   Widget build(BuildContext context, WidgetRef ref) { | ||||||
|  |     final socialCredits = ref.watch(socialCreditsProvider); | ||||||
|  |  | ||||||
|  |     return AppScaffold( | ||||||
|  |       appBar: AppBar(title: Text('socialCredits').tr()), | ||||||
|  |       body: Column( | ||||||
|  |         children: [ | ||||||
|  |           Card( | ||||||
|  |             margin: EdgeInsets.only(left: 16, right: 16, top: 8), | ||||||
|  |             child: socialCredits | ||||||
|  |                 .when( | ||||||
|  |                   data: | ||||||
|  |                       (credits) => Stack( | ||||||
|  |                         children: [ | ||||||
|  |                           Column( | ||||||
|  |                             crossAxisAlignment: CrossAxisAlignment.start, | ||||||
|  |                             children: [ | ||||||
|  |                               Text( | ||||||
|  |                                 credits < 100 | ||||||
|  |                                     ? 'socialCreditsLevelPoor'.tr() | ||||||
|  |                                     : credits < 150 | ||||||
|  |                                     ? 'socialCreditsLevelNormal'.tr() | ||||||
|  |                                     : credits < 200 | ||||||
|  |                                     ? 'socialCreditsLevelGood'.tr() | ||||||
|  |                                     : 'socialCreditsLevelExcellent'.tr(), | ||||||
|  |                               ).tr().bold().fontSize(20), | ||||||
|  |                               Text( | ||||||
|  |                                 '${credits.toStringAsFixed(2)} pts', | ||||||
|  |                               ).fontSize(14), | ||||||
|  |                               const Gap(8), | ||||||
|  |                               LinearProgressIndicator(value: credits / 200), | ||||||
|  |                             ], | ||||||
|  |                           ), | ||||||
|  |                           Positioned( | ||||||
|  |                             right: 0, | ||||||
|  |                             top: 0, | ||||||
|  |                             child: IconButton( | ||||||
|  |                               onPressed: () {}, | ||||||
|  |                               icon: const Icon(Symbols.info), | ||||||
|  |                               tooltip: 'socialCreditsDescription'.tr(), | ||||||
|  |                             ), | ||||||
|  |                           ), | ||||||
|  |                         ], | ||||||
|  |                       ), | ||||||
|  |                   error: (_, _) => Text('Error loading credits'), | ||||||
|  |                   loading: () => const LinearProgressIndicator(), | ||||||
|  |                 ) | ||||||
|  |                 .padding(horizontal: 20, vertical: 16), | ||||||
|  |           ), | ||||||
|  |           Expanded( | ||||||
|  |             child: PagingHelperView( | ||||||
|  |               provider: socialCreditHistoryNotifierProvider, | ||||||
|  |               futureRefreshable: socialCreditHistoryNotifierProvider.future, | ||||||
|  |               notifierRefreshable: socialCreditHistoryNotifierProvider.notifier, | ||||||
|  |               contentBuilder: | ||||||
|  |                   (data, widgetCount, endItemView) => ListView.builder( | ||||||
|  |                     padding: EdgeInsets.zero, | ||||||
|  |                     itemCount: widgetCount, | ||||||
|  |                     itemBuilder: (context, index) { | ||||||
|  |                       if (index == widgetCount - 1) { | ||||||
|  |                         return endItemView; | ||||||
|  |                       } | ||||||
|  |                       final record = data.items[index]; | ||||||
|  |                       return ListTile( | ||||||
|  |                         contentPadding: EdgeInsets.symmetric(horizontal: 24), | ||||||
|  |                         title: Text(record.reason), | ||||||
|  |                         subtitle: Text( | ||||||
|  |                           DateFormat.yMMMd().format(record.createdAt), | ||||||
|  |                         ), | ||||||
|  |                         trailing: Text( | ||||||
|  |                           record.delta > 0 | ||||||
|  |                               ? '+${record.delta}' | ||||||
|  |                               : '${record.delta}', | ||||||
|  |                           style: TextStyle( | ||||||
|  |                             color: record.delta > 0 ? Colors.green : Colors.red, | ||||||
|  |                           ), | ||||||
|  |                         ), | ||||||
|  |                       ); | ||||||
|  |                     }, | ||||||
|  |                   ), | ||||||
|  |             ), | ||||||
|  |           ), | ||||||
|  |         ], | ||||||
|  |       ), | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  | } | ||||||
							
								
								
									
										49
									
								
								lib/screens/account/credits.g.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										49
									
								
								lib/screens/account/credits.g.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,49 @@ | |||||||
|  | // GENERATED CODE - DO NOT MODIFY BY HAND | ||||||
|  |  | ||||||
|  | part of 'credits.dart'; | ||||||
|  |  | ||||||
|  | // ************************************************************************** | ||||||
|  | // RiverpodGenerator | ||||||
|  | // ************************************************************************** | ||||||
|  |  | ||||||
|  | String _$socialCreditsHash() => r'2599844e892127ee4d315caced5c10e4dbaea142'; | ||||||
|  |  | ||||||
|  | /// See also [socialCredits]. | ||||||
|  | @ProviderFor(socialCredits) | ||||||
|  | final socialCreditsProvider = AutoDisposeFutureProvider<double>.internal( | ||||||
|  |   socialCredits, | ||||||
|  |   name: r'socialCreditsProvider', | ||||||
|  |   debugGetCreateSourceHash: | ||||||
|  |       const bool.fromEnvironment('dart.vm.product') | ||||||
|  |           ? null | ||||||
|  |           : _$socialCreditsHash, | ||||||
|  |   dependencies: null, | ||||||
|  |   allTransitiveDependencies: null, | ||||||
|  | ); | ||||||
|  |  | ||||||
|  | @Deprecated('Will be removed in 3.0. Use Ref instead') | ||||||
|  | // ignore: unused_element | ||||||
|  | typedef SocialCreditsRef = AutoDisposeFutureProviderRef<double>; | ||||||
|  | String _$socialCreditHistoryNotifierHash() => | ||||||
|  |     r'950db020754160f835c64cedf3fa2175e61e4d64'; | ||||||
|  |  | ||||||
|  | /// See also [SocialCreditHistoryNotifier]. | ||||||
|  | @ProviderFor(SocialCreditHistoryNotifier) | ||||||
|  | final socialCreditHistoryNotifierProvider = AutoDisposeAsyncNotifierProvider< | ||||||
|  |   SocialCreditHistoryNotifier, | ||||||
|  |   CursorPagingData<SnSocialCreditRecord> | ||||||
|  | >.internal( | ||||||
|  |   SocialCreditHistoryNotifier.new, | ||||||
|  |   name: r'socialCreditHistoryNotifierProvider', | ||||||
|  |   debugGetCreateSourceHash: | ||||||
|  |       const bool.fromEnvironment('dart.vm.product') | ||||||
|  |           ? null | ||||||
|  |           : _$socialCreditHistoryNotifierHash, | ||||||
|  |   dependencies: null, | ||||||
|  |   allTransitiveDependencies: null, | ||||||
|  | ); | ||||||
|  |  | ||||||
|  | typedef _$SocialCreditHistoryNotifier = | ||||||
|  |     AutoDisposeAsyncNotifier<CursorPagingData<SnSocialCreditRecord>>; | ||||||
|  | // 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 | ||||||
| @@ -6,6 +6,7 @@ import 'package:flutter/material.dart'; | |||||||
| import 'package:gap/gap.dart'; | import 'package:gap/gap.dart'; | ||||||
| import 'package:google_fonts/google_fonts.dart'; | import 'package:google_fonts/google_fonts.dart'; | ||||||
| import 'package:hooks_riverpod/hooks_riverpod.dart'; | import 'package:hooks_riverpod/hooks_riverpod.dart'; | ||||||
|  | import 'package:island/models/account.dart'; | ||||||
| import 'package:island/models/wallet.dart'; | import 'package:island/models/wallet.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'; | ||||||
| @@ -19,6 +20,7 @@ import 'package:island/widgets/payment/payment_overlay.dart'; | |||||||
| import 'package:easy_localization/easy_localization.dart'; | import 'package:easy_localization/easy_localization.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:styled_widget/styled_widget.dart'; | import 'package:styled_widget/styled_widget.dart'; | ||||||
|  |  | ||||||
| part 'leveling.g.dart'; | part 'leveling.g.dart'; | ||||||
| @@ -35,13 +37,49 @@ Future<SnWalletSubscription?> accountStellarSubscription(Ref ref) async { | |||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @riverpod | ||||||
|  | class LevelingHistoryNotifier extends _$LevelingHistoryNotifier | ||||||
|  |     with CursorPagingNotifierMixin<SnExperienceRecord> { | ||||||
|  |   static const int _pageSize = 20; | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   Future<CursorPagingData<SnExperienceRecord>> build() => fetch(cursor: null); | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   Future<CursorPagingData<SnExperienceRecord>> fetch({ | ||||||
|  |     required String? cursor, | ||||||
|  |   }) async { | ||||||
|  |     final client = ref.read(apiClientProvider); | ||||||
|  |     final offset = cursor == null ? 0 : int.parse(cursor); | ||||||
|  |  | ||||||
|  |     final queryParams = {'offset': offset, 'take': _pageSize}; | ||||||
|  |  | ||||||
|  |     final response = await client.get( | ||||||
|  |       '/id/accounts/me/leveling', | ||||||
|  |       queryParameters: queryParams, | ||||||
|  |     ); | ||||||
|  |     final total = int.parse(response.headers.value('X-Total') ?? '0'); | ||||||
|  |     final List<dynamic> data = response.data; | ||||||
|  |     final records = | ||||||
|  |         data.map((json) => SnExperienceRecord.fromJson(json)).toList(); | ||||||
|  |  | ||||||
|  |     final hasMore = offset + records.length < total; | ||||||
|  |     final nextCursor = hasMore ? (offset + records.length).toString() : null; | ||||||
|  |  | ||||||
|  |     return CursorPagingData( | ||||||
|  |       items: records, | ||||||
|  |       hasMore: hasMore, | ||||||
|  |       nextCursor: nextCursor, | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
| class LevelingScreen extends HookConsumerWidget { | class LevelingScreen extends HookConsumerWidget { | ||||||
|   const LevelingScreen({super.key}); |   const LevelingScreen({super.key}); | ||||||
|  |  | ||||||
|   @override |   @override | ||||||
|   Widget build(BuildContext context, WidgetRef ref) { |   Widget build(BuildContext context, WidgetRef ref) { | ||||||
|     final user = ref.watch(userInfoProvider); |     final user = ref.watch(userInfoProvider); | ||||||
|     final stellarSubscription = ref.watch(accountStellarSubscriptionProvider); |  | ||||||
|  |  | ||||||
|     if (user.value == null) { |     if (user.value == null) { | ||||||
|       return AppScaffold( |       return AppScaffold( | ||||||
| @@ -50,47 +88,150 @@ class LevelingScreen extends HookConsumerWidget { | |||||||
|       ); |       ); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     final currentLevel = user.value!.profile.level; |     return DefaultTabController( | ||||||
|     final currentExp = user.value!.profile.experience; |       length: 2, | ||||||
|     final progress = user.value!.profile.levelingProgress; |       child: AppScaffold( | ||||||
|  |         appBar: AppBar( | ||||||
|  |           title: Text('levelingProgress'.tr()), | ||||||
|  |           bottom: TabBar( | ||||||
|  |             tabs: [ | ||||||
|  |               Tab(text: 'leveling'.tr()), | ||||||
|  |               Tab(text: 'stellarProgram'.tr()), | ||||||
|  |             ], | ||||||
|  |           ), | ||||||
|  |         ), | ||||||
|  |         body: TabBarView( | ||||||
|  |           children: [ | ||||||
|  |             _buildLevelingTab(context, ref, user.value!), | ||||||
|  |             _buildStellarProgramTab(context, ref), | ||||||
|  |           ], | ||||||
|  |         ), | ||||||
|  |       ), | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  |  | ||||||
|     return AppScaffold( |   Widget _buildLevelingTab( | ||||||
|       appBar: AppBar(title: Text('levelingProgress'.tr())), |     BuildContext context, | ||||||
|       body: SingleChildScrollView( |     WidgetRef ref, | ||||||
|         padding: getTabbedPadding(context, horizontal: 20, vertical: 20), |     SnAccount user, | ||||||
|         child: Center( |   ) { | ||||||
|           child: ConstrainedBox( |     final currentLevel = user.profile.level; | ||||||
|             constraints: const BoxConstraints(maxWidth: 480), |     final currentExp = user.profile.experience; | ||||||
|             child: Column( |     final progress = user.profile.levelingProgress; | ||||||
|               crossAxisAlignment: CrossAxisAlignment.stretch, |  | ||||||
|               children: [ |  | ||||||
|                 // Current Progress Card |  | ||||||
|                 LevelingProgressCard( |  | ||||||
|                   level: currentLevel, |  | ||||||
|                   experience: currentExp, |  | ||||||
|                   progress: progress, |  | ||||||
|                 ), |  | ||||||
|                 const Gap(24), |  | ||||||
|  |  | ||||||
|                 // Level Stairs Graph |     return Center( | ||||||
|                 Text( |       child: Container( | ||||||
|                   'levelProgress'.tr(), |         padding: const EdgeInsets.symmetric(horizontal: 20), | ||||||
|                   style: Theme.of(context).textTheme.headlineSmall?.copyWith( |         constraints: const BoxConstraints(maxWidth: 480), | ||||||
|                     fontWeight: FontWeight.bold, |         child: CustomScrollView( | ||||||
|                   ), |           slivers: [ | ||||||
|                 ), |             const SliverGap(20), | ||||||
|                 const Gap(16), |  | ||||||
|  |  | ||||||
|                 // Stairs visualization with fixed height and horizontal scroll |             // Current Progress Card | ||||||
|                 _buildLevelStairs(context, currentLevel), |             SliverToBoxAdapter( | ||||||
|  |               child: LevelingProgressCard( | ||||||
|                 const Gap(24), |                 level: currentLevel, | ||||||
|  |                 experience: currentExp, | ||||||
|                 // Membership section |                 progress: progress, | ||||||
|                 _buildMembershipSection(context, ref, stellarSubscription), |               ), | ||||||
|                 const Gap(16), |  | ||||||
|               ], |  | ||||||
|             ), |             ), | ||||||
|  |             const SliverGap(24), | ||||||
|  |  | ||||||
|  |             // Level Stairs Graph | ||||||
|  |             SliverToBoxAdapter( | ||||||
|  |               child: Text( | ||||||
|  |                 'levelProgress'.tr(), | ||||||
|  |                 style: Theme.of(context).textTheme.headlineSmall?.copyWith( | ||||||
|  |                   fontWeight: FontWeight.bold, | ||||||
|  |                 ), | ||||||
|  |               ), | ||||||
|  |             ), | ||||||
|  |             const SliverGap(16), | ||||||
|  |  | ||||||
|  |             // Stairs visualization with fixed height and horizontal scroll | ||||||
|  |             SliverToBoxAdapter(child: _buildLevelStairs(context, currentLevel)), | ||||||
|  |             const SliverGap(24), | ||||||
|  |  | ||||||
|  |             // Leveling History | ||||||
|  |             SliverToBoxAdapter( | ||||||
|  |               child: Text( | ||||||
|  |                 'levelingHistory'.tr(), | ||||||
|  |                 style: Theme.of(context).textTheme.headlineSmall?.copyWith( | ||||||
|  |                   fontWeight: FontWeight.bold, | ||||||
|  |                 ), | ||||||
|  |               ), | ||||||
|  |             ), | ||||||
|  |             const SliverGap(8), | ||||||
|  |             PagingHelperSliverView( | ||||||
|  |               provider: levelingHistoryNotifierProvider, | ||||||
|  |               futureRefreshable: levelingHistoryNotifierProvider.future, | ||||||
|  |               notifierRefreshable: levelingHistoryNotifierProvider.notifier, | ||||||
|  |               contentBuilder: | ||||||
|  |                   (data, widgetCount, endItemView) => SliverList.builder( | ||||||
|  |                     itemCount: widgetCount, | ||||||
|  |                     itemBuilder: (context, index) { | ||||||
|  |                       if (index == widgetCount - 1) { | ||||||
|  |                         return endItemView; | ||||||
|  |                       } | ||||||
|  |                       final record = data.items[index]; | ||||||
|  |                       return ListTile( | ||||||
|  |                         title: Column( | ||||||
|  |                           mainAxisSize: MainAxisSize.min, | ||||||
|  |                           crossAxisAlignment: CrossAxisAlignment.stretch, | ||||||
|  |                           children: [ | ||||||
|  |                             Text(record.reason), | ||||||
|  |                             Row( | ||||||
|  |                               spacing: 4, | ||||||
|  |                               children: [ | ||||||
|  |                                 Text( | ||||||
|  |                                   record.createdAt.formatRelative(context), | ||||||
|  |                                 ).fontSize(13), | ||||||
|  |                                 Text('·').fontSize(13).bold(), | ||||||
|  |                                 Text( | ||||||
|  |                                   record.createdAt.formatSystem(), | ||||||
|  |                                 ).fontSize(13), | ||||||
|  |                               ], | ||||||
|  |                             ).opacity(0.8), | ||||||
|  |                           ], | ||||||
|  |                         ), | ||||||
|  |                         subtitle: Row( | ||||||
|  |                           spacing: 8, | ||||||
|  |                           children: [ | ||||||
|  |                             Text( | ||||||
|  |                               '${record.delta > 0 ? '+' : ''}${record.delta} EXP', | ||||||
|  |                             ), | ||||||
|  |                             if (record.bonusMultiplier != 1.0) | ||||||
|  |                               Text('x${record.bonusMultiplier}'), | ||||||
|  |                           ], | ||||||
|  |                         ), | ||||||
|  |                         minTileHeight: 56, | ||||||
|  |                         contentPadding: EdgeInsets.symmetric(horizontal: 4), | ||||||
|  |                       ); | ||||||
|  |                     }, | ||||||
|  |                   ), | ||||||
|  |             ), | ||||||
|  |  | ||||||
|  |             SliverGap(getTabbedPadding(context, vertical: 20).vertical), | ||||||
|  |           ], | ||||||
|  |         ), | ||||||
|  |       ), | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   Widget _buildStellarProgramTab(BuildContext context, WidgetRef ref) { | ||||||
|  |     final stellarSubscription = ref.watch(accountStellarSubscriptionProvider); | ||||||
|  |  | ||||||
|  |     return SingleChildScrollView( | ||||||
|  |       padding: getTabbedPadding(context, horizontal: 20, vertical: 20), | ||||||
|  |       child: Center( | ||||||
|  |         child: ConstrainedBox( | ||||||
|  |           constraints: const BoxConstraints(maxWidth: 480), | ||||||
|  |           child: Column( | ||||||
|  |             crossAxisAlignment: CrossAxisAlignment.stretch, | ||||||
|  |             children: [ | ||||||
|  |               _buildMembershipSection(context, ref, stellarSubscription), | ||||||
|  |               const Gap(16), | ||||||
|  |             ], | ||||||
|           ), |           ), | ||||||
|         ), |         ), | ||||||
|       ), |       ), | ||||||
|   | |||||||
| @@ -27,5 +27,26 @@ final accountStellarSubscriptionProvider = | |||||||
| // ignore: unused_element | // ignore: unused_element | ||||||
| typedef AccountStellarSubscriptionRef = | typedef AccountStellarSubscriptionRef = | ||||||
|     AutoDisposeFutureProviderRef<SnWalletSubscription?>; |     AutoDisposeFutureProviderRef<SnWalletSubscription?>; | ||||||
|  | String _$levelingHistoryNotifierHash() => | ||||||
|  |     r'e795f9b7911c9e50f15c095ea237cb0e87bf1e89'; | ||||||
|  |  | ||||||
|  | /// See also [LevelingHistoryNotifier]. | ||||||
|  | @ProviderFor(LevelingHistoryNotifier) | ||||||
|  | final levelingHistoryNotifierProvider = AutoDisposeAsyncNotifierProvider< | ||||||
|  |   LevelingHistoryNotifier, | ||||||
|  |   CursorPagingData<SnExperienceRecord> | ||||||
|  | >.internal( | ||||||
|  |   LevelingHistoryNotifier.new, | ||||||
|  |   name: r'levelingHistoryNotifierProvider', | ||||||
|  |   debugGetCreateSourceHash: | ||||||
|  |       const bool.fromEnvironment('dart.vm.product') | ||||||
|  |           ? null | ||||||
|  |           : _$levelingHistoryNotifierHash, | ||||||
|  |   dependencies: null, | ||||||
|  |   allTransitiveDependencies: null, | ||||||
|  | ); | ||||||
|  |  | ||||||
|  | typedef _$LevelingHistoryNotifier = | ||||||
|  |     AutoDisposeAsyncNotifier<CursorPagingData<SnExperienceRecord>>; | ||||||
| // 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 | ||||||
|   | |||||||
| @@ -7,6 +7,7 @@ import 'package:go_router/go_router.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/chat.dart'; | import 'package:island/models/chat.dart'; | ||||||
|  | import 'package:island/models/developer.dart'; | ||||||
| import 'package:island/models/relationship.dart'; | import 'package:island/models/relationship.dart'; | ||||||
| import 'package:island/models/account.dart'; | import 'package:island/models/account.dart'; | ||||||
| import 'package:island/pods/config.dart'; | import 'package:island/pods/config.dart'; | ||||||
| @@ -112,6 +113,24 @@ Future<SnRelationship?> accountRelationship(Ref ref, String uname) async { | |||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @riverpod | ||||||
|  | Future<SnDeveloper?> accountBotDeveloper(Ref ref, String uname) async { | ||||||
|  |   final account = await ref.watch(accountProvider(uname).future); | ||||||
|  |   if (account.automatedId == null) return null; | ||||||
|  |   final apiClient = ref.watch(apiClientProvider); | ||||||
|  |   try { | ||||||
|  |     final resp = await apiClient.get( | ||||||
|  |       "/develop/bots/${account.automatedId}/developer", | ||||||
|  |     ); | ||||||
|  |     return SnDeveloper.fromJson(resp.data); | ||||||
|  |   } catch (err) { | ||||||
|  |     if (err is DioException && err.response?.statusCode == 404) { | ||||||
|  |       return null; | ||||||
|  |     } | ||||||
|  |     rethrow; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
| class AccountProfileScreen extends HookConsumerWidget { | class AccountProfileScreen extends HookConsumerWidget { | ||||||
|   final String name; |   final String name; | ||||||
|   const AccountProfileScreen({super.key, required this.name}); |   const AccountProfileScreen({super.key, required this.name}); | ||||||
| @@ -128,6 +147,7 @@ class AccountProfileScreen extends HookConsumerWidget { | |||||||
|     ); |     ); | ||||||
|     final accountChat = ref.watch(accountDirectChatProvider(name)); |     final accountChat = ref.watch(accountDirectChatProvider(name)); | ||||||
|     final accountRelationship = ref.watch(accountRelationshipProvider(name)); |     final accountRelationship = ref.watch(accountRelationshipProvider(name)); | ||||||
|  |     final accountDeveloper = ref.watch(accountBotDeveloperProvider(name)); | ||||||
|  |  | ||||||
|     final appbarColor = ref.watch(accountAppbarForcegroundColorProvider(name)); |     final appbarColor = ref.watch(accountAppbarForcegroundColorProvider(name)); | ||||||
|  |  | ||||||
| @@ -292,6 +312,19 @@ class AccountProfileScreen extends HookConsumerWidget { | |||||||
|                     ), |                     ), | ||||||
|                   ], |                   ], | ||||||
|                 ), |                 ), | ||||||
|  |                 if (accountDeveloper.value != null) | ||||||
|  |                   Row( | ||||||
|  |                     spacing: 7, | ||||||
|  |                     children: [ | ||||||
|  |                       const Icon(Symbols.smart_toy, size: 18), | ||||||
|  |                       Text( | ||||||
|  |                         'botAutomatedBy'.tr( | ||||||
|  |                           args: [accountDeveloper.value!.publisher!.nick], | ||||||
|  |                         ), | ||||||
|  |                       ).fontSize(13), | ||||||
|  |                     ], | ||||||
|  |                   ).opacity(0.75), | ||||||
|  |                 const Gap(4), | ||||||
|                 AccountStatusWidget(uname: name, padding: EdgeInsets.zero), |                 AccountStatusWidget(uname: name, padding: EdgeInsets.zero), | ||||||
|               ], |               ], | ||||||
|             ), |             ), | ||||||
|   | |||||||
| @@ -639,5 +639,128 @@ class _AccountRelationshipProviderElement | |||||||
|   String get uname => (origin as AccountRelationshipProvider).uname; |   String get uname => (origin as AccountRelationshipProvider).uname; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | String _$accountBotDeveloperHash() => | ||||||
|  |     r'673534770640a8cf1484ea0af0f4d0ef283ef157'; | ||||||
|  |  | ||||||
|  | /// See also [accountBotDeveloper]. | ||||||
|  | @ProviderFor(accountBotDeveloper) | ||||||
|  | const accountBotDeveloperProvider = AccountBotDeveloperFamily(); | ||||||
|  |  | ||||||
|  | /// See also [accountBotDeveloper]. | ||||||
|  | class AccountBotDeveloperFamily extends Family<AsyncValue<SnDeveloper?>> { | ||||||
|  |   /// See also [accountBotDeveloper]. | ||||||
|  |   const AccountBotDeveloperFamily(); | ||||||
|  |  | ||||||
|  |   /// See also [accountBotDeveloper]. | ||||||
|  |   AccountBotDeveloperProvider call(String uname) { | ||||||
|  |     return AccountBotDeveloperProvider(uname); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   AccountBotDeveloperProvider getProviderOverride( | ||||||
|  |     covariant AccountBotDeveloperProvider provider, | ||||||
|  |   ) { | ||||||
|  |     return call(provider.uname); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   static const Iterable<ProviderOrFamily>? _dependencies = null; | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   Iterable<ProviderOrFamily>? get dependencies => _dependencies; | ||||||
|  |  | ||||||
|  |   static const Iterable<ProviderOrFamily>? _allTransitiveDependencies = null; | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   Iterable<ProviderOrFamily>? get allTransitiveDependencies => | ||||||
|  |       _allTransitiveDependencies; | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   String? get name => r'accountBotDeveloperProvider'; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /// See also [accountBotDeveloper]. | ||||||
|  | class AccountBotDeveloperProvider | ||||||
|  |     extends AutoDisposeFutureProvider<SnDeveloper?> { | ||||||
|  |   /// See also [accountBotDeveloper]. | ||||||
|  |   AccountBotDeveloperProvider(String uname) | ||||||
|  |     : this._internal( | ||||||
|  |         (ref) => accountBotDeveloper(ref as AccountBotDeveloperRef, uname), | ||||||
|  |         from: accountBotDeveloperProvider, | ||||||
|  |         name: r'accountBotDeveloperProvider', | ||||||
|  |         debugGetCreateSourceHash: | ||||||
|  |             const bool.fromEnvironment('dart.vm.product') | ||||||
|  |                 ? null | ||||||
|  |                 : _$accountBotDeveloperHash, | ||||||
|  |         dependencies: AccountBotDeveloperFamily._dependencies, | ||||||
|  |         allTransitiveDependencies: | ||||||
|  |             AccountBotDeveloperFamily._allTransitiveDependencies, | ||||||
|  |         uname: uname, | ||||||
|  |       ); | ||||||
|  |  | ||||||
|  |   AccountBotDeveloperProvider._internal( | ||||||
|  |     super._createNotifier, { | ||||||
|  |     required super.name, | ||||||
|  |     required super.dependencies, | ||||||
|  |     required super.allTransitiveDependencies, | ||||||
|  |     required super.debugGetCreateSourceHash, | ||||||
|  |     required super.from, | ||||||
|  |     required this.uname, | ||||||
|  |   }) : super.internal(); | ||||||
|  |  | ||||||
|  |   final String uname; | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   Override overrideWith( | ||||||
|  |     FutureOr<SnDeveloper?> Function(AccountBotDeveloperRef provider) create, | ||||||
|  |   ) { | ||||||
|  |     return ProviderOverride( | ||||||
|  |       origin: this, | ||||||
|  |       override: AccountBotDeveloperProvider._internal( | ||||||
|  |         (ref) => create(ref as AccountBotDeveloperRef), | ||||||
|  |         from: from, | ||||||
|  |         name: null, | ||||||
|  |         dependencies: null, | ||||||
|  |         allTransitiveDependencies: null, | ||||||
|  |         debugGetCreateSourceHash: null, | ||||||
|  |         uname: uname, | ||||||
|  |       ), | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   AutoDisposeFutureProviderElement<SnDeveloper?> createElement() { | ||||||
|  |     return _AccountBotDeveloperProviderElement(this); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   bool operator ==(Object other) { | ||||||
|  |     return other is AccountBotDeveloperProvider && other.uname == uname; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   int get hashCode { | ||||||
|  |     var hash = _SystemHash.combine(0, runtimeType.hashCode); | ||||||
|  |     hash = _SystemHash.combine(hash, uname.hashCode); | ||||||
|  |  | ||||||
|  |     return _SystemHash.finish(hash); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @Deprecated('Will be removed in 3.0. Use Ref instead') | ||||||
|  | // ignore: unused_element | ||||||
|  | mixin AccountBotDeveloperRef on AutoDisposeFutureProviderRef<SnDeveloper?> { | ||||||
|  |   /// The parameter `uname` of this provider. | ||||||
|  |   String get uname; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | class _AccountBotDeveloperProviderElement | ||||||
|  |     extends AutoDisposeFutureProviderElement<SnDeveloper?> | ||||||
|  |     with AccountBotDeveloperRef { | ||||||
|  |   _AccountBotDeveloperProviderElement(super.provider); | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   String get uname => (origin as AccountBotDeveloperProvider).uname; | ||||||
|  | } | ||||||
|  |  | ||||||
| // 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 | ||||||
|   | |||||||
| @@ -79,33 +79,38 @@ class ChatRoomListTile extends HookConsumerWidget { | |||||||
|                     color: Theme.of(context).colorScheme.primary, |                     color: Theme.of(context).colorScheme.primary, | ||||||
|                   ), |                   ), | ||||||
|                 ), |                 ), | ||||||
|               Row( |               if (data.lastMessage == null) | ||||||
|                 spacing: 4, |                 Text(room.description ?? 'descriptionNone'.tr(), maxLines: 1) | ||||||
|                 children: [ |               else | ||||||
|                   Badge( |                 Row( | ||||||
|                     label: Text(data.lastMessage.sender.account.nick), |                   spacing: 4, | ||||||
|                     textColor: Theme.of(context).colorScheme.onPrimary, |                   children: [ | ||||||
|                     backgroundColor: Theme.of(context).colorScheme.primary, |                     Badge( | ||||||
|                   ), |                       label: Text(data.lastMessage!.sender.account.nick), | ||||||
|                   Expanded( |                       textColor: Theme.of(context).colorScheme.onPrimary, | ||||||
|                     child: Text( |                       backgroundColor: Theme.of(context).colorScheme.primary, | ||||||
|                       (data.lastMessage.content?.isNotEmpty ?? false) |  | ||||||
|                           ? data.lastMessage.content! |  | ||||||
|                           : 'messageNone'.tr(), |  | ||||||
|                       maxLines: 1, |  | ||||||
|                       overflow: TextOverflow.ellipsis, |  | ||||||
|                       style: Theme.of(context).textTheme.bodySmall, |  | ||||||
|                     ), |                     ), | ||||||
|                   ), |                     Expanded( | ||||||
|                   Align( |                       child: Text( | ||||||
|                     alignment: Alignment.centerRight, |                         (data.lastMessage!.content?.isNotEmpty ?? false) | ||||||
|                     child: Text( |                             ? data.lastMessage!.content! | ||||||
|                       RelativeTime(context).format(data.lastMessage.createdAt), |                             : 'messageNone'.tr(), | ||||||
|                       style: Theme.of(context).textTheme.bodySmall, |                         maxLines: 1, | ||||||
|  |                         overflow: TextOverflow.ellipsis, | ||||||
|  |                         style: Theme.of(context).textTheme.bodySmall, | ||||||
|  |                       ), | ||||||
|                     ), |                     ), | ||||||
|                   ), |                     Align( | ||||||
|                 ], |                       alignment: Alignment.centerRight, | ||||||
|               ), |                       child: Text( | ||||||
|  |                         RelativeTime( | ||||||
|  |                           context, | ||||||
|  |                         ).format(data.lastMessage!.createdAt), | ||||||
|  |                         style: Theme.of(context).textTheme.bodySmall, | ||||||
|  |                       ), | ||||||
|  |                     ), | ||||||
|  |                   ], | ||||||
|  |                 ), | ||||||
|             ], |             ], | ||||||
|           ); |           ); | ||||||
|         }, |         }, | ||||||
|   | |||||||
| @@ -7,7 +7,7 @@ part of 'room_detail.dart'; | |||||||
| // ************************************************************************** | // ************************************************************************** | ||||||
|  |  | ||||||
| String _$chatMemberListNotifierHash() => | String _$chatMemberListNotifierHash() => | ||||||
|     r'c8fbf4b95df6dae24b1ba21063e9a43351832974'; |     r'3ea30150278523e9f6b23f9200ea9a9fbae9c973'; | ||||||
|  |  | ||||||
| /// Copied from Dart SDK | /// Copied from Dart SDK | ||||||
| class _SystemHash { | class _SystemHash { | ||||||
|   | |||||||
| @@ -106,11 +106,7 @@ class StickerPacksNotifier extends _$StickerPacksNotifier | |||||||
|     try { |     try { | ||||||
|       final response = await client.get( |       final response = await client.get( | ||||||
|         '/sphere/stickers', |         '/sphere/stickers', | ||||||
|         queryParameters: { |         queryParameters: {'offset': offset, 'take': _pageSize, 'pub': pubName}, | ||||||
|           'offset': offset, |  | ||||||
|           'take': _pageSize, |  | ||||||
|           'pubName': pubName, |  | ||||||
|         }, |  | ||||||
|       ); |       ); | ||||||
|  |  | ||||||
|       final total = int.parse(response.headers.value('X-Total') ?? '0'); |       final total = int.parse(response.headers.value('X-Total') ?? '0'); | ||||||
|   | |||||||
| @@ -148,7 +148,7 @@ class _StickerPackProviderElement | |||||||
| } | } | ||||||
|  |  | ||||||
| String _$stickerPacksNotifierHash() => | String _$stickerPacksNotifierHash() => | ||||||
|     r'0a8edcf9c35396c411f1214f5e77b1e8fac6a3e6'; |     r'30024b35235f3085a5b1ec2204d0a974ee907e22'; | ||||||
|  |  | ||||||
| abstract class _$StickerPacksNotifier | abstract class _$StickerPacksNotifier | ||||||
|     extends BuildlessAutoDisposeAsyncNotifier<CursorPagingData<SnStickerPack>> { |     extends BuildlessAutoDisposeAsyncNotifier<CursorPagingData<SnStickerPack>> { | ||||||
|   | |||||||
							
								
								
									
										131
									
								
								lib/screens/developers/app_detail.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										131
									
								
								lib/screens/developers/app_detail.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,131 @@ | |||||||
|  | import 'package:easy_localization/easy_localization.dart'; | ||||||
|  | import 'package:flutter/material.dart'; | ||||||
|  | import 'package:flutter_hooks/flutter_hooks.dart'; | ||||||
|  | import 'package:go_router/go_router.dart'; | ||||||
|  | import 'package:hooks_riverpod/hooks_riverpod.dart'; | ||||||
|  | import 'package:island/models/custom_app.dart'; | ||||||
|  | import 'package:island/screens/developers/app_secrets.dart'; | ||||||
|  | import 'package:island/screens/developers/apps.dart'; | ||||||
|  | import 'package:island/widgets/app_scaffold.dart'; | ||||||
|  | import 'package:island/widgets/content/cloud_files.dart'; | ||||||
|  | import 'package:island/widgets/response.dart'; | ||||||
|  | import 'package:material_symbols_icons/symbols.dart'; | ||||||
|  | import 'package:styled_widget/styled_widget.dart'; | ||||||
|  |  | ||||||
|  | class AppDetailScreen extends HookConsumerWidget { | ||||||
|  |   final String publisherName; | ||||||
|  |   final String projectId; | ||||||
|  |   final String appId; | ||||||
|  |  | ||||||
|  |   const AppDetailScreen({ | ||||||
|  |     super.key, | ||||||
|  |     required this.publisherName, | ||||||
|  |     required this.projectId, | ||||||
|  |     required this.appId, | ||||||
|  |   }); | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   Widget build(BuildContext context, WidgetRef ref) { | ||||||
|  |     final tabController = useTabController(initialLength: 2); | ||||||
|  |     final appData = ref.watch(customAppProvider(publisherName, projectId, appId)); | ||||||
|  |  | ||||||
|  |     return AppScaffold( | ||||||
|  |       appBar: AppBar( | ||||||
|  |         title: Text(appData.value?.name ?? 'appDetails'.tr()), | ||||||
|  |         actions: [ | ||||||
|  |           IconButton( | ||||||
|  |             icon: const Icon(Symbols.edit), | ||||||
|  |             onPressed: appData.value == null | ||||||
|  |                 ? null | ||||||
|  |                 : () { | ||||||
|  |                     context.pushNamed( | ||||||
|  |                       'developerAppEdit', | ||||||
|  |                       pathParameters: { | ||||||
|  |                         'name': publisherName, | ||||||
|  |                         'projectId': projectId, | ||||||
|  |                         'id': appId, | ||||||
|  |                       }, | ||||||
|  |                     ); | ||||||
|  |                   }, | ||||||
|  |           ), | ||||||
|  |         ], | ||||||
|  |         bottom: TabBar( | ||||||
|  |           controller: tabController, | ||||||
|  |           tabs: [Tab(text: 'overview'.tr()), Tab(text: 'secrets'.tr())], | ||||||
|  |         ), | ||||||
|  |       ), | ||||||
|  |       body: appData.when( | ||||||
|  |         data: (app) { | ||||||
|  |           return TabBarView( | ||||||
|  |             controller: tabController, | ||||||
|  |             physics: const NeverScrollableScrollPhysics(), | ||||||
|  |             children: [ | ||||||
|  |               _AppOverview(app: app), | ||||||
|  |               AppSecretsScreen( | ||||||
|  |                 publisherName: publisherName, | ||||||
|  |                 projectId: projectId, | ||||||
|  |                 appId: appId, | ||||||
|  |               ), | ||||||
|  |             ], | ||||||
|  |           ); | ||||||
|  |         }, | ||||||
|  |         loading: () => const Center(child: CircularProgressIndicator()), | ||||||
|  |         error: (err, stack) => ResponseErrorWidget( | ||||||
|  |           error: err, | ||||||
|  |           onRetry: () => ref.invalidate( | ||||||
|  |             customAppProvider(publisherName, projectId, appId), | ||||||
|  |           ), | ||||||
|  |         ), | ||||||
|  |       ), | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | class _AppOverview extends StatelessWidget { | ||||||
|  |   final CustomApp app; | ||||||
|  |   const _AppOverview({required this.app}); | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   Widget build(BuildContext context) { | ||||||
|  |     return SingleChildScrollView( | ||||||
|  |       child: Column( | ||||||
|  |         children: [ | ||||||
|  |           AspectRatio( | ||||||
|  |             aspectRatio: 16 / 7, | ||||||
|  |             child: Stack( | ||||||
|  |               clipBehavior: Clip.none, | ||||||
|  |               fit: StackFit.expand, | ||||||
|  |               children: [ | ||||||
|  |                 Container( | ||||||
|  |                   color: Theme.of(context).colorScheme.surfaceContainer, | ||||||
|  |                   child: app.background != null | ||||||
|  |                       ? CloudFileWidget( | ||||||
|  |                           item: app.background!, | ||||||
|  |                           fit: BoxFit.cover, | ||||||
|  |                         ) | ||||||
|  |                       : const SizedBox.shrink(), | ||||||
|  |                 ), | ||||||
|  |                 Positioned( | ||||||
|  |                   left: 20, | ||||||
|  |                   bottom: -32, | ||||||
|  |                   child: ProfilePictureWidget( | ||||||
|  |                     fileId: app.picture?.id, | ||||||
|  |                     radius: 40, | ||||||
|  |                     fallbackIcon: Symbols.apps, | ||||||
|  |                   ), | ||||||
|  |                 ), | ||||||
|  |               ], | ||||||
|  |             ), | ||||||
|  |           ).padding(bottom: 32), | ||||||
|  |           ListTile(title: Text('name'.tr()), subtitle: Text(app.name)), | ||||||
|  |           ListTile(title: Text('slug'.tr()), subtitle: Text(app.slug)), | ||||||
|  |           if (app.description?.isNotEmpty ?? false) | ||||||
|  |             ListTile( | ||||||
|  |               title: Text('description'.tr()), | ||||||
|  |               subtitle: Text(app.description!), | ||||||
|  |             ), | ||||||
|  |         ], | ||||||
|  |       ).padding(bottom: 24), | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  | } | ||||||
							
								
								
									
										252
									
								
								lib/screens/developers/app_secrets.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										252
									
								
								lib/screens/developers/app_secrets.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,252 @@ | |||||||
|  | import 'package:flutter/material.dart'; | ||||||
|  | import 'package:flutter/services.dart'; | ||||||
|  | import 'package:easy_localization/easy_localization.dart'; | ||||||
|  | import 'package:flutter_hooks/flutter_hooks.dart'; | ||||||
|  | import 'package:hooks_riverpod/hooks_riverpod.dart'; | ||||||
|  | import 'package:island/models/custom_app_secret.dart'; | ||||||
|  | import 'package:island/pods/network.dart'; | ||||||
|  | import 'package:island/services/time.dart'; | ||||||
|  | import 'package:island/widgets/alert.dart'; | ||||||
|  | import 'package:island/widgets/content/sheet.dart'; | ||||||
|  | import 'package:island/widgets/response.dart'; | ||||||
|  | import 'package:material_symbols_icons/symbols.dart'; | ||||||
|  | import 'package:riverpod_annotation/riverpod_annotation.dart'; | ||||||
|  |  | ||||||
|  | part 'app_secrets.g.dart'; | ||||||
|  |  | ||||||
|  | @riverpod | ||||||
|  | Future<List<CustomAppSecret>> customAppSecrets( | ||||||
|  |   Ref ref, | ||||||
|  |   String publisherName, | ||||||
|  |   String projectId, | ||||||
|  |   String appId, | ||||||
|  | ) async { | ||||||
|  |   final client = ref.watch(apiClientProvider); | ||||||
|  |   final resp = await client.get( | ||||||
|  |     '/develop/developers/$publisherName/projects/$projectId/apps/$appId/secrets', | ||||||
|  |   ); | ||||||
|  |   return (resp.data as List) | ||||||
|  |       .map((e) => CustomAppSecret.fromJson(e)) | ||||||
|  |       .cast<CustomAppSecret>() | ||||||
|  |       .toList(); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | class AppSecretsScreen extends HookConsumerWidget { | ||||||
|  |   final String publisherName; | ||||||
|  |   final String projectId; | ||||||
|  |   final String appId; | ||||||
|  |  | ||||||
|  |   const AppSecretsScreen({ | ||||||
|  |     super.key, | ||||||
|  |     required this.publisherName, | ||||||
|  |     required this.projectId, | ||||||
|  |     required this.appId, | ||||||
|  |   }); | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   Widget build(BuildContext context, WidgetRef ref) { | ||||||
|  |     final secrets = ref.watch( | ||||||
|  |       customAppSecretsProvider(publisherName, projectId, appId), | ||||||
|  |     ); | ||||||
|  |  | ||||||
|  |     void showNewSecretSheet(String newSecret) { | ||||||
|  |       showModalBottomSheet( | ||||||
|  |         context: context, | ||||||
|  |         isScrollControlled: true, | ||||||
|  |         builder: | ||||||
|  |             (context) => SheetScaffold( | ||||||
|  |               titleText: 'newSecretGenerated'.tr(), | ||||||
|  |               child: Padding( | ||||||
|  |                 padding: const EdgeInsets.all(20.0), | ||||||
|  |                 child: Column( | ||||||
|  |                   crossAxisAlignment: CrossAxisAlignment.stretch, | ||||||
|  |                   mainAxisSize: MainAxisSize.min, | ||||||
|  |                   children: [ | ||||||
|  |                     Text('copySecretHint'.tr()), | ||||||
|  |                     const SizedBox(height: 16), | ||||||
|  |                     Container( | ||||||
|  |                       padding: const EdgeInsets.all(12), | ||||||
|  |                       decoration: BoxDecoration( | ||||||
|  |                         color: Theme.of(context).colorScheme.surfaceContainer, | ||||||
|  |                         borderRadius: BorderRadius.circular(8), | ||||||
|  |                       ), | ||||||
|  |                       child: SelectableText(newSecret), | ||||||
|  |                     ), | ||||||
|  |                     const SizedBox(height: 20), | ||||||
|  |                     FilledButton.icon( | ||||||
|  |                       onPressed: () { | ||||||
|  |                         Clipboard.setData(ClipboardData(text: newSecret)); | ||||||
|  |                       }, | ||||||
|  |                       icon: const Icon(Symbols.copy_all), | ||||||
|  |                       label: Text('copy'.tr()), | ||||||
|  |                     ), | ||||||
|  |                   ], | ||||||
|  |                 ), | ||||||
|  |               ), | ||||||
|  |             ), | ||||||
|  |       ).whenComplete(() { | ||||||
|  |         ref.invalidate( | ||||||
|  |           customAppSecretsProvider(publisherName, projectId, appId), | ||||||
|  |         ); | ||||||
|  |       }); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     void createSecret() { | ||||||
|  |       showModalBottomSheet( | ||||||
|  |         context: context, | ||||||
|  |         isScrollControlled: true, | ||||||
|  |         builder: (context) { | ||||||
|  |           return HookBuilder( | ||||||
|  |             builder: (context) { | ||||||
|  |               final descriptionController = useTextEditingController(); | ||||||
|  |               final expiresInController = useTextEditingController(); | ||||||
|  |               final isOidc = useState(false); | ||||||
|  |  | ||||||
|  |               return SheetScaffold( | ||||||
|  |                 titleText: 'generateSecret'.tr(), | ||||||
|  |                 child: Padding( | ||||||
|  |                   padding: const EdgeInsets.all(20.0), | ||||||
|  |                   child: Column( | ||||||
|  |                     crossAxisAlignment: CrossAxisAlignment.stretch, | ||||||
|  |                     mainAxisSize: MainAxisSize.min, | ||||||
|  |                     children: [ | ||||||
|  |                       TextFormField( | ||||||
|  |                         controller: descriptionController, | ||||||
|  |                         decoration: InputDecoration( | ||||||
|  |                           labelText: 'description'.tr(), | ||||||
|  |                         ), | ||||||
|  |                         autofocus: true, | ||||||
|  |                       ), | ||||||
|  |                       const SizedBox(height: 20), | ||||||
|  |                       TextFormField( | ||||||
|  |                         controller: expiresInController, | ||||||
|  |                         decoration: InputDecoration( | ||||||
|  |                           labelText: 'expiresIn'.tr(), | ||||||
|  |                         ), | ||||||
|  |                         keyboardType: TextInputType.number, | ||||||
|  |                       ), | ||||||
|  |                       const SizedBox(height: 20), | ||||||
|  |                       SwitchListTile( | ||||||
|  |                         title: Text('isOidc'.tr()), | ||||||
|  |                         value: isOidc.value, | ||||||
|  |                         onChanged: (value) => isOidc.value = value, | ||||||
|  |                       ), | ||||||
|  |                       const SizedBox(height: 20), | ||||||
|  |                       FilledButton.icon( | ||||||
|  |                         onPressed: () async { | ||||||
|  |                           final description = descriptionController.text; | ||||||
|  |                           final expiresIn = int.tryParse( | ||||||
|  |                             expiresInController.text, | ||||||
|  |                           ); | ||||||
|  |                           Navigator.pop(context); // Close the sheet | ||||||
|  |                           try { | ||||||
|  |                             final client = ref.read(apiClientProvider); | ||||||
|  |                             final resp = await client.post( | ||||||
|  |                               '/develop/developers/$publisherName/projects/$projectId/apps/$appId/secrets', | ||||||
|  |                               data: { | ||||||
|  |                                 'description': description, | ||||||
|  |                                 'expires_in': expiresIn, | ||||||
|  |                                 'is_oidc': isOidc.value, | ||||||
|  |                               }, | ||||||
|  |                             ); | ||||||
|  |                             final newSecret = CustomAppSecret.fromJson( | ||||||
|  |                               resp.data, | ||||||
|  |                             ); | ||||||
|  |                             if (newSecret.secret != null) { | ||||||
|  |                               showNewSecretSheet(newSecret.secret!); | ||||||
|  |                             } | ||||||
|  |                           } catch (e) { | ||||||
|  |                             showErrorAlert(e.toString()); | ||||||
|  |                           } | ||||||
|  |                         }, | ||||||
|  |                         icon: const Icon(Symbols.add), | ||||||
|  |                         label: Text('create'.tr()), | ||||||
|  |                       ), | ||||||
|  |                     ], | ||||||
|  |                   ), | ||||||
|  |                 ), | ||||||
|  |               ); | ||||||
|  |             }, | ||||||
|  |           ); | ||||||
|  |         }, | ||||||
|  |       ); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     return secrets.when( | ||||||
|  |       data: (data) { | ||||||
|  |         return RefreshIndicator( | ||||||
|  |           onRefresh: | ||||||
|  |               () => ref.refresh( | ||||||
|  |                 customAppSecretsProvider( | ||||||
|  |                   publisherName, | ||||||
|  |                   projectId, | ||||||
|  |                   appId, | ||||||
|  |                 ).future, | ||||||
|  |               ), | ||||||
|  |           child: Column( | ||||||
|  |             children: [ | ||||||
|  |               ListTile( | ||||||
|  |                 leading: const Icon(Symbols.add), | ||||||
|  |                 title: Text('generateSecret'.tr()), | ||||||
|  |                 onTap: createSecret, | ||||||
|  |               ), | ||||||
|  |               const Divider(height: 1), | ||||||
|  |               Expanded( | ||||||
|  |                 child: ListView.builder( | ||||||
|  |                   padding: EdgeInsets.zero, | ||||||
|  |                   itemCount: data.length, | ||||||
|  |                   itemBuilder: (context, index) { | ||||||
|  |                     final secret = data[index]; | ||||||
|  |                     return ListTile( | ||||||
|  |                       title: Text(secret.description ?? secret.id), | ||||||
|  |                       subtitle: Text( | ||||||
|  |                         'createdAt'.tr(args: [secret.createdAt.formatSystem()]), | ||||||
|  |                       ), | ||||||
|  |                       trailing: Row( | ||||||
|  |                         mainAxisSize: MainAxisSize.min, | ||||||
|  |                         children: [ | ||||||
|  |                           IconButton( | ||||||
|  |                             icon: const Icon(Symbols.delete, color: Colors.red), | ||||||
|  |                             onPressed: () { | ||||||
|  |                               showConfirmAlert( | ||||||
|  |                                 'deleteSecretHint'.tr(), | ||||||
|  |                                 'deleteSecret'.tr(), | ||||||
|  |                               ).then((confirm) { | ||||||
|  |                                 if (confirm) { | ||||||
|  |                                   final client = ref.read(apiClientProvider); | ||||||
|  |                                   client.delete( | ||||||
|  |                                     '/develop/developers/$publisherName/projects/$projectId/apps/$appId/secrets/${secret.id}', | ||||||
|  |                                   ); | ||||||
|  |                                   ref.invalidate( | ||||||
|  |                                     customAppSecretsProvider( | ||||||
|  |                                       publisherName, | ||||||
|  |                                       projectId, | ||||||
|  |                                       appId, | ||||||
|  |                                     ), | ||||||
|  |                                   ); | ||||||
|  |                                 } | ||||||
|  |                               }); | ||||||
|  |                             }, | ||||||
|  |                           ), | ||||||
|  |                         ], | ||||||
|  |                       ), | ||||||
|  |                     ); | ||||||
|  |                   }, | ||||||
|  |                 ), | ||||||
|  |               ), | ||||||
|  |             ], | ||||||
|  |           ), | ||||||
|  |         ); | ||||||
|  |       }, | ||||||
|  |       loading: () => const Center(child: CircularProgressIndicator()), | ||||||
|  |       error: | ||||||
|  |           (err, stack) => ResponseErrorWidget( | ||||||
|  |             error: err, | ||||||
|  |             onRetry: | ||||||
|  |                 () => ref.invalidate( | ||||||
|  |                   customAppSecretsProvider(publisherName, projectId, appId), | ||||||
|  |                 ), | ||||||
|  |           ), | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  | } | ||||||
							
								
								
									
										188
									
								
								lib/screens/developers/app_secrets.g.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										188
									
								
								lib/screens/developers/app_secrets.g.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,188 @@ | |||||||
|  | // GENERATED CODE - DO NOT MODIFY BY HAND | ||||||
|  |  | ||||||
|  | part of 'app_secrets.dart'; | ||||||
|  |  | ||||||
|  | // ************************************************************************** | ||||||
|  | // RiverpodGenerator | ||||||
|  | // ************************************************************************** | ||||||
|  |  | ||||||
|  | String _$customAppSecretsHash() => r'1bc62ad812487883ce739793b22a76168d656752'; | ||||||
|  |  | ||||||
|  | /// 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)); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /// See also [customAppSecrets]. | ||||||
|  | @ProviderFor(customAppSecrets) | ||||||
|  | const customAppSecretsProvider = CustomAppSecretsFamily(); | ||||||
|  |  | ||||||
|  | /// See also [customAppSecrets]. | ||||||
|  | class CustomAppSecretsFamily extends Family<AsyncValue<List<CustomAppSecret>>> { | ||||||
|  |   /// See also [customAppSecrets]. | ||||||
|  |   const CustomAppSecretsFamily(); | ||||||
|  |  | ||||||
|  |   /// See also [customAppSecrets]. | ||||||
|  |   CustomAppSecretsProvider call( | ||||||
|  |     String publisherName, | ||||||
|  |     String projectId, | ||||||
|  |     String appId, | ||||||
|  |   ) { | ||||||
|  |     return CustomAppSecretsProvider(publisherName, projectId, appId); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   CustomAppSecretsProvider getProviderOverride( | ||||||
|  |     covariant CustomAppSecretsProvider provider, | ||||||
|  |   ) { | ||||||
|  |     return call(provider.publisherName, provider.projectId, provider.appId); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   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'customAppSecretsProvider'; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /// See also [customAppSecrets]. | ||||||
|  | class CustomAppSecretsProvider | ||||||
|  |     extends AutoDisposeFutureProvider<List<CustomAppSecret>> { | ||||||
|  |   /// See also [customAppSecrets]. | ||||||
|  |   CustomAppSecretsProvider(String publisherName, String projectId, String appId) | ||||||
|  |     : this._internal( | ||||||
|  |         (ref) => customAppSecrets( | ||||||
|  |           ref as CustomAppSecretsRef, | ||||||
|  |           publisherName, | ||||||
|  |           projectId, | ||||||
|  |           appId, | ||||||
|  |         ), | ||||||
|  |         from: customAppSecretsProvider, | ||||||
|  |         name: r'customAppSecretsProvider', | ||||||
|  |         debugGetCreateSourceHash: | ||||||
|  |             const bool.fromEnvironment('dart.vm.product') | ||||||
|  |                 ? null | ||||||
|  |                 : _$customAppSecretsHash, | ||||||
|  |         dependencies: CustomAppSecretsFamily._dependencies, | ||||||
|  |         allTransitiveDependencies: | ||||||
|  |             CustomAppSecretsFamily._allTransitiveDependencies, | ||||||
|  |         publisherName: publisherName, | ||||||
|  |         projectId: projectId, | ||||||
|  |         appId: appId, | ||||||
|  |       ); | ||||||
|  |  | ||||||
|  |   CustomAppSecretsProvider._internal( | ||||||
|  |     super._createNotifier, { | ||||||
|  |     required super.name, | ||||||
|  |     required super.dependencies, | ||||||
|  |     required super.allTransitiveDependencies, | ||||||
|  |     required super.debugGetCreateSourceHash, | ||||||
|  |     required super.from, | ||||||
|  |     required this.publisherName, | ||||||
|  |     required this.projectId, | ||||||
|  |     required this.appId, | ||||||
|  |   }) : super.internal(); | ||||||
|  |  | ||||||
|  |   final String publisherName; | ||||||
|  |   final String projectId; | ||||||
|  |   final String appId; | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   Override overrideWith( | ||||||
|  |     FutureOr<List<CustomAppSecret>> Function(CustomAppSecretsRef provider) | ||||||
|  |     create, | ||||||
|  |   ) { | ||||||
|  |     return ProviderOverride( | ||||||
|  |       origin: this, | ||||||
|  |       override: CustomAppSecretsProvider._internal( | ||||||
|  |         (ref) => create(ref as CustomAppSecretsRef), | ||||||
|  |         from: from, | ||||||
|  |         name: null, | ||||||
|  |         dependencies: null, | ||||||
|  |         allTransitiveDependencies: null, | ||||||
|  |         debugGetCreateSourceHash: null, | ||||||
|  |         publisherName: publisherName, | ||||||
|  |         projectId: projectId, | ||||||
|  |         appId: appId, | ||||||
|  |       ), | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   AutoDisposeFutureProviderElement<List<CustomAppSecret>> createElement() { | ||||||
|  |     return _CustomAppSecretsProviderElement(this); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   bool operator ==(Object other) { | ||||||
|  |     return other is CustomAppSecretsProvider && | ||||||
|  |         other.publisherName == publisherName && | ||||||
|  |         other.projectId == projectId && | ||||||
|  |         other.appId == appId; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   int get hashCode { | ||||||
|  |     var hash = _SystemHash.combine(0, runtimeType.hashCode); | ||||||
|  |     hash = _SystemHash.combine(hash, publisherName.hashCode); | ||||||
|  |     hash = _SystemHash.combine(hash, projectId.hashCode); | ||||||
|  |     hash = _SystemHash.combine(hash, appId.hashCode); | ||||||
|  |  | ||||||
|  |     return _SystemHash.finish(hash); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @Deprecated('Will be removed in 3.0. Use Ref instead') | ||||||
|  | // ignore: unused_element | ||||||
|  | mixin CustomAppSecretsRef | ||||||
|  |     on AutoDisposeFutureProviderRef<List<CustomAppSecret>> { | ||||||
|  |   /// The parameter `publisherName` of this provider. | ||||||
|  |   String get publisherName; | ||||||
|  |  | ||||||
|  |   /// The parameter `projectId` of this provider. | ||||||
|  |   String get projectId; | ||||||
|  |  | ||||||
|  |   /// The parameter `appId` of this provider. | ||||||
|  |   String get appId; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | class _CustomAppSecretsProviderElement | ||||||
|  |     extends AutoDisposeFutureProviderElement<List<CustomAppSecret>> | ||||||
|  |     with CustomAppSecretsRef { | ||||||
|  |   _CustomAppSecretsProviderElement(super.provider); | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   String get publisherName => | ||||||
|  |       (origin as CustomAppSecretsProvider).publisherName; | ||||||
|  |   @override | ||||||
|  |   String get projectId => (origin as CustomAppSecretsProvider).projectId; | ||||||
|  |   @override | ||||||
|  |   String get appId => (origin as CustomAppSecretsProvider).appId; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // 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 | ||||||
| @@ -6,7 +6,6 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; | |||||||
| import 'package:island/models/custom_app.dart'; | import 'package:island/models/custom_app.dart'; | ||||||
| import 'package:island/pods/network.dart'; | import 'package:island/pods/network.dart'; | ||||||
| import 'package:island/widgets/alert.dart'; | import 'package:island/widgets/alert.dart'; | ||||||
| import 'package:island/widgets/app_scaffold.dart'; |  | ||||||
| import 'package:island/widgets/content/cloud_files.dart'; | import 'package:island/widgets/content/cloud_files.dart'; | ||||||
| import 'package:island/widgets/response.dart'; | import 'package:island/widgets/response.dart'; | ||||||
| import 'package:material_symbols_icons/symbols.dart'; | import 'package:material_symbols_icons/symbols.dart'; | ||||||
| @@ -16,50 +15,98 @@ import 'package:styled_widget/styled_widget.dart'; | |||||||
| part 'apps.g.dart'; | part 'apps.g.dart'; | ||||||
|  |  | ||||||
| @riverpod | @riverpod | ||||||
| Future<List<CustomApp>> customApps(Ref ref, String publisherName) async { | Future<CustomApp> customApp( | ||||||
|  |   Ref ref, | ||||||
|  |   String publisherName, | ||||||
|  |   String projectId, | ||||||
|  |   String appId, | ||||||
|  | ) async { | ||||||
|   final client = ref.watch(apiClientProvider); |   final client = ref.watch(apiClientProvider); | ||||||
|   final resp = await client.get('/develop/developers/$publisherName/apps'); |   final resp = await client.get( | ||||||
|   return resp.data.map((e) => CustomApp.fromJson(e)).cast<CustomApp>().toList(); |     '/develop/developers/$publisherName/projects/$projectId/apps/$appId', | ||||||
|  |   ); | ||||||
|  |   return CustomApp.fromJson(resp.data); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @riverpod | ||||||
|  | Future<List<CustomApp>> customApps( | ||||||
|  |   Ref ref, | ||||||
|  |   String publisherName, | ||||||
|  |   String projectId, | ||||||
|  | ) async { | ||||||
|  |   final client = ref.watch(apiClientProvider); | ||||||
|  |   final resp = await client.get( | ||||||
|  |     '/develop/developers/$publisherName/projects/$projectId/apps', | ||||||
|  |   ); | ||||||
|  |   return (resp.data as List) | ||||||
|  |       .map((e) => CustomApp.fromJson(e)) | ||||||
|  |       .cast<CustomApp>() | ||||||
|  |       .toList(); | ||||||
| } | } | ||||||
|  |  | ||||||
| class CustomAppsScreen extends HookConsumerWidget { | class CustomAppsScreen extends HookConsumerWidget { | ||||||
|   final String publisherName; |   final String publisherName; | ||||||
|   const CustomAppsScreen({super.key, required this.publisherName}); |   final String projectId; | ||||||
|  |   const CustomAppsScreen({ | ||||||
|  |     super.key, | ||||||
|  |     required this.publisherName, | ||||||
|  |     required this.projectId, | ||||||
|  |   }); | ||||||
|  |  | ||||||
|   @override |   @override | ||||||
|   Widget build(BuildContext context, WidgetRef ref) { |   Widget build(BuildContext context, WidgetRef ref) { | ||||||
|     final apps = ref.watch(customAppsProvider(publisherName)); |     final apps = ref.watch(customAppsProvider(publisherName, projectId)); | ||||||
|  |  | ||||||
|     return AppScaffold( |     return apps.when( | ||||||
|       appBar: AppBar( |       data: (data) { | ||||||
|         title: Text('customApps').tr(), |         if (data.isEmpty) { | ||||||
|         actions: [ |           return Center( | ||||||
|           IconButton( |             child: Column( | ||||||
|             icon: const Icon(Symbols.add), |               mainAxisAlignment: MainAxisAlignment.center, | ||||||
|             onPressed: () { |               children: [ | ||||||
|               context.pushNamed( |                 Text('noCustomApps').tr(), | ||||||
|                 'developerAppNew', |                 const SizedBox(height: 16), | ||||||
|                 pathParameters: {'name': publisherName}, |                 ElevatedButton.icon( | ||||||
|               ); |                   onPressed: () { | ||||||
|             }, |                     context.pushNamed( | ||||||
|           ), |                       'developerAppNew', | ||||||
|         ], |                       pathParameters: { | ||||||
|       ), |                         'name': publisherName, | ||||||
|       body: apps.when( |                         'projectId': projectId, | ||||||
|         data: (data) { |                       }, | ||||||
|           if (data.isEmpty) { |                     ); | ||||||
|             return Center(child: Text('noCustomApps').tr()); |                   }, | ||||||
|           } |                   icon: const Icon(Symbols.add), | ||||||
|           return RefreshIndicator( |                   label: Text('createCustomApp').tr(), | ||||||
|             onRefresh: |                 ), | ||||||
|                 () => ref.refresh(customAppsProvider(publisherName).future), |               ], | ||||||
|             child: ListView.builder( |             ), | ||||||
|               padding: EdgeInsets.only(top: 4), |           ); | ||||||
|               itemCount: data.length, |         } | ||||||
|               itemBuilder: (context, index) { |         return RefreshIndicator( | ||||||
|                 final app = data[index]; |           onRefresh: | ||||||
|                 return Card( |               () => ref.refresh( | ||||||
|                   margin: const EdgeInsets.all(8.0), |                 customAppsProvider(publisherName, projectId).future, | ||||||
|  |               ), | ||||||
|  |           child: ListView.builder( | ||||||
|  |             padding: EdgeInsets.only(top: 4), | ||||||
|  |             itemCount: data.length, | ||||||
|  |             itemBuilder: (context, index) { | ||||||
|  |               final app = data[index]; | ||||||
|  |               return Card( | ||||||
|  |                 margin: const EdgeInsets.all(8.0), | ||||||
|  |                 clipBehavior: Clip.antiAlias, | ||||||
|  |                 child: InkWell( | ||||||
|  |                   onTap: () { | ||||||
|  |                     context.pushNamed( | ||||||
|  |                       'developerAppDetail', | ||||||
|  |                       pathParameters: { | ||||||
|  |                         'name': publisherName, | ||||||
|  |                         'projectId': projectId, | ||||||
|  |                         'appId': app.id, | ||||||
|  |                       }, | ||||||
|  |                     ); | ||||||
|  |                   }, | ||||||
|                   child: Column( |                   child: Column( | ||||||
|                     children: [ |                     children: [ | ||||||
|                       SizedBox( |                       SizedBox( | ||||||
| @@ -128,6 +175,7 @@ class CustomAppsScreen extends HookConsumerWidget { | |||||||
|                                 'developerAppEdit', |                                 'developerAppEdit', | ||||||
|                                 pathParameters: { |                                 pathParameters: { | ||||||
|                                   'name': publisherName, |                                   'name': publisherName, | ||||||
|  |                                   'projectId': projectId, | ||||||
|                                   'id': app.id, |                                   'id': app.id, | ||||||
|                                 }, |                                 }, | ||||||
|                               ); |                               ); | ||||||
| @@ -139,10 +187,13 @@ class CustomAppsScreen extends HookConsumerWidget { | |||||||
|                                 if (confirm) { |                                 if (confirm) { | ||||||
|                                   final client = ref.read(apiClientProvider); |                                   final client = ref.read(apiClientProvider); | ||||||
|                                   client.delete( |                                   client.delete( | ||||||
|                                     '/develop/developers/$publisherName/apps/${app.id}', |                                     '/develop/developers/$publisherName/projects/$projectId/apps/${app.id}', | ||||||
|                                   ); |                                   ); | ||||||
|                                   ref.invalidate( |                                   ref.invalidate( | ||||||
|                                     customAppsProvider(publisherName), |                                     customAppsProvider( | ||||||
|  |                                       publisherName, | ||||||
|  |                                       projectId, | ||||||
|  |                                     ), | ||||||
|                                   ); |                                   ); | ||||||
|                                 } |                                 } | ||||||
|                               }); |                               }); | ||||||
| @@ -152,18 +203,21 @@ class CustomAppsScreen extends HookConsumerWidget { | |||||||
|                       ), |                       ), | ||||||
|                     ], |                     ], | ||||||
|                   ), |                   ), | ||||||
|                 ); |                 ), | ||||||
|               }, |               ); | ||||||
|             ), |             }, | ||||||
|           ); |           ), | ||||||
|         }, |         ); | ||||||
|         loading: () => const Center(child: CircularProgressIndicator()), |       }, | ||||||
|         error: |       loading: () => const Center(child: CircularProgressIndicator()), | ||||||
|             (err, stack) => ResponseErrorWidget( |       error: | ||||||
|               error: err, |           (err, stack) => ResponseErrorWidget( | ||||||
|               onRetry: () => ref.invalidate(customAppsProvider(publisherName)), |             error: err, | ||||||
|             ), |             onRetry: | ||||||
|       ), |                 () => ref.invalidate( | ||||||
|  |                   customAppsProvider(publisherName, projectId), | ||||||
|  |                 ), | ||||||
|  |           ), | ||||||
|     ); |     ); | ||||||
|   } |   } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -6,7 +6,7 @@ part of 'apps.dart'; | |||||||
| // RiverpodGenerator | // RiverpodGenerator | ||||||
| // ************************************************************************** | // ************************************************************************** | ||||||
|  |  | ||||||
| String _$customAppsHash() => r'c6ac78060eb51a2b208a749a81ecbe0a9c608ce1'; | String _$customAppHash() => r'be05431ba8bf06fd20ee988a61c3663a68e15fc9'; | ||||||
|  |  | ||||||
| /// Copied from Dart SDK | /// Copied from Dart SDK | ||||||
| class _SystemHash { | class _SystemHash { | ||||||
| @@ -29,6 +29,148 @@ class _SystemHash { | |||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | /// See also [customApp]. | ||||||
|  | @ProviderFor(customApp) | ||||||
|  | const customAppProvider = CustomAppFamily(); | ||||||
|  |  | ||||||
|  | /// See also [customApp]. | ||||||
|  | class CustomAppFamily extends Family<AsyncValue<CustomApp>> { | ||||||
|  |   /// See also [customApp]. | ||||||
|  |   const CustomAppFamily(); | ||||||
|  |  | ||||||
|  |   /// See also [customApp]. | ||||||
|  |   CustomAppProvider call(String publisherName, String projectId, String appId) { | ||||||
|  |     return CustomAppProvider(publisherName, projectId, appId); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   CustomAppProvider getProviderOverride(covariant CustomAppProvider provider) { | ||||||
|  |     return call(provider.publisherName, provider.projectId, provider.appId); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   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'customAppProvider'; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /// See also [customApp]. | ||||||
|  | class CustomAppProvider extends AutoDisposeFutureProvider<CustomApp> { | ||||||
|  |   /// See also [customApp]. | ||||||
|  |   CustomAppProvider(String publisherName, String projectId, String appId) | ||||||
|  |     : this._internal( | ||||||
|  |         (ref) => | ||||||
|  |             customApp(ref as CustomAppRef, publisherName, projectId, appId), | ||||||
|  |         from: customAppProvider, | ||||||
|  |         name: r'customAppProvider', | ||||||
|  |         debugGetCreateSourceHash: | ||||||
|  |             const bool.fromEnvironment('dart.vm.product') | ||||||
|  |                 ? null | ||||||
|  |                 : _$customAppHash, | ||||||
|  |         dependencies: CustomAppFamily._dependencies, | ||||||
|  |         allTransitiveDependencies: CustomAppFamily._allTransitiveDependencies, | ||||||
|  |         publisherName: publisherName, | ||||||
|  |         projectId: projectId, | ||||||
|  |         appId: appId, | ||||||
|  |       ); | ||||||
|  |  | ||||||
|  |   CustomAppProvider._internal( | ||||||
|  |     super._createNotifier, { | ||||||
|  |     required super.name, | ||||||
|  |     required super.dependencies, | ||||||
|  |     required super.allTransitiveDependencies, | ||||||
|  |     required super.debugGetCreateSourceHash, | ||||||
|  |     required super.from, | ||||||
|  |     required this.publisherName, | ||||||
|  |     required this.projectId, | ||||||
|  |     required this.appId, | ||||||
|  |   }) : super.internal(); | ||||||
|  |  | ||||||
|  |   final String publisherName; | ||||||
|  |   final String projectId; | ||||||
|  |   final String appId; | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   Override overrideWith( | ||||||
|  |     FutureOr<CustomApp> Function(CustomAppRef provider) create, | ||||||
|  |   ) { | ||||||
|  |     return ProviderOverride( | ||||||
|  |       origin: this, | ||||||
|  |       override: CustomAppProvider._internal( | ||||||
|  |         (ref) => create(ref as CustomAppRef), | ||||||
|  |         from: from, | ||||||
|  |         name: null, | ||||||
|  |         dependencies: null, | ||||||
|  |         allTransitiveDependencies: null, | ||||||
|  |         debugGetCreateSourceHash: null, | ||||||
|  |         publisherName: publisherName, | ||||||
|  |         projectId: projectId, | ||||||
|  |         appId: appId, | ||||||
|  |       ), | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   AutoDisposeFutureProviderElement<CustomApp> createElement() { | ||||||
|  |     return _CustomAppProviderElement(this); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   bool operator ==(Object other) { | ||||||
|  |     return other is CustomAppProvider && | ||||||
|  |         other.publisherName == publisherName && | ||||||
|  |         other.projectId == projectId && | ||||||
|  |         other.appId == appId; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   int get hashCode { | ||||||
|  |     var hash = _SystemHash.combine(0, runtimeType.hashCode); | ||||||
|  |     hash = _SystemHash.combine(hash, publisherName.hashCode); | ||||||
|  |     hash = _SystemHash.combine(hash, projectId.hashCode); | ||||||
|  |     hash = _SystemHash.combine(hash, appId.hashCode); | ||||||
|  |  | ||||||
|  |     return _SystemHash.finish(hash); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @Deprecated('Will be removed in 3.0. Use Ref instead') | ||||||
|  | // ignore: unused_element | ||||||
|  | mixin CustomAppRef on AutoDisposeFutureProviderRef<CustomApp> { | ||||||
|  |   /// The parameter `publisherName` of this provider. | ||||||
|  |   String get publisherName; | ||||||
|  |  | ||||||
|  |   /// The parameter `projectId` of this provider. | ||||||
|  |   String get projectId; | ||||||
|  |  | ||||||
|  |   /// The parameter `appId` of this provider. | ||||||
|  |   String get appId; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | class _CustomAppProviderElement | ||||||
|  |     extends AutoDisposeFutureProviderElement<CustomApp> | ||||||
|  |     with CustomAppRef { | ||||||
|  |   _CustomAppProviderElement(super.provider); | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   String get publisherName => (origin as CustomAppProvider).publisherName; | ||||||
|  |   @override | ||||||
|  |   String get projectId => (origin as CustomAppProvider).projectId; | ||||||
|  |   @override | ||||||
|  |   String get appId => (origin as CustomAppProvider).appId; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | String _$customAppsHash() => r'450bedaf4220b8963cb44afeb14d4c0e80f01b11'; | ||||||
|  |  | ||||||
| /// See also [customApps]. | /// See also [customApps]. | ||||||
| @ProviderFor(customApps) | @ProviderFor(customApps) | ||||||
| const customAppsProvider = CustomAppsFamily(); | const customAppsProvider = CustomAppsFamily(); | ||||||
| @@ -39,15 +181,15 @@ class CustomAppsFamily extends Family<AsyncValue<List<CustomApp>>> { | |||||||
|   const CustomAppsFamily(); |   const CustomAppsFamily(); | ||||||
|  |  | ||||||
|   /// See also [customApps]. |   /// See also [customApps]. | ||||||
|   CustomAppsProvider call(String publisherName) { |   CustomAppsProvider call(String publisherName, String projectId) { | ||||||
|     return CustomAppsProvider(publisherName); |     return CustomAppsProvider(publisherName, projectId); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   @override |   @override | ||||||
|   CustomAppsProvider getProviderOverride( |   CustomAppsProvider getProviderOverride( | ||||||
|     covariant CustomAppsProvider provider, |     covariant CustomAppsProvider provider, | ||||||
|   ) { |   ) { | ||||||
|     return call(provider.publisherName); |     return call(provider.publisherName, provider.projectId); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   static const Iterable<ProviderOrFamily>? _dependencies = null; |   static const Iterable<ProviderOrFamily>? _dependencies = null; | ||||||
| @@ -68,9 +210,9 @@ class CustomAppsFamily extends Family<AsyncValue<List<CustomApp>>> { | |||||||
| /// See also [customApps]. | /// See also [customApps]. | ||||||
| class CustomAppsProvider extends AutoDisposeFutureProvider<List<CustomApp>> { | class CustomAppsProvider extends AutoDisposeFutureProvider<List<CustomApp>> { | ||||||
|   /// See also [customApps]. |   /// See also [customApps]. | ||||||
|   CustomAppsProvider(String publisherName) |   CustomAppsProvider(String publisherName, String projectId) | ||||||
|     : this._internal( |     : this._internal( | ||||||
|         (ref) => customApps(ref as CustomAppsRef, publisherName), |         (ref) => customApps(ref as CustomAppsRef, publisherName, projectId), | ||||||
|         from: customAppsProvider, |         from: customAppsProvider, | ||||||
|         name: r'customAppsProvider', |         name: r'customAppsProvider', | ||||||
|         debugGetCreateSourceHash: |         debugGetCreateSourceHash: | ||||||
| @@ -80,6 +222,7 @@ class CustomAppsProvider extends AutoDisposeFutureProvider<List<CustomApp>> { | |||||||
|         dependencies: CustomAppsFamily._dependencies, |         dependencies: CustomAppsFamily._dependencies, | ||||||
|         allTransitiveDependencies: CustomAppsFamily._allTransitiveDependencies, |         allTransitiveDependencies: CustomAppsFamily._allTransitiveDependencies, | ||||||
|         publisherName: publisherName, |         publisherName: publisherName, | ||||||
|  |         projectId: projectId, | ||||||
|       ); |       ); | ||||||
|  |  | ||||||
|   CustomAppsProvider._internal( |   CustomAppsProvider._internal( | ||||||
| @@ -90,9 +233,11 @@ class CustomAppsProvider extends AutoDisposeFutureProvider<List<CustomApp>> { | |||||||
|     required super.debugGetCreateSourceHash, |     required super.debugGetCreateSourceHash, | ||||||
|     required super.from, |     required super.from, | ||||||
|     required this.publisherName, |     required this.publisherName, | ||||||
|  |     required this.projectId, | ||||||
|   }) : super.internal(); |   }) : super.internal(); | ||||||
|  |  | ||||||
|   final String publisherName; |   final String publisherName; | ||||||
|  |   final String projectId; | ||||||
|  |  | ||||||
|   @override |   @override | ||||||
|   Override overrideWith( |   Override overrideWith( | ||||||
| @@ -108,6 +253,7 @@ class CustomAppsProvider extends AutoDisposeFutureProvider<List<CustomApp>> { | |||||||
|         allTransitiveDependencies: null, |         allTransitiveDependencies: null, | ||||||
|         debugGetCreateSourceHash: null, |         debugGetCreateSourceHash: null, | ||||||
|         publisherName: publisherName, |         publisherName: publisherName, | ||||||
|  |         projectId: projectId, | ||||||
|       ), |       ), | ||||||
|     ); |     ); | ||||||
|   } |   } | ||||||
| @@ -119,13 +265,16 @@ class CustomAppsProvider extends AutoDisposeFutureProvider<List<CustomApp>> { | |||||||
|  |  | ||||||
|   @override |   @override | ||||||
|   bool operator ==(Object other) { |   bool operator ==(Object other) { | ||||||
|     return other is CustomAppsProvider && other.publisherName == publisherName; |     return other is CustomAppsProvider && | ||||||
|  |         other.publisherName == publisherName && | ||||||
|  |         other.projectId == projectId; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   @override |   @override | ||||||
|   int get hashCode { |   int get hashCode { | ||||||
|     var hash = _SystemHash.combine(0, runtimeType.hashCode); |     var hash = _SystemHash.combine(0, runtimeType.hashCode); | ||||||
|     hash = _SystemHash.combine(hash, publisherName.hashCode); |     hash = _SystemHash.combine(hash, publisherName.hashCode); | ||||||
|  |     hash = _SystemHash.combine(hash, projectId.hashCode); | ||||||
|  |  | ||||||
|     return _SystemHash.finish(hash); |     return _SystemHash.finish(hash); | ||||||
|   } |   } | ||||||
| @@ -136,6 +285,9 @@ class CustomAppsProvider extends AutoDisposeFutureProvider<List<CustomApp>> { | |||||||
| mixin CustomAppsRef on AutoDisposeFutureProviderRef<List<CustomApp>> { | mixin CustomAppsRef on AutoDisposeFutureProviderRef<List<CustomApp>> { | ||||||
|   /// The parameter `publisherName` of this provider. |   /// The parameter `publisherName` of this provider. | ||||||
|   String get publisherName; |   String get publisherName; | ||||||
|  |  | ||||||
|  |   /// The parameter `projectId` of this provider. | ||||||
|  |   String get projectId; | ||||||
| } | } | ||||||
|  |  | ||||||
| class _CustomAppsProviderElement | class _CustomAppsProviderElement | ||||||
| @@ -145,6 +297,8 @@ class _CustomAppsProviderElement | |||||||
|  |  | ||||||
|   @override |   @override | ||||||
|   String get publisherName => (origin as CustomAppsProvider).publisherName; |   String get publisherName => (origin as CustomAppsProvider).publisherName; | ||||||
|  |   @override | ||||||
|  |   String get projectId => (origin as CustomAppsProvider).projectId; | ||||||
| } | } | ||||||
|  |  | ||||||
| // ignore_for_file: type=lint | // ignore_for_file: type=lint | ||||||
|   | |||||||
							
								
								
									
										142
									
								
								lib/screens/developers/bot_detail.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										142
									
								
								lib/screens/developers/bot_detail.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,142 @@ | |||||||
|  | import 'package:easy_localization/easy_localization.dart'; | ||||||
|  | import 'package:flutter/material.dart'; | ||||||
|  | import 'package:flutter_hooks/flutter_hooks.dart'; | ||||||
|  | import 'package:hooks_riverpod/hooks_riverpod.dart'; | ||||||
|  | import 'package:island/models/bot.dart'; | ||||||
|  | import 'package:island/screens/developers/bot_keys.dart'; | ||||||
|  | import 'package:island/screens/developers/edit_bot.dart'; | ||||||
|  | import 'package:island/widgets/app_scaffold.dart'; | ||||||
|  | import 'package:island/widgets/content/cloud_files.dart'; | ||||||
|  | import 'package:island/widgets/response.dart'; | ||||||
|  | import 'package:material_symbols_icons/symbols.dart'; | ||||||
|  | import 'package:go_router/go_router.dart'; | ||||||
|  | import 'package:styled_widget/styled_widget.dart'; | ||||||
|  |  | ||||||
|  | class BotDetailScreen extends HookConsumerWidget { | ||||||
|  |   final String publisherName; | ||||||
|  |   final String projectId; | ||||||
|  |   final String botId; | ||||||
|  |  | ||||||
|  |   const BotDetailScreen({ | ||||||
|  |     super.key, | ||||||
|  |     required this.publisherName, | ||||||
|  |     required this.projectId, | ||||||
|  |     required this.botId, | ||||||
|  |   }); | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   Widget build(BuildContext context, WidgetRef ref) { | ||||||
|  |     final tabController = useTabController(initialLength: 2); | ||||||
|  |     final botData = ref.watch(botProvider(publisherName, projectId, botId)); | ||||||
|  |  | ||||||
|  |     return AppScaffold( | ||||||
|  |       appBar: AppBar( | ||||||
|  |         title: Text(botData.value?.account.nick ?? 'botDetails'.tr()), | ||||||
|  |         actions: [ | ||||||
|  |           IconButton( | ||||||
|  |             icon: const Icon(Symbols.edit), | ||||||
|  |             onPressed: | ||||||
|  |                 botData.value == null | ||||||
|  |                     ? null | ||||||
|  |                     : () { | ||||||
|  |                       context.pushNamed( | ||||||
|  |                         'developerBotEdit', | ||||||
|  |                         pathParameters: { | ||||||
|  |                           'name': publisherName, | ||||||
|  |                           'projectId': projectId, | ||||||
|  |                           'id': botId, | ||||||
|  |                         }, | ||||||
|  |                       ); | ||||||
|  |                     }, | ||||||
|  |           ), | ||||||
|  |         ], | ||||||
|  |         bottom: TabBar( | ||||||
|  |           controller: tabController, | ||||||
|  |           tabs: [Tab(text: 'overview'.tr()), Tab(text: 'keys'.tr())], | ||||||
|  |         ), | ||||||
|  |       ), | ||||||
|  |       body: botData.when( | ||||||
|  |         data: (bot) { | ||||||
|  |           if (bot == null) { | ||||||
|  |             return Center(child: Text('botNotFound'.tr())); | ||||||
|  |           } | ||||||
|  |           return TabBarView( | ||||||
|  |             controller: tabController, | ||||||
|  |             physics: const NeverScrollableScrollPhysics(), | ||||||
|  |             children: [ | ||||||
|  |               _BotOverview(bot: bot), | ||||||
|  |               BotKeysScreen( | ||||||
|  |                 publisherName: publisherName, | ||||||
|  |                 projectId: projectId, | ||||||
|  |                 botId: botId, | ||||||
|  |               ), | ||||||
|  |             ], | ||||||
|  |           ); | ||||||
|  |         }, | ||||||
|  |         loading: () => const Center(child: CircularProgressIndicator()), | ||||||
|  |         error: | ||||||
|  |             (err, stack) => ResponseErrorWidget( | ||||||
|  |               error: err, | ||||||
|  |               onRetry: | ||||||
|  |                   () => ref.invalidate( | ||||||
|  |                     botProvider(publisherName, projectId, botId), | ||||||
|  |                   ), | ||||||
|  |             ), | ||||||
|  |       ), | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | class _BotOverview extends StatelessWidget { | ||||||
|  |   final Bot bot; | ||||||
|  |   const _BotOverview({required this.bot}); | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   Widget build(BuildContext context) { | ||||||
|  |     return SingleChildScrollView( | ||||||
|  |       child: Column( | ||||||
|  |         children: [ | ||||||
|  |           AspectRatio( | ||||||
|  |             aspectRatio: 16 / 7, | ||||||
|  |             child: Stack( | ||||||
|  |               clipBehavior: Clip.none, | ||||||
|  |               fit: StackFit.expand, | ||||||
|  |               children: [ | ||||||
|  |                 Container( | ||||||
|  |                   color: Theme.of(context).colorScheme.surfaceContainer, | ||||||
|  |                   child: | ||||||
|  |                       bot.account.profile.background != null | ||||||
|  |                           ? CloudFileWidget( | ||||||
|  |                             item: bot.account.profile.background!, | ||||||
|  |                             fit: BoxFit.cover, | ||||||
|  |                           ) | ||||||
|  |                           : const SizedBox.shrink(), | ||||||
|  |                 ), | ||||||
|  |                 Positioned( | ||||||
|  |                   left: 20, | ||||||
|  |                   bottom: -32, | ||||||
|  |                   child: ProfilePictureWidget( | ||||||
|  |                     fileId: bot.account.profile.picture?.id, | ||||||
|  |                     radius: 40, | ||||||
|  |                     fallbackIcon: Symbols.smart_toy, | ||||||
|  |                   ), | ||||||
|  |                 ), | ||||||
|  |               ], | ||||||
|  |             ), | ||||||
|  |           ).padding(bottom: 32), | ||||||
|  |           ListTile(title: Text('name'.tr()), subtitle: Text(bot.account.name)), | ||||||
|  |           ListTile( | ||||||
|  |             title: Text('nickname'.tr()), | ||||||
|  |             subtitle: Text(bot.account.nick), | ||||||
|  |           ), | ||||||
|  |           ListTile(title: Text('slug'.tr()), subtitle: Text(bot.slug)), | ||||||
|  |           if (bot.account.profile.bio.isNotEmpty) | ||||||
|  |             ListTile( | ||||||
|  |               title: Text('bio'.tr()), | ||||||
|  |               subtitle: Text(bot.account.profile.bio), | ||||||
|  |             ), | ||||||
|  |         ], | ||||||
|  |       ).padding(bottom: 24), | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  | } | ||||||
							
								
								
									
										278
									
								
								lib/screens/developers/bot_keys.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										278
									
								
								lib/screens/developers/bot_keys.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,278 @@ | |||||||
|  | import 'package:flutter/material.dart'; | ||||||
|  | import 'package:flutter/services.dart'; | ||||||
|  | import 'package:easy_localization/easy_localization.dart'; | ||||||
|  | import 'package:flutter_hooks/flutter_hooks.dart'; | ||||||
|  | import 'package:gap/gap.dart'; | ||||||
|  | import 'package:hooks_riverpod/hooks_riverpod.dart'; | ||||||
|  | import 'package:island/models/bot_key.dart'; | ||||||
|  | import 'package:island/pods/network.dart'; | ||||||
|  | import 'package:island/services/time.dart'; | ||||||
|  | import 'package:island/widgets/alert.dart'; | ||||||
|  | import 'package:island/widgets/content/sheet.dart'; | ||||||
|  | import 'package:island/widgets/response.dart'; | ||||||
|  | import 'package:material_symbols_icons/symbols.dart'; | ||||||
|  | import 'package:riverpod_annotation/riverpod_annotation.dart'; | ||||||
|  |  | ||||||
|  | part 'bot_keys.g.dart'; | ||||||
|  |  | ||||||
|  | @riverpod | ||||||
|  | Future<List<SnAccountApiKey>> botKeys( | ||||||
|  |   Ref ref, | ||||||
|  |   String publisherName, | ||||||
|  |   String projectId, | ||||||
|  |   String botId, | ||||||
|  | ) async { | ||||||
|  |   final client = ref.watch(apiClientProvider); | ||||||
|  |   final resp = await client.get( | ||||||
|  |     '/develop/developers/$publisherName/projects/$projectId/bots/$botId/keys', | ||||||
|  |   ); | ||||||
|  |   return (resp.data as List).map((e) => SnAccountApiKey.fromJson(e)).toList(); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | class BotKeysScreen extends HookConsumerWidget { | ||||||
|  |   final String publisherName; | ||||||
|  |   final String projectId; | ||||||
|  |   final String botId; | ||||||
|  |  | ||||||
|  |   const BotKeysScreen({ | ||||||
|  |     super.key, | ||||||
|  |     required this.publisherName, | ||||||
|  |     required this.projectId, | ||||||
|  |     required this.botId, | ||||||
|  |   }); | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   Widget build(BuildContext context, WidgetRef ref) { | ||||||
|  |     final keys = ref.watch(botKeysProvider(publisherName, projectId, botId)); | ||||||
|  |     final keyNameController = useTextEditingController(); | ||||||
|  |  | ||||||
|  |     void showNewKeySheet(SnAccountApiKey newApiKey) { | ||||||
|  |       final token = newApiKey.key; | ||||||
|  |       if (token == null) return; | ||||||
|  |  | ||||||
|  |       showModalBottomSheet( | ||||||
|  |         context: context, | ||||||
|  |         isScrollControlled: true, | ||||||
|  |         builder: | ||||||
|  |             (context) => SheetScaffold( | ||||||
|  |               titleText: 'newKeyGenerated'.tr(), | ||||||
|  |               child: Padding( | ||||||
|  |                 padding: const EdgeInsets.all(20.0), | ||||||
|  |                 child: Column( | ||||||
|  |                   crossAxisAlignment: CrossAxisAlignment.stretch, | ||||||
|  |                   mainAxisSize: MainAxisSize.min, | ||||||
|  |                   children: [ | ||||||
|  |                     Text('copyKeyHint'.tr()), | ||||||
|  |                     const SizedBox(height: 16), | ||||||
|  |                     Container( | ||||||
|  |                       padding: const EdgeInsets.all(12), | ||||||
|  |                       decoration: BoxDecoration( | ||||||
|  |                         color: Theme.of(context).colorScheme.surfaceContainer, | ||||||
|  |                         borderRadius: BorderRadius.circular(8), | ||||||
|  |                       ), | ||||||
|  |                       child: SelectableText(token), | ||||||
|  |                     ), | ||||||
|  |                     const SizedBox(height: 20), | ||||||
|  |                     FilledButton.icon( | ||||||
|  |                       onPressed: () { | ||||||
|  |                         Clipboard.setData(ClipboardData(text: token)); | ||||||
|  |                       }, | ||||||
|  |                       icon: const Icon(Symbols.copy_all), | ||||||
|  |                       label: Text('copy'.tr()), | ||||||
|  |                     ), | ||||||
|  |                   ], | ||||||
|  |                 ), | ||||||
|  |               ), | ||||||
|  |             ), | ||||||
|  |       ).whenComplete(() { | ||||||
|  |         ref.invalidate(botKeysProvider(publisherName, projectId, botId)); | ||||||
|  |       }); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     void createKey() { | ||||||
|  |       keyNameController.clear(); | ||||||
|  |       showModalBottomSheet( | ||||||
|  |         context: context, | ||||||
|  |         isScrollControlled: true, | ||||||
|  |         builder: | ||||||
|  |             (context) => SheetScaffold( | ||||||
|  |               titleText: 'newBotKey'.tr(), | ||||||
|  |               child: Padding( | ||||||
|  |                 padding: const EdgeInsets.all(20.0), | ||||||
|  |                 child: Column( | ||||||
|  |                   crossAxisAlignment: CrossAxisAlignment.stretch, | ||||||
|  |                   mainAxisSize: MainAxisSize.min, | ||||||
|  |                   children: [ | ||||||
|  |                     TextFormField( | ||||||
|  |                       controller: keyNameController, | ||||||
|  |                       decoration: InputDecoration(labelText: 'keyName'.tr()), | ||||||
|  |                       autofocus: true, | ||||||
|  |                     ), | ||||||
|  |                     const SizedBox(height: 20), | ||||||
|  |                     FilledButton.icon( | ||||||
|  |                       onPressed: () async { | ||||||
|  |                         if (keyNameController.text.isEmpty) return; | ||||||
|  |                         final keyName = keyNameController.text; | ||||||
|  |                         Navigator.pop(context); // Close the sheet | ||||||
|  |                         try { | ||||||
|  |                           final client = ref.read(apiClientProvider); | ||||||
|  |                           final resp = await client.post( | ||||||
|  |                             '/develop/developers/$publisherName/projects/$projectId/bots/$botId/keys', | ||||||
|  |                             data: {'label': keyName}, | ||||||
|  |                           ); | ||||||
|  |                           final newApiKey = SnAccountApiKey.fromJson(resp.data); | ||||||
|  |                           showNewKeySheet(newApiKey); | ||||||
|  |                         } catch (e) { | ||||||
|  |                           showErrorAlert(e.toString()); | ||||||
|  |                         } | ||||||
|  |                       }, | ||||||
|  |                       icon: const Icon(Symbols.add), | ||||||
|  |                       label: Text('create'.tr()), | ||||||
|  |                     ), | ||||||
|  |                   ], | ||||||
|  |                 ), | ||||||
|  |               ), | ||||||
|  |             ), | ||||||
|  |       ); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     void rotateKey(String keyId) { | ||||||
|  |       showConfirmAlert('rotateBotKeyHint'.tr(), 'rotateBotKey'.tr()).then(( | ||||||
|  |         confirm, | ||||||
|  |       ) async { | ||||||
|  |         if (confirm) { | ||||||
|  |           try { | ||||||
|  |             if (context.mounted) showLoadingModal(context); | ||||||
|  |             final client = ref.read(apiClientProvider); | ||||||
|  |             final resp = await client.post( | ||||||
|  |               '/develop/developers/$publisherName/projects/$projectId/bots/$botId/keys/$keyId/rotate', | ||||||
|  |             ); | ||||||
|  |             final rotatedApiKey = SnAccountApiKey.fromJson(resp.data); | ||||||
|  |             showNewKeySheet(rotatedApiKey); | ||||||
|  |           } catch (err) { | ||||||
|  |             showErrorAlert(err.toString()); | ||||||
|  |           } finally { | ||||||
|  |             if (context.mounted) hideLoadingModal(context); | ||||||
|  |           } | ||||||
|  |         } | ||||||
|  |       }); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     void revokeKey(String keyId) { | ||||||
|  |       showConfirmAlert('revokeBotKeyHint'.tr(), 'revokeBotKey'.tr()).then(( | ||||||
|  |         confirm, | ||||||
|  |       ) { | ||||||
|  |         if (confirm) { | ||||||
|  |           final client = ref.read(apiClientProvider); | ||||||
|  |           client | ||||||
|  |               .delete( | ||||||
|  |                 '/develop/developers/$publisherName/projects/$projectId/bots/$botId/keys/$keyId', | ||||||
|  |               ) | ||||||
|  |               .then((_) { | ||||||
|  |                 ref.invalidate( | ||||||
|  |                   botKeysProvider(publisherName, projectId, botId), | ||||||
|  |                 ); | ||||||
|  |               }) | ||||||
|  |               .catchError((err) { | ||||||
|  |                 showErrorAlert(err.toString()); | ||||||
|  |               }); | ||||||
|  |         } | ||||||
|  |       }); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     return keys.when( | ||||||
|  |       data: (data) { | ||||||
|  |         return Column( | ||||||
|  |           children: [ | ||||||
|  |             ListTile( | ||||||
|  |               leading: const Icon(Symbols.add), | ||||||
|  |               title: Text('newBotKey'.tr()), | ||||||
|  |               trailing: const Icon(Symbols.chevron_right), | ||||||
|  |               onTap: createKey, | ||||||
|  |             ), | ||||||
|  |             const Divider(height: 1), | ||||||
|  |             Expanded( | ||||||
|  |               child: | ||||||
|  |                   data.isEmpty | ||||||
|  |                       ? Center(child: Text('noBotKeys'.tr())) | ||||||
|  |                       : RefreshIndicator( | ||||||
|  |                         onRefresh: | ||||||
|  |                             () => ref.refresh( | ||||||
|  |                               botKeysProvider( | ||||||
|  |                                 publisherName, | ||||||
|  |                                 projectId, | ||||||
|  |                                 botId, | ||||||
|  |                               ).future, | ||||||
|  |                             ), | ||||||
|  |                         child: ListView.builder( | ||||||
|  |                           padding: EdgeInsets.zero, | ||||||
|  |                           itemCount: data.length, | ||||||
|  |                           itemBuilder: (context, index) { | ||||||
|  |                             final apiKey = data[index]; | ||||||
|  |                             return ListTile( | ||||||
|  |                               title: Text(apiKey.label), | ||||||
|  |                               subtitle: Text(apiKey.createdAt.formatSystem()), | ||||||
|  |                               contentPadding: EdgeInsets.only( | ||||||
|  |                                 left: 16, | ||||||
|  |                                 right: 12, | ||||||
|  |                               ), | ||||||
|  |                               trailing: PopupMenuButton( | ||||||
|  |                                 itemBuilder: | ||||||
|  |                                     (context) => [ | ||||||
|  |                                       PopupMenuItem( | ||||||
|  |                                         value: 'rotate', | ||||||
|  |                                         child: Row( | ||||||
|  |                                           children: [ | ||||||
|  |                                             const Icon(Symbols.refresh), | ||||||
|  |                                             const Gap(12), | ||||||
|  |                                             Text('rotateKey'.tr()), | ||||||
|  |                                           ], | ||||||
|  |                                         ), | ||||||
|  |                                       ), | ||||||
|  |                                       PopupMenuItem( | ||||||
|  |                                         value: 'revoke', | ||||||
|  |                                         child: Row( | ||||||
|  |                                           children: [ | ||||||
|  |                                             const Icon( | ||||||
|  |                                               Symbols.delete, | ||||||
|  |                                               color: Colors.red, | ||||||
|  |                                             ), | ||||||
|  |                                             const Gap(12), | ||||||
|  |                                             Text( | ||||||
|  |                                               'revoke'.tr(), | ||||||
|  |                                               style: TextStyle( | ||||||
|  |                                                 color: Colors.red, | ||||||
|  |                                               ), | ||||||
|  |                                             ), | ||||||
|  |                                           ], | ||||||
|  |                                         ), | ||||||
|  |                                       ), | ||||||
|  |                                     ], | ||||||
|  |                                 onSelected: (value) { | ||||||
|  |                                   if (value == 'rotate') { | ||||||
|  |                                     rotateKey(apiKey.id); | ||||||
|  |                                   } else if (value == 'revoke') { | ||||||
|  |                                     revokeKey(apiKey.id); | ||||||
|  |                                   } | ||||||
|  |                                 }, | ||||||
|  |                               ), | ||||||
|  |                             ); | ||||||
|  |                           }, | ||||||
|  |                         ), | ||||||
|  |                       ), | ||||||
|  |             ), | ||||||
|  |           ], | ||||||
|  |         ); | ||||||
|  |       }, | ||||||
|  |       loading: () => const Center(child: CircularProgressIndicator()), | ||||||
|  |       error: | ||||||
|  |           (err, stack) => ResponseErrorWidget( | ||||||
|  |             error: err, | ||||||
|  |             onRetry: | ||||||
|  |                 () => ref.invalidate( | ||||||
|  |                   botKeysProvider(publisherName, projectId, botId), | ||||||
|  |                 ), | ||||||
|  |           ), | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  | } | ||||||
							
								
								
									
										172
									
								
								lib/screens/developers/bot_keys.g.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										172
									
								
								lib/screens/developers/bot_keys.g.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,172 @@ | |||||||
|  | // GENERATED CODE - DO NOT MODIFY BY HAND | ||||||
|  |  | ||||||
|  | part of 'bot_keys.dart'; | ||||||
|  |  | ||||||
|  | // ************************************************************************** | ||||||
|  | // RiverpodGenerator | ||||||
|  | // ************************************************************************** | ||||||
|  |  | ||||||
|  | String _$botKeysHash() => r'f7d1121833dc3da0cbd84b6171c2b2539edeb785'; | ||||||
|  |  | ||||||
|  | /// 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)); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /// See also [botKeys]. | ||||||
|  | @ProviderFor(botKeys) | ||||||
|  | const botKeysProvider = BotKeysFamily(); | ||||||
|  |  | ||||||
|  | /// See also [botKeys]. | ||||||
|  | class BotKeysFamily extends Family<AsyncValue<List<SnAccountApiKey>>> { | ||||||
|  |   /// See also [botKeys]. | ||||||
|  |   const BotKeysFamily(); | ||||||
|  |  | ||||||
|  |   /// See also [botKeys]. | ||||||
|  |   BotKeysProvider call(String publisherName, String projectId, String botId) { | ||||||
|  |     return BotKeysProvider(publisherName, projectId, botId); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   BotKeysProvider getProviderOverride(covariant BotKeysProvider provider) { | ||||||
|  |     return call(provider.publisherName, provider.projectId, provider.botId); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   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'botKeysProvider'; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /// See also [botKeys]. | ||||||
|  | class BotKeysProvider extends AutoDisposeFutureProvider<List<SnAccountApiKey>> { | ||||||
|  |   /// See also [botKeys]. | ||||||
|  |   BotKeysProvider(String publisherName, String projectId, String botId) | ||||||
|  |     : this._internal( | ||||||
|  |         (ref) => botKeys(ref as BotKeysRef, publisherName, projectId, botId), | ||||||
|  |         from: botKeysProvider, | ||||||
|  |         name: r'botKeysProvider', | ||||||
|  |         debugGetCreateSourceHash: | ||||||
|  |             const bool.fromEnvironment('dart.vm.product') | ||||||
|  |                 ? null | ||||||
|  |                 : _$botKeysHash, | ||||||
|  |         dependencies: BotKeysFamily._dependencies, | ||||||
|  |         allTransitiveDependencies: BotKeysFamily._allTransitiveDependencies, | ||||||
|  |         publisherName: publisherName, | ||||||
|  |         projectId: projectId, | ||||||
|  |         botId: botId, | ||||||
|  |       ); | ||||||
|  |  | ||||||
|  |   BotKeysProvider._internal( | ||||||
|  |     super._createNotifier, { | ||||||
|  |     required super.name, | ||||||
|  |     required super.dependencies, | ||||||
|  |     required super.allTransitiveDependencies, | ||||||
|  |     required super.debugGetCreateSourceHash, | ||||||
|  |     required super.from, | ||||||
|  |     required this.publisherName, | ||||||
|  |     required this.projectId, | ||||||
|  |     required this.botId, | ||||||
|  |   }) : super.internal(); | ||||||
|  |  | ||||||
|  |   final String publisherName; | ||||||
|  |   final String projectId; | ||||||
|  |   final String botId; | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   Override overrideWith( | ||||||
|  |     FutureOr<List<SnAccountApiKey>> Function(BotKeysRef provider) create, | ||||||
|  |   ) { | ||||||
|  |     return ProviderOverride( | ||||||
|  |       origin: this, | ||||||
|  |       override: BotKeysProvider._internal( | ||||||
|  |         (ref) => create(ref as BotKeysRef), | ||||||
|  |         from: from, | ||||||
|  |         name: null, | ||||||
|  |         dependencies: null, | ||||||
|  |         allTransitiveDependencies: null, | ||||||
|  |         debugGetCreateSourceHash: null, | ||||||
|  |         publisherName: publisherName, | ||||||
|  |         projectId: projectId, | ||||||
|  |         botId: botId, | ||||||
|  |       ), | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   AutoDisposeFutureProviderElement<List<SnAccountApiKey>> createElement() { | ||||||
|  |     return _BotKeysProviderElement(this); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   bool operator ==(Object other) { | ||||||
|  |     return other is BotKeysProvider && | ||||||
|  |         other.publisherName == publisherName && | ||||||
|  |         other.projectId == projectId && | ||||||
|  |         other.botId == botId; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   int get hashCode { | ||||||
|  |     var hash = _SystemHash.combine(0, runtimeType.hashCode); | ||||||
|  |     hash = _SystemHash.combine(hash, publisherName.hashCode); | ||||||
|  |     hash = _SystemHash.combine(hash, projectId.hashCode); | ||||||
|  |     hash = _SystemHash.combine(hash, botId.hashCode); | ||||||
|  |  | ||||||
|  |     return _SystemHash.finish(hash); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @Deprecated('Will be removed in 3.0. Use Ref instead') | ||||||
|  | // ignore: unused_element | ||||||
|  | mixin BotKeysRef on AutoDisposeFutureProviderRef<List<SnAccountApiKey>> { | ||||||
|  |   /// The parameter `publisherName` of this provider. | ||||||
|  |   String get publisherName; | ||||||
|  |  | ||||||
|  |   /// The parameter `projectId` of this provider. | ||||||
|  |   String get projectId; | ||||||
|  |  | ||||||
|  |   /// The parameter `botId` of this provider. | ||||||
|  |   String get botId; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | class _BotKeysProviderElement | ||||||
|  |     extends AutoDisposeFutureProviderElement<List<SnAccountApiKey>> | ||||||
|  |     with BotKeysRef { | ||||||
|  |   _BotKeysProviderElement(super.provider); | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   String get publisherName => (origin as BotKeysProvider).publisherName; | ||||||
|  |   @override | ||||||
|  |   String get projectId => (origin as BotKeysProvider).projectId; | ||||||
|  |   @override | ||||||
|  |   String get botId => (origin as BotKeysProvider).botId; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // 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 | ||||||
							
								
								
									
										167
									
								
								lib/screens/developers/bots.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										167
									
								
								lib/screens/developers/bots.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,167 @@ | |||||||
|  | import 'package:flutter/material.dart'; | ||||||
|  | import 'package:easy_localization/easy_localization.dart'; | ||||||
|  | import 'package:go_router/go_router.dart'; | ||||||
|  | import 'package:hooks_riverpod/hooks_riverpod.dart'; | ||||||
|  | import 'package:island/models/bot.dart'; | ||||||
|  | import 'package:island/pods/network.dart'; | ||||||
|  | import 'package:island/widgets/alert.dart'; | ||||||
|  | import 'package:island/widgets/content/cloud_files.dart'; | ||||||
|  | import 'package:island/widgets/response.dart'; | ||||||
|  | import 'package:material_symbols_icons/symbols.dart'; | ||||||
|  | import 'package:riverpod_annotation/riverpod_annotation.dart'; | ||||||
|  |  | ||||||
|  | part 'bots.g.dart'; | ||||||
|  |  | ||||||
|  | @riverpod | ||||||
|  | Future<List<Bot>> bots(Ref ref, String publisherName, String projectId) async { | ||||||
|  |   final client = ref.watch(apiClientProvider); | ||||||
|  |   final resp = await client.get( | ||||||
|  |     '/develop/developers/$publisherName/projects/$projectId/bots', | ||||||
|  |   ); | ||||||
|  |   return (resp.data as List).map((e) => Bot.fromJson(e)).cast<Bot>().toList(); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | class BotsScreen extends HookConsumerWidget { | ||||||
|  |   final String publisherName; | ||||||
|  |   final String projectId; | ||||||
|  |   const BotsScreen({ | ||||||
|  |     super.key, | ||||||
|  |     required this.publisherName, | ||||||
|  |     required this.projectId, | ||||||
|  |   }); | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   Widget build(BuildContext context, WidgetRef ref) { | ||||||
|  |     final botsList = ref.watch(botsProvider(publisherName, projectId)); | ||||||
|  |  | ||||||
|  |     return botsList.when( | ||||||
|  |       data: (data) { | ||||||
|  |         if (data.isEmpty) { | ||||||
|  |           return Center( | ||||||
|  |             child: Column( | ||||||
|  |               mainAxisAlignment: MainAxisAlignment.center, | ||||||
|  |               children: [ | ||||||
|  |                 Text('noBots').tr(), | ||||||
|  |                 const SizedBox(height: 16), | ||||||
|  |                 ElevatedButton.icon( | ||||||
|  |                   onPressed: () { | ||||||
|  |                     context.pushNamed( | ||||||
|  |                       'developerBotNew', | ||||||
|  |                       pathParameters: { | ||||||
|  |                         'name': publisherName, | ||||||
|  |                         'projectId': projectId, | ||||||
|  |                       }, | ||||||
|  |                     ); | ||||||
|  |                   }, | ||||||
|  |                   icon: const Icon(Symbols.add), | ||||||
|  |                   label: Text('createBot').tr(), | ||||||
|  |                 ), | ||||||
|  |               ], | ||||||
|  |             ), | ||||||
|  |           ); | ||||||
|  |         } | ||||||
|  |         return RefreshIndicator( | ||||||
|  |           onRefresh: | ||||||
|  |               () => ref.refresh(botsProvider(publisherName, projectId).future), | ||||||
|  |           child: ListView.builder( | ||||||
|  |             padding: const EdgeInsets.only(top: 4), | ||||||
|  |             itemCount: data.length, | ||||||
|  |             itemBuilder: (context, index) { | ||||||
|  |               final bot = data[index]; | ||||||
|  |               return Card( | ||||||
|  |                 margin: const EdgeInsets.all(8.0), | ||||||
|  |                 child: ListTile( | ||||||
|  |                   shape: const RoundedRectangleBorder( | ||||||
|  |                     borderRadius: BorderRadius.all(Radius.circular(8.0)), | ||||||
|  |                   ), | ||||||
|  |                   leading: CircleAvatar( | ||||||
|  |                     child: | ||||||
|  |                         bot.account.profile.picture != null | ||||||
|  |                             ? ProfilePictureWidget( | ||||||
|  |                               file: bot.account.profile.picture!, | ||||||
|  |                             ) | ||||||
|  |                             : const Icon(Symbols.smart_toy), | ||||||
|  |                   ), | ||||||
|  |                   title: Text(bot.account.nick), | ||||||
|  |                   subtitle: Text(bot.account.name), | ||||||
|  |                   trailing: PopupMenuButton( | ||||||
|  |                     itemBuilder: | ||||||
|  |                         (context) => [ | ||||||
|  |                           PopupMenuItem( | ||||||
|  |                             value: 'edit', | ||||||
|  |                             child: Row( | ||||||
|  |                               children: [ | ||||||
|  |                                 const Icon(Symbols.edit), | ||||||
|  |                                 const SizedBox(width: 12), | ||||||
|  |                                 Text('edit').tr(), | ||||||
|  |                               ], | ||||||
|  |                             ), | ||||||
|  |                           ), | ||||||
|  |                           PopupMenuItem( | ||||||
|  |                             value: 'delete', | ||||||
|  |                             child: Row( | ||||||
|  |                               children: [ | ||||||
|  |                                 const Icon(Symbols.delete, color: Colors.red), | ||||||
|  |                                 const SizedBox(width: 12), | ||||||
|  |                                 Text( | ||||||
|  |                                   'delete', | ||||||
|  |                                   style: TextStyle(color: Colors.red), | ||||||
|  |                                 ).tr(), | ||||||
|  |                               ], | ||||||
|  |                             ), | ||||||
|  |                           ), | ||||||
|  |                         ], | ||||||
|  |                     onSelected: (value) { | ||||||
|  |                       if (value == 'edit') { | ||||||
|  |                         context.pushNamed( | ||||||
|  |                           'developerBotEdit', | ||||||
|  |                           pathParameters: { | ||||||
|  |                             'name': publisherName, | ||||||
|  |                             'projectId': projectId, | ||||||
|  |                             'id': bot.id, | ||||||
|  |                           }, | ||||||
|  |                         ); | ||||||
|  |                       } else if (value == 'delete') { | ||||||
|  |                         showConfirmAlert( | ||||||
|  |                           'deleteBotHint'.tr(), | ||||||
|  |                           'deleteBot'.tr(), | ||||||
|  |                         ).then((confirm) { | ||||||
|  |                           if (confirm) { | ||||||
|  |                             final client = ref.read(apiClientProvider); | ||||||
|  |                             client.delete( | ||||||
|  |                               '/develop/developers/$publisherName/projects/$projectId/bots/${bot.id}', | ||||||
|  |                             ); | ||||||
|  |                             ref.invalidate( | ||||||
|  |                               botsProvider(publisherName, projectId), | ||||||
|  |                             ); | ||||||
|  |                           } | ||||||
|  |                         }); | ||||||
|  |                       } | ||||||
|  |                     }, | ||||||
|  |                   ), | ||||||
|  |                   onTap: () { | ||||||
|  |                     context.pushNamed( | ||||||
|  |                       'developerBotDetail', | ||||||
|  |                       pathParameters: { | ||||||
|  |                         'name': publisherName, | ||||||
|  |                         'projectId': projectId, | ||||||
|  |                         'botId': bot.id, | ||||||
|  |                       }, | ||||||
|  |                     ); | ||||||
|  |                   }, | ||||||
|  |                 ), | ||||||
|  |               ); | ||||||
|  |             }, | ||||||
|  |           ), | ||||||
|  |         ); | ||||||
|  |       }, | ||||||
|  |       loading: () => const Center(child: CircularProgressIndicator()), | ||||||
|  |       error: | ||||||
|  |           (err, stack) => ResponseErrorWidget( | ||||||
|  |             error: err, | ||||||
|  |             onRetry: | ||||||
|  |                 () => ref.invalidate(botsProvider(publisherName, projectId)), | ||||||
|  |           ), | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  | } | ||||||
							
								
								
									
										156
									
								
								lib/screens/developers/bots.g.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										156
									
								
								lib/screens/developers/bots.g.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,156 @@ | |||||||
|  | // GENERATED CODE - DO NOT MODIFY BY HAND | ||||||
|  |  | ||||||
|  | part of 'bots.dart'; | ||||||
|  |  | ||||||
|  | // ************************************************************************** | ||||||
|  | // RiverpodGenerator | ||||||
|  | // ************************************************************************** | ||||||
|  |  | ||||||
|  | String _$botsHash() => r'15cefd5781350eb68208a342e85fcb0b9e0e3269'; | ||||||
|  |  | ||||||
|  | /// 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)); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /// See also [bots]. | ||||||
|  | @ProviderFor(bots) | ||||||
|  | const botsProvider = BotsFamily(); | ||||||
|  |  | ||||||
|  | /// See also [bots]. | ||||||
|  | class BotsFamily extends Family<AsyncValue<List<Bot>>> { | ||||||
|  |   /// See also [bots]. | ||||||
|  |   const BotsFamily(); | ||||||
|  |  | ||||||
|  |   /// See also [bots]. | ||||||
|  |   BotsProvider call(String publisherName, String projectId) { | ||||||
|  |     return BotsProvider(publisherName, projectId); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   BotsProvider getProviderOverride(covariant BotsProvider provider) { | ||||||
|  |     return call(provider.publisherName, provider.projectId); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   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'botsProvider'; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /// See also [bots]. | ||||||
|  | class BotsProvider extends AutoDisposeFutureProvider<List<Bot>> { | ||||||
|  |   /// See also [bots]. | ||||||
|  |   BotsProvider(String publisherName, String projectId) | ||||||
|  |     : this._internal( | ||||||
|  |         (ref) => bots(ref as BotsRef, publisherName, projectId), | ||||||
|  |         from: botsProvider, | ||||||
|  |         name: r'botsProvider', | ||||||
|  |         debugGetCreateSourceHash: | ||||||
|  |             const bool.fromEnvironment('dart.vm.product') ? null : _$botsHash, | ||||||
|  |         dependencies: BotsFamily._dependencies, | ||||||
|  |         allTransitiveDependencies: BotsFamily._allTransitiveDependencies, | ||||||
|  |         publisherName: publisherName, | ||||||
|  |         projectId: projectId, | ||||||
|  |       ); | ||||||
|  |  | ||||||
|  |   BotsProvider._internal( | ||||||
|  |     super._createNotifier, { | ||||||
|  |     required super.name, | ||||||
|  |     required super.dependencies, | ||||||
|  |     required super.allTransitiveDependencies, | ||||||
|  |     required super.debugGetCreateSourceHash, | ||||||
|  |     required super.from, | ||||||
|  |     required this.publisherName, | ||||||
|  |     required this.projectId, | ||||||
|  |   }) : super.internal(); | ||||||
|  |  | ||||||
|  |   final String publisherName; | ||||||
|  |   final String projectId; | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   Override overrideWith(FutureOr<List<Bot>> Function(BotsRef provider) create) { | ||||||
|  |     return ProviderOverride( | ||||||
|  |       origin: this, | ||||||
|  |       override: BotsProvider._internal( | ||||||
|  |         (ref) => create(ref as BotsRef), | ||||||
|  |         from: from, | ||||||
|  |         name: null, | ||||||
|  |         dependencies: null, | ||||||
|  |         allTransitiveDependencies: null, | ||||||
|  |         debugGetCreateSourceHash: null, | ||||||
|  |         publisherName: publisherName, | ||||||
|  |         projectId: projectId, | ||||||
|  |       ), | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   AutoDisposeFutureProviderElement<List<Bot>> createElement() { | ||||||
|  |     return _BotsProviderElement(this); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   bool operator ==(Object other) { | ||||||
|  |     return other is BotsProvider && | ||||||
|  |         other.publisherName == publisherName && | ||||||
|  |         other.projectId == projectId; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   int get hashCode { | ||||||
|  |     var hash = _SystemHash.combine(0, runtimeType.hashCode); | ||||||
|  |     hash = _SystemHash.combine(hash, publisherName.hashCode); | ||||||
|  |     hash = _SystemHash.combine(hash, projectId.hashCode); | ||||||
|  |  | ||||||
|  |     return _SystemHash.finish(hash); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @Deprecated('Will be removed in 3.0. Use Ref instead') | ||||||
|  | // ignore: unused_element | ||||||
|  | mixin BotsRef on AutoDisposeFutureProviderRef<List<Bot>> { | ||||||
|  |   /// The parameter `publisherName` of this provider. | ||||||
|  |   String get publisherName; | ||||||
|  |  | ||||||
|  |   /// The parameter `projectId` of this provider. | ||||||
|  |   String get projectId; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | class _BotsProviderElement extends AutoDisposeFutureProviderElement<List<Bot>> | ||||||
|  |     with BotsRef { | ||||||
|  |   _BotsProviderElement(super.provider); | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   String get publisherName => (origin as BotsProvider).publisherName; | ||||||
|  |   @override | ||||||
|  |   String get projectId => (origin as BotsProvider).projectId; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // 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 | ||||||
| @@ -22,21 +22,37 @@ import 'package:island/widgets/content/sheet.dart'; | |||||||
| part 'edit_app.g.dart'; | part 'edit_app.g.dart'; | ||||||
|  |  | ||||||
| @riverpod | @riverpod | ||||||
| Future<CustomApp?> customApp(Ref ref, String publisherName, String id) async { | Future<CustomApp?> customApp( | ||||||
|  |   Ref ref, | ||||||
|  |   String publisherName, | ||||||
|  |   String projectId, | ||||||
|  |   String id, | ||||||
|  | ) async { | ||||||
|   final client = ref.watch(apiClientProvider); |   final client = ref.watch(apiClientProvider); | ||||||
|   final resp = await client.get('/develop/developers/$publisherName/apps/$id'); |   final resp = await client.get( | ||||||
|  |     '/develop/developers/$publisherName/projects/$projectId/apps/$id', | ||||||
|  |   ); | ||||||
|   return CustomApp.fromJson(resp.data); |   return CustomApp.fromJson(resp.data); | ||||||
| } | } | ||||||
|  |  | ||||||
| class EditAppScreen extends HookConsumerWidget { | class EditAppScreen extends HookConsumerWidget { | ||||||
|   final String publisherName; |   final String publisherName; | ||||||
|  |   final String projectId; | ||||||
|   final String? id; |   final String? id; | ||||||
|   const EditAppScreen({super.key, required this.publisherName, this.id}); |   const EditAppScreen({ | ||||||
|  |     super.key, | ||||||
|  |     required this.publisherName, | ||||||
|  |     required this.projectId, | ||||||
|  |     this.id, | ||||||
|  |   }); | ||||||
|  |  | ||||||
|   @override |   @override | ||||||
|   Widget build(BuildContext context, WidgetRef ref) { |   Widget build(BuildContext context, WidgetRef ref) { | ||||||
|     final isNew = id == null; |     final isNew = id == null; | ||||||
|     final app = isNew ? null : ref.watch(customAppProvider(publisherName, id!)); |     final app = | ||||||
|  |         isNew | ||||||
|  |             ? null | ||||||
|  |             : ref.watch(customAppProvider(publisherName, projectId, id!)); | ||||||
|  |  | ||||||
|     final formKey = useMemoized(() => GlobalKey<FormState>()); |     final formKey = useMemoized(() => GlobalKey<FormState>()); | ||||||
|  |  | ||||||
| @@ -281,18 +297,26 @@ class EditAppScreen extends HookConsumerWidget { | |||||||
|                 } |                 } | ||||||
|                 : null, |                 : null, | ||||||
|       }; |       }; | ||||||
|       if (isNew) { |       try { | ||||||
|         await client.post( |         showLoadingModal(context); | ||||||
|           '/develop/developers/$publisherName/apps', |         if (isNew) { | ||||||
|           data: data, |           await client.post( | ||||||
|         ); |             '/develop/developers/$publisherName/projects/$projectId/apps', | ||||||
|       } else { |             data: data, | ||||||
|         await client.patch( |           ); | ||||||
|           '/develop/developers/$publisherName/apps/$id', |         } else { | ||||||
|           data: data, |           await client.patch( | ||||||
|         ); |             '/develop/developers/$publisherName/projects/$projectId/apps/$id', | ||||||
|  |             data: data, | ||||||
|  |           ); | ||||||
|  |         } | ||||||
|  |       } catch (err) { | ||||||
|  |         showErrorAlert(err); | ||||||
|  |         return; | ||||||
|  |       } finally { | ||||||
|  |         if (context.mounted) hideLoadingModal(context); | ||||||
|       } |       } | ||||||
|       ref.invalidate(customAppsProvider(publisherName)); |       ref.invalidate(customAppsProvider(publisherName, projectId)); | ||||||
|       if (context.mounted) { |       if (context.mounted) { | ||||||
|         Navigator.pop(context); |         Navigator.pop(context); | ||||||
|       } |       } | ||||||
| @@ -309,7 +333,9 @@ class EditAppScreen extends HookConsumerWidget { | |||||||
|               ? ResponseErrorWidget( |               ? ResponseErrorWidget( | ||||||
|                 error: app!.error, |                 error: app!.error, | ||||||
|                 onRetry: |                 onRetry: | ||||||
|                     () => ref.invalidate(customAppProvider(publisherName, id!)), |                     () => ref.invalidate( | ||||||
|  |                       customAppProvider(publisherName, projectId, id!), | ||||||
|  |                     ), | ||||||
|               ) |               ) | ||||||
|               : SingleChildScrollView( |               : SingleChildScrollView( | ||||||
|                 child: Column( |                 child: Column( | ||||||
|   | |||||||
| @@ -6,7 +6,7 @@ part of 'edit_app.dart'; | |||||||
| // RiverpodGenerator | // RiverpodGenerator | ||||||
| // ************************************************************************** | // ************************************************************************** | ||||||
|  |  | ||||||
| String _$customAppHash() => r'42ad937b8439c793e3c5c35568bb5fa4da017df3'; | String _$customAppHash() => r'8e1b38f3dc9b04fad362ee1141fcbfc53f008c09'; | ||||||
|  |  | ||||||
| /// Copied from Dart SDK | /// Copied from Dart SDK | ||||||
| class _SystemHash { | class _SystemHash { | ||||||
| @@ -39,13 +39,13 @@ class CustomAppFamily extends Family<AsyncValue<CustomApp?>> { | |||||||
|   const CustomAppFamily(); |   const CustomAppFamily(); | ||||||
|  |  | ||||||
|   /// See also [customApp]. |   /// See also [customApp]. | ||||||
|   CustomAppProvider call(String publisherName, String id) { |   CustomAppProvider call(String publisherName, String projectId, String id) { | ||||||
|     return CustomAppProvider(publisherName, id); |     return CustomAppProvider(publisherName, projectId, id); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   @override |   @override | ||||||
|   CustomAppProvider getProviderOverride(covariant CustomAppProvider provider) { |   CustomAppProvider getProviderOverride(covariant CustomAppProvider provider) { | ||||||
|     return call(provider.publisherName, provider.id); |     return call(provider.publisherName, provider.projectId, provider.id); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   static const Iterable<ProviderOrFamily>? _dependencies = null; |   static const Iterable<ProviderOrFamily>? _dependencies = null; | ||||||
| @@ -66,9 +66,9 @@ class CustomAppFamily extends Family<AsyncValue<CustomApp?>> { | |||||||
| /// See also [customApp]. | /// See also [customApp]. | ||||||
| class CustomAppProvider extends AutoDisposeFutureProvider<CustomApp?> { | class CustomAppProvider extends AutoDisposeFutureProvider<CustomApp?> { | ||||||
|   /// See also [customApp]. |   /// See also [customApp]. | ||||||
|   CustomAppProvider(String publisherName, String id) |   CustomAppProvider(String publisherName, String projectId, String id) | ||||||
|     : this._internal( |     : this._internal( | ||||||
|         (ref) => customApp(ref as CustomAppRef, publisherName, id), |         (ref) => customApp(ref as CustomAppRef, publisherName, projectId, id), | ||||||
|         from: customAppProvider, |         from: customAppProvider, | ||||||
|         name: r'customAppProvider', |         name: r'customAppProvider', | ||||||
|         debugGetCreateSourceHash: |         debugGetCreateSourceHash: | ||||||
| @@ -78,6 +78,7 @@ class CustomAppProvider extends AutoDisposeFutureProvider<CustomApp?> { | |||||||
|         dependencies: CustomAppFamily._dependencies, |         dependencies: CustomAppFamily._dependencies, | ||||||
|         allTransitiveDependencies: CustomAppFamily._allTransitiveDependencies, |         allTransitiveDependencies: CustomAppFamily._allTransitiveDependencies, | ||||||
|         publisherName: publisherName, |         publisherName: publisherName, | ||||||
|  |         projectId: projectId, | ||||||
|         id: id, |         id: id, | ||||||
|       ); |       ); | ||||||
|  |  | ||||||
| @@ -89,10 +90,12 @@ class CustomAppProvider extends AutoDisposeFutureProvider<CustomApp?> { | |||||||
|     required super.debugGetCreateSourceHash, |     required super.debugGetCreateSourceHash, | ||||||
|     required super.from, |     required super.from, | ||||||
|     required this.publisherName, |     required this.publisherName, | ||||||
|  |     required this.projectId, | ||||||
|     required this.id, |     required this.id, | ||||||
|   }) : super.internal(); |   }) : super.internal(); | ||||||
|  |  | ||||||
|   final String publisherName; |   final String publisherName; | ||||||
|  |   final String projectId; | ||||||
|   final String id; |   final String id; | ||||||
|  |  | ||||||
|   @override |   @override | ||||||
| @@ -109,6 +112,7 @@ class CustomAppProvider extends AutoDisposeFutureProvider<CustomApp?> { | |||||||
|         allTransitiveDependencies: null, |         allTransitiveDependencies: null, | ||||||
|         debugGetCreateSourceHash: null, |         debugGetCreateSourceHash: null, | ||||||
|         publisherName: publisherName, |         publisherName: publisherName, | ||||||
|  |         projectId: projectId, | ||||||
|         id: id, |         id: id, | ||||||
|       ), |       ), | ||||||
|     ); |     ); | ||||||
| @@ -123,6 +127,7 @@ class CustomAppProvider extends AutoDisposeFutureProvider<CustomApp?> { | |||||||
|   bool operator ==(Object other) { |   bool operator ==(Object other) { | ||||||
|     return other is CustomAppProvider && |     return other is CustomAppProvider && | ||||||
|         other.publisherName == publisherName && |         other.publisherName == publisherName && | ||||||
|  |         other.projectId == projectId && | ||||||
|         other.id == id; |         other.id == id; | ||||||
|   } |   } | ||||||
|  |  | ||||||
| @@ -130,6 +135,7 @@ class CustomAppProvider extends AutoDisposeFutureProvider<CustomApp?> { | |||||||
|   int get hashCode { |   int get hashCode { | ||||||
|     var hash = _SystemHash.combine(0, runtimeType.hashCode); |     var hash = _SystemHash.combine(0, runtimeType.hashCode); | ||||||
|     hash = _SystemHash.combine(hash, publisherName.hashCode); |     hash = _SystemHash.combine(hash, publisherName.hashCode); | ||||||
|  |     hash = _SystemHash.combine(hash, projectId.hashCode); | ||||||
|     hash = _SystemHash.combine(hash, id.hashCode); |     hash = _SystemHash.combine(hash, id.hashCode); | ||||||
|  |  | ||||||
|     return _SystemHash.finish(hash); |     return _SystemHash.finish(hash); | ||||||
| @@ -142,6 +148,9 @@ mixin CustomAppRef on AutoDisposeFutureProviderRef<CustomApp?> { | |||||||
|   /// The parameter `publisherName` of this provider. |   /// The parameter `publisherName` of this provider. | ||||||
|   String get publisherName; |   String get publisherName; | ||||||
|  |  | ||||||
|  |   /// The parameter `projectId` of this provider. | ||||||
|  |   String get projectId; | ||||||
|  |  | ||||||
|   /// The parameter `id` of this provider. |   /// The parameter `id` of this provider. | ||||||
|   String get id; |   String get id; | ||||||
| } | } | ||||||
| @@ -154,6 +163,8 @@ class _CustomAppProviderElement | |||||||
|   @override |   @override | ||||||
|   String get publisherName => (origin as CustomAppProvider).publisherName; |   String get publisherName => (origin as CustomAppProvider).publisherName; | ||||||
|   @override |   @override | ||||||
|  |   String get projectId => (origin as CustomAppProvider).projectId; | ||||||
|  |   @override | ||||||
|   String get id => (origin as CustomAppProvider).id; |   String get id => (origin as CustomAppProvider).id; | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
							
								
								
									
										425
									
								
								lib/screens/developers/edit_bot.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										425
									
								
								lib/screens/developers/edit_bot.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,425 @@ | |||||||
|  | import 'package:croppy/croppy.dart' hide cropImage; | ||||||
|  | import 'package:flutter/material.dart'; | ||||||
|  | import 'package:easy_localization/easy_localization.dart'; | ||||||
|  | import 'package:flutter_hooks/flutter_hooks.dart'; | ||||||
|  | import 'package:go_router/go_router.dart'; | ||||||
|  | import 'package:hooks_riverpod/hooks_riverpod.dart'; | ||||||
|  | import 'package:image_picker/image_picker.dart'; | ||||||
|  | import 'package:island/models/bot.dart'; | ||||||
|  | import 'package:island/models/file.dart'; | ||||||
|  | import 'package:island/pods/config.dart'; | ||||||
|  | import 'package:island/pods/network.dart'; | ||||||
|  | import 'package:island/services/file.dart'; | ||||||
|  | import 'package:island/widgets/alert.dart'; | ||||||
|  | import 'package:island/widgets/app_scaffold.dart'; | ||||||
|  | import 'package:island/widgets/content/cloud_files.dart'; | ||||||
|  | import 'package:island/widgets/response.dart'; | ||||||
|  | import 'package:material_symbols_icons/symbols.dart'; | ||||||
|  | import 'package:riverpod_annotation/riverpod_annotation.dart'; | ||||||
|  | import 'package:styled_widget/styled_widget.dart'; | ||||||
|  |  | ||||||
|  | part 'edit_bot.g.dart'; | ||||||
|  |  | ||||||
|  | @riverpod | ||||||
|  | Future<Bot?> bot( | ||||||
|  |   Ref ref, | ||||||
|  |   String publisherName, | ||||||
|  |   String projectId, | ||||||
|  |   String id, | ||||||
|  | ) async { | ||||||
|  |   final client = ref.watch(apiClientProvider); | ||||||
|  |   final resp = await client.get( | ||||||
|  |     '/develop/developers/$publisherName/projects/$projectId/bots/$id', | ||||||
|  |   ); | ||||||
|  |   return Bot.fromJson(resp.data); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | class EditBotScreen extends HookConsumerWidget { | ||||||
|  |   final String publisherName; | ||||||
|  |   final String projectId; | ||||||
|  |   final String? id; | ||||||
|  |   const EditBotScreen({ | ||||||
|  |     super.key, | ||||||
|  |     required this.publisherName, | ||||||
|  |     required this.projectId, | ||||||
|  |     this.id, | ||||||
|  |   }); | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   Widget build(BuildContext context, WidgetRef ref) { | ||||||
|  |     final isNew = id == null; | ||||||
|  |     final botData = | ||||||
|  |         isNew ? null : ref.watch(botProvider(publisherName, projectId, id!)); | ||||||
|  |  | ||||||
|  |     final formKey = useMemoized(() => GlobalKey<FormState>()); | ||||||
|  |     final submitting = useState(false); | ||||||
|  |  | ||||||
|  |     final nameController = useTextEditingController(); | ||||||
|  |     final nickController = useTextEditingController(); | ||||||
|  |     final slugController = useTextEditingController(); | ||||||
|  |     final picture = useState<SnCloudFile?>(null); | ||||||
|  |  | ||||||
|  |     final firstNameController = useTextEditingController(); | ||||||
|  |     final middleNameController = useTextEditingController(); | ||||||
|  |     final lastNameController = useTextEditingController(); | ||||||
|  |     final genderController = useTextEditingController(); | ||||||
|  |     final pronounsController = useTextEditingController(); | ||||||
|  |     final locationController = useTextEditingController(); | ||||||
|  |     final timeZoneController = useTextEditingController(); | ||||||
|  |     final bioController = useTextEditingController(); | ||||||
|  |     final birthday = useState<DateTime?>(null); | ||||||
|  |     final background = useState<SnCloudFile?>(null); | ||||||
|  |  | ||||||
|  |     useEffect(() { | ||||||
|  |       if (botData?.value != null) { | ||||||
|  |         nameController.text = botData!.value!.account.name; | ||||||
|  |         nickController.text = botData.value!.account.nick; | ||||||
|  |         slugController.text = botData.value!.slug; | ||||||
|  |         picture.value = botData.value!.account.profile.picture; | ||||||
|  |         background.value = botData.value!.account.profile.background; | ||||||
|  |  | ||||||
|  |         // Populate from botData.value.account.profile | ||||||
|  |         firstNameController.text = botData.value!.account.profile.firstName; | ||||||
|  |         middleNameController.text = botData.value!.account.profile.middleName; | ||||||
|  |         lastNameController.text = botData.value!.account.profile.lastName; | ||||||
|  |         genderController.text = botData.value!.account.profile.gender; | ||||||
|  |         pronounsController.text = botData.value!.account.profile.pronouns; | ||||||
|  |         locationController.text = botData.value!.account.profile.location; | ||||||
|  |         timeZoneController.text = botData.value!.account.profile.timeZone; | ||||||
|  |         bioController.text = botData.value!.account.profile.bio; | ||||||
|  |         birthday.value = botData.value!.account.profile.birthday?.toLocal(); | ||||||
|  |       } | ||||||
|  |       return null; | ||||||
|  |     }, [botData]); | ||||||
|  |  | ||||||
|  |     void setPicture(String position) async { | ||||||
|  |       showLoadingModal(context); | ||||||
|  |       var result = await ref | ||||||
|  |           .read(imagePickerProvider) | ||||||
|  |           .pickImage(source: ImageSource.gallery); | ||||||
|  |       if (result == null) { | ||||||
|  |         if (context.mounted) hideLoadingModal(context); | ||||||
|  |         return; | ||||||
|  |       } | ||||||
|  |       if (!context.mounted) return; | ||||||
|  |       hideLoadingModal(context); | ||||||
|  |  | ||||||
|  |       result = await cropImage( | ||||||
|  |         context, | ||||||
|  |         image: result, | ||||||
|  |         allowedAspectRatios: [ | ||||||
|  |           if (position == 'background') | ||||||
|  |             const CropAspectRatio(height: 7, width: 16) | ||||||
|  |           else | ||||||
|  |             const CropAspectRatio(height: 1, width: 1), | ||||||
|  |         ], | ||||||
|  |       ); | ||||||
|  |       if (result == null) { | ||||||
|  |         if (context.mounted) hideLoadingModal(context); | ||||||
|  |         return; | ||||||
|  |       } | ||||||
|  |       if (!context.mounted) return; | ||||||
|  |       showLoadingModal(context); | ||||||
|  |  | ||||||
|  |       submitting.value = true; | ||||||
|  |       try { | ||||||
|  |         final baseUrl = ref.watch(serverUrlProvider); | ||||||
|  |         final token = await getToken(ref.watch(tokenProvider)); | ||||||
|  |         if (token == null) throw ArgumentError('Token is null'); | ||||||
|  |         final cloudFile = | ||||||
|  |             await putMediaToCloud( | ||||||
|  |               fileData: UniversalFile( | ||||||
|  |                 data: result, | ||||||
|  |                 type: UniversalFileType.image, | ||||||
|  |               ), | ||||||
|  |               atk: token, | ||||||
|  |               baseUrl: baseUrl, | ||||||
|  |               filename: result.name, | ||||||
|  |               mimetype: result.mimeType ?? 'image/jpeg', | ||||||
|  |             ).future; | ||||||
|  |         if (cloudFile == null) { | ||||||
|  |           throw ArgumentError('Failed to upload the file...'); | ||||||
|  |         } | ||||||
|  |         switch (position) { | ||||||
|  |           case 'picture': | ||||||
|  |             picture.value = cloudFile; | ||||||
|  |           case 'background': | ||||||
|  |             background.value = cloudFile; | ||||||
|  |         } | ||||||
|  |       } catch (err) { | ||||||
|  |         showErrorAlert(err); | ||||||
|  |       } finally { | ||||||
|  |         if (context.mounted) hideLoadingModal(context); | ||||||
|  |         submitting.value = false; | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     void performAction() async { | ||||||
|  |       final client = ref.read(apiClientProvider); | ||||||
|  |       final data = { | ||||||
|  |         'name': nameController.text, | ||||||
|  |         'nick': nickController.text, | ||||||
|  |         'slug': slugController.text, | ||||||
|  |         'picture_id': picture.value?.id, | ||||||
|  |         'background_id': background.value?.id, | ||||||
|  |         'first_name': firstNameController.text, | ||||||
|  |         'middle_name': middleNameController.text, | ||||||
|  |         'last_name': lastNameController.text, | ||||||
|  |         'gender': genderController.text, | ||||||
|  |         'pronouns': pronounsController.text, | ||||||
|  |         'location': locationController.text, | ||||||
|  |         'time_zone': timeZoneController.text, | ||||||
|  |         'bio': bioController.text, | ||||||
|  |         'birthday': birthday.value?.toUtc().toIso8601String(), | ||||||
|  |       }; | ||||||
|  |  | ||||||
|  |       try { | ||||||
|  |         showLoadingModal(context); | ||||||
|  |         if (isNew) { | ||||||
|  |           await client.post( | ||||||
|  |             '/develop/developers/$publisherName/projects/$projectId/bots', | ||||||
|  |             data: data, | ||||||
|  |           ); | ||||||
|  |         } else { | ||||||
|  |           await client.patch( | ||||||
|  |             '/develop/developers/$publisherName/projects/$projectId/bots/$id', | ||||||
|  |             data: data, | ||||||
|  |           ); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         if (context.mounted) { | ||||||
|  |           context.pop(); | ||||||
|  |         } | ||||||
|  |       } catch (err) { | ||||||
|  |         showErrorAlert(err); | ||||||
|  |       } finally { | ||||||
|  |         if (context.mounted) hideLoadingModal(context); | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     return AppScaffold( | ||||||
|  |       appBar: AppBar(title: Text(isNew ? 'createBot'.tr() : 'editBot'.tr())), | ||||||
|  |       body: | ||||||
|  |           botData == null && !isNew | ||||||
|  |               ? const Center(child: CircularProgressIndicator()) | ||||||
|  |               : botData?.hasError == true && !isNew | ||||||
|  |               ? ResponseErrorWidget( | ||||||
|  |                 error: botData!.error, | ||||||
|  |                 onRetry: | ||||||
|  |                     () => ref.invalidate( | ||||||
|  |                       botProvider(publisherName, projectId, id!), | ||||||
|  |                     ), | ||||||
|  |               ) | ||||||
|  |               : SingleChildScrollView( | ||||||
|  |                 child: Column( | ||||||
|  |                   children: [ | ||||||
|  |                     AspectRatio( | ||||||
|  |                       aspectRatio: 16 / 7, | ||||||
|  |                       child: Stack( | ||||||
|  |                         clipBehavior: Clip.none, | ||||||
|  |                         fit: StackFit.expand, | ||||||
|  |                         children: [ | ||||||
|  |                           GestureDetector( | ||||||
|  |                             child: Container( | ||||||
|  |                               color: | ||||||
|  |                                   Theme.of( | ||||||
|  |                                     context, | ||||||
|  |                                   ).colorScheme.surfaceContainerHigh, | ||||||
|  |                               child: | ||||||
|  |                                   background.value != null | ||||||
|  |                                       ? CloudFileWidget( | ||||||
|  |                                         item: background.value!, | ||||||
|  |                                         fit: BoxFit.cover, | ||||||
|  |                                       ) | ||||||
|  |                                       : const SizedBox.shrink(), | ||||||
|  |                             ), | ||||||
|  |                             onTap: () { | ||||||
|  |                               setPicture('background'); | ||||||
|  |                             }, | ||||||
|  |                           ), | ||||||
|  |                           Positioned( | ||||||
|  |                             left: 20, | ||||||
|  |                             bottom: -32, | ||||||
|  |                             child: GestureDetector( | ||||||
|  |                               child: ProfilePictureWidget( | ||||||
|  |                                 fileId: picture.value?.id, | ||||||
|  |                                 radius: 40, | ||||||
|  |                                 fallbackIcon: Symbols.smart_toy, | ||||||
|  |                               ), | ||||||
|  |                               onTap: () { | ||||||
|  |                                 setPicture('picture'); | ||||||
|  |                               }, | ||||||
|  |                             ), | ||||||
|  |                           ), | ||||||
|  |                         ], | ||||||
|  |                       ), | ||||||
|  |                     ).padding(bottom: 32), | ||||||
|  |                     Form( | ||||||
|  |                       key: formKey, | ||||||
|  |                       child: Column( | ||||||
|  |                         children: [ | ||||||
|  |                           TextFormField( | ||||||
|  |                             controller: nameController, | ||||||
|  |                             decoration: InputDecoration(labelText: 'name'.tr()), | ||||||
|  |                           ), | ||||||
|  |                           const SizedBox(height: 16), | ||||||
|  |                           TextFormField( | ||||||
|  |                             controller: nickController, | ||||||
|  |                             decoration: InputDecoration( | ||||||
|  |                               labelText: 'nickname'.tr(), | ||||||
|  |                               alignLabelWithHint: true, | ||||||
|  |                             ), | ||||||
|  |                           ), | ||||||
|  |                           const SizedBox(height: 16), | ||||||
|  |                           TextFormField( | ||||||
|  |                             controller: slugController, | ||||||
|  |                             decoration: InputDecoration( | ||||||
|  |                               labelText: 'slug'.tr(), | ||||||
|  |                               helperText: 'slugHint'.tr(), | ||||||
|  |                             ), | ||||||
|  |                           ), | ||||||
|  |                           const SizedBox(height: 16), | ||||||
|  |                           TextFormField( | ||||||
|  |                             controller: bioController, | ||||||
|  |                             decoration: InputDecoration( | ||||||
|  |                               labelText: 'bio'.tr(), | ||||||
|  |                               alignLabelWithHint: true, | ||||||
|  |                             ), | ||||||
|  |                             maxLines: 3, | ||||||
|  |                           ), | ||||||
|  |                           const SizedBox(height: 16), | ||||||
|  |                           Row( | ||||||
|  |                             spacing: 16, | ||||||
|  |                             children: [ | ||||||
|  |                               Expanded( | ||||||
|  |                                 child: TextFormField( | ||||||
|  |                                   controller: firstNameController, | ||||||
|  |                                   decoration: InputDecoration( | ||||||
|  |                                     labelText: 'firstName'.tr(), | ||||||
|  |                                   ), | ||||||
|  |                                 ), | ||||||
|  |                               ), | ||||||
|  |                               Expanded( | ||||||
|  |                                 child: TextFormField( | ||||||
|  |                                   controller: middleNameController, | ||||||
|  |                                   decoration: InputDecoration( | ||||||
|  |                                     labelText: 'middleName'.tr(), | ||||||
|  |                                   ), | ||||||
|  |                                 ), | ||||||
|  |                               ), | ||||||
|  |                               Expanded( | ||||||
|  |                                 child: TextFormField( | ||||||
|  |                                   controller: lastNameController, | ||||||
|  |                                   decoration: InputDecoration( | ||||||
|  |                                     labelText: 'lastName'.tr(), | ||||||
|  |                                   ), | ||||||
|  |                                 ), | ||||||
|  |                               ), | ||||||
|  |                             ], | ||||||
|  |                           ), | ||||||
|  |                           const SizedBox(height: 16), | ||||||
|  |                           Row( | ||||||
|  |                             spacing: 16, | ||||||
|  |                             children: [ | ||||||
|  |                               Expanded( | ||||||
|  |                                 child: TextFormField( | ||||||
|  |                                   controller: genderController, | ||||||
|  |                                   decoration: InputDecoration( | ||||||
|  |                                     labelText: 'gender'.tr(), | ||||||
|  |                                   ), | ||||||
|  |                                 ), | ||||||
|  |                               ), | ||||||
|  |                               Expanded( | ||||||
|  |                                 child: TextFormField( | ||||||
|  |                                   controller: pronounsController, | ||||||
|  |                                   decoration: InputDecoration( | ||||||
|  |                                     labelText: 'pronouns'.tr(), | ||||||
|  |                                   ), | ||||||
|  |                                 ), | ||||||
|  |                               ), | ||||||
|  |                             ], | ||||||
|  |                           ), | ||||||
|  |                           const SizedBox(height: 16), | ||||||
|  |                           Row( | ||||||
|  |                             spacing: 16, | ||||||
|  |                             children: [ | ||||||
|  |                               Expanded( | ||||||
|  |                                 child: TextFormField( | ||||||
|  |                                   controller: locationController, | ||||||
|  |                                   decoration: InputDecoration( | ||||||
|  |                                     labelText: 'location'.tr(), | ||||||
|  |                                   ), | ||||||
|  |                                 ), | ||||||
|  |                               ), | ||||||
|  |                               Expanded( | ||||||
|  |                                 child: TextFormField( | ||||||
|  |                                   controller: timeZoneController, | ||||||
|  |                                   decoration: InputDecoration( | ||||||
|  |                                     labelText: 'timeZone'.tr(), | ||||||
|  |                                   ), | ||||||
|  |                                 ), | ||||||
|  |                               ), | ||||||
|  |                             ], | ||||||
|  |                           ), | ||||||
|  |                           const SizedBox(height: 16), | ||||||
|  |                           GestureDetector( | ||||||
|  |                             onTap: () async { | ||||||
|  |                               final date = await showDatePicker( | ||||||
|  |                                 context: context, | ||||||
|  |                                 initialDate: birthday.value ?? DateTime.now(), | ||||||
|  |                                 firstDate: DateTime(1900), | ||||||
|  |                                 lastDate: DateTime.now(), | ||||||
|  |                               ); | ||||||
|  |                               if (date != null) { | ||||||
|  |                                 birthday.value = date; | ||||||
|  |                               } | ||||||
|  |                             }, | ||||||
|  |                             child: Container( | ||||||
|  |                               padding: const EdgeInsets.symmetric(vertical: 8), | ||||||
|  |                               decoration: BoxDecoration( | ||||||
|  |                                 border: Border( | ||||||
|  |                                   bottom: BorderSide( | ||||||
|  |                                     color: Theme.of(context).dividerColor, | ||||||
|  |                                     width: 1, | ||||||
|  |                                   ), | ||||||
|  |                                 ), | ||||||
|  |                               ), | ||||||
|  |                               child: Column( | ||||||
|  |                                 crossAxisAlignment: CrossAxisAlignment.stretch, | ||||||
|  |                                 children: [ | ||||||
|  |                                   Text( | ||||||
|  |                                     'birthday'.tr(), | ||||||
|  |                                     style: TextStyle( | ||||||
|  |                                       color: Theme.of(context).hintColor, | ||||||
|  |                                     ), | ||||||
|  |                                   ), | ||||||
|  |                                   Text( | ||||||
|  |                                     birthday.value != null | ||||||
|  |                                         ? DateFormat.yMMMd().format( | ||||||
|  |                                           birthday.value!, | ||||||
|  |                                         ) | ||||||
|  |                                         : 'Select a date'.tr(), | ||||||
|  |                                   ), | ||||||
|  |                                 ], | ||||||
|  |                               ), | ||||||
|  |                             ), | ||||||
|  |                           ), | ||||||
|  |                           const SizedBox(height: 16), | ||||||
|  |                           Align( | ||||||
|  |                             alignment: Alignment.centerRight, | ||||||
|  |                             child: TextButton.icon( | ||||||
|  |                               onPressed: | ||||||
|  |                                   submitting.value ? null : performAction, | ||||||
|  |                               label: Text('saveChanges').tr(), | ||||||
|  |                               icon: const Icon(Symbols.save), | ||||||
|  |                             ), | ||||||
|  |                           ), | ||||||
|  |                         ], | ||||||
|  |                       ).padding(all: 24), | ||||||
|  |                     ), | ||||||
|  |                   ], | ||||||
|  |                 ), | ||||||
|  |               ), | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  | } | ||||||
							
								
								
									
										167
									
								
								lib/screens/developers/edit_bot.g.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										167
									
								
								lib/screens/developers/edit_bot.g.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,167 @@ | |||||||
|  | // GENERATED CODE - DO NOT MODIFY BY HAND | ||||||
|  |  | ||||||
|  | part of 'edit_bot.dart'; | ||||||
|  |  | ||||||
|  | // ************************************************************************** | ||||||
|  | // RiverpodGenerator | ||||||
|  | // ************************************************************************** | ||||||
|  |  | ||||||
|  | String _$botHash() => r'7bec47bb2a4061a5babc6d6d19c3d4c320c91188'; | ||||||
|  |  | ||||||
|  | /// 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)); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /// See also [bot]. | ||||||
|  | @ProviderFor(bot) | ||||||
|  | const botProvider = BotFamily(); | ||||||
|  |  | ||||||
|  | /// See also [bot]. | ||||||
|  | class BotFamily extends Family<AsyncValue<Bot?>> { | ||||||
|  |   /// See also [bot]. | ||||||
|  |   const BotFamily(); | ||||||
|  |  | ||||||
|  |   /// See also [bot]. | ||||||
|  |   BotProvider call(String publisherName, String projectId, String id) { | ||||||
|  |     return BotProvider(publisherName, projectId, id); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   BotProvider getProviderOverride(covariant BotProvider provider) { | ||||||
|  |     return call(provider.publisherName, provider.projectId, provider.id); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   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'botProvider'; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /// See also [bot]. | ||||||
|  | class BotProvider extends AutoDisposeFutureProvider<Bot?> { | ||||||
|  |   /// See also [bot]. | ||||||
|  |   BotProvider(String publisherName, String projectId, String id) | ||||||
|  |     : this._internal( | ||||||
|  |         (ref) => bot(ref as BotRef, publisherName, projectId, id), | ||||||
|  |         from: botProvider, | ||||||
|  |         name: r'botProvider', | ||||||
|  |         debugGetCreateSourceHash: | ||||||
|  |             const bool.fromEnvironment('dart.vm.product') ? null : _$botHash, | ||||||
|  |         dependencies: BotFamily._dependencies, | ||||||
|  |         allTransitiveDependencies: BotFamily._allTransitiveDependencies, | ||||||
|  |         publisherName: publisherName, | ||||||
|  |         projectId: projectId, | ||||||
|  |         id: id, | ||||||
|  |       ); | ||||||
|  |  | ||||||
|  |   BotProvider._internal( | ||||||
|  |     super._createNotifier, { | ||||||
|  |     required super.name, | ||||||
|  |     required super.dependencies, | ||||||
|  |     required super.allTransitiveDependencies, | ||||||
|  |     required super.debugGetCreateSourceHash, | ||||||
|  |     required super.from, | ||||||
|  |     required this.publisherName, | ||||||
|  |     required this.projectId, | ||||||
|  |     required this.id, | ||||||
|  |   }) : super.internal(); | ||||||
|  |  | ||||||
|  |   final String publisherName; | ||||||
|  |   final String projectId; | ||||||
|  |   final String id; | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   Override overrideWith(FutureOr<Bot?> Function(BotRef provider) create) { | ||||||
|  |     return ProviderOverride( | ||||||
|  |       origin: this, | ||||||
|  |       override: BotProvider._internal( | ||||||
|  |         (ref) => create(ref as BotRef), | ||||||
|  |         from: from, | ||||||
|  |         name: null, | ||||||
|  |         dependencies: null, | ||||||
|  |         allTransitiveDependencies: null, | ||||||
|  |         debugGetCreateSourceHash: null, | ||||||
|  |         publisherName: publisherName, | ||||||
|  |         projectId: projectId, | ||||||
|  |         id: id, | ||||||
|  |       ), | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   AutoDisposeFutureProviderElement<Bot?> createElement() { | ||||||
|  |     return _BotProviderElement(this); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   bool operator ==(Object other) { | ||||||
|  |     return other is BotProvider && | ||||||
|  |         other.publisherName == publisherName && | ||||||
|  |         other.projectId == projectId && | ||||||
|  |         other.id == id; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   int get hashCode { | ||||||
|  |     var hash = _SystemHash.combine(0, runtimeType.hashCode); | ||||||
|  |     hash = _SystemHash.combine(hash, publisherName.hashCode); | ||||||
|  |     hash = _SystemHash.combine(hash, projectId.hashCode); | ||||||
|  |     hash = _SystemHash.combine(hash, id.hashCode); | ||||||
|  |  | ||||||
|  |     return _SystemHash.finish(hash); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @Deprecated('Will be removed in 3.0. Use Ref instead') | ||||||
|  | // ignore: unused_element | ||||||
|  | mixin BotRef on AutoDisposeFutureProviderRef<Bot?> { | ||||||
|  |   /// The parameter `publisherName` of this provider. | ||||||
|  |   String get publisherName; | ||||||
|  |  | ||||||
|  |   /// The parameter `projectId` of this provider. | ||||||
|  |   String get projectId; | ||||||
|  |  | ||||||
|  |   /// The parameter `id` of this provider. | ||||||
|  |   String get id; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | class _BotProviderElement extends AutoDisposeFutureProviderElement<Bot?> | ||||||
|  |     with BotRef { | ||||||
|  |   _BotProviderElement(super.provider); | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   String get publisherName => (origin as BotProvider).publisherName; | ||||||
|  |   @override | ||||||
|  |   String get projectId => (origin as BotProvider).projectId; | ||||||
|  |   @override | ||||||
|  |   String get id => (origin as BotProvider).id; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // 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 | ||||||
							
								
								
									
										130
									
								
								lib/screens/developers/edit_project.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										130
									
								
								lib/screens/developers/edit_project.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,130 @@ | |||||||
|  | import 'package:flutter/material.dart'; | ||||||
|  | import 'package:easy_localization/easy_localization.dart'; | ||||||
|  | import 'package:flutter_hooks/flutter_hooks.dart'; | ||||||
|  | import 'package:go_router/go_router.dart'; | ||||||
|  | import 'package:hooks_riverpod/hooks_riverpod.dart'; | ||||||
|  | import 'package:island/models/dev_project.dart'; | ||||||
|  | import 'package:island/pods/network.dart'; | ||||||
|  | import 'package:island/screens/developers/projects.dart'; | ||||||
|  | import 'package:island/widgets/app_scaffold.dart'; | ||||||
|  | import 'package:island/widgets/response.dart'; | ||||||
|  | import 'package:material_symbols_icons/symbols.dart'; | ||||||
|  | import 'package:riverpod_annotation/riverpod_annotation.dart'; | ||||||
|  | import 'package:styled_widget/styled_widget.dart'; | ||||||
|  |  | ||||||
|  | part 'edit_project.g.dart'; | ||||||
|  |  | ||||||
|  | @riverpod | ||||||
|  | Future<DevProject?> devProject(Ref ref, String pubName, String id) async { | ||||||
|  |   final client = ref.watch(apiClientProvider); | ||||||
|  |   final resp = await client.get('/develop/developers/$pubName/projects/$id'); | ||||||
|  |   return DevProject.fromJson(resp.data); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | class EditProjectScreen extends HookConsumerWidget { | ||||||
|  |   final String publisherName; | ||||||
|  |   final String? id; | ||||||
|  |   const EditProjectScreen({super.key, required this.publisherName, this.id}); | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   Widget build(BuildContext context, WidgetRef ref) { | ||||||
|  |     final isNew = id == null; | ||||||
|  |     final projectData = | ||||||
|  |         isNew ? null : ref.watch(devProjectProvider(publisherName, id!)); | ||||||
|  |  | ||||||
|  |     final formKey = useMemoized(() => GlobalKey<FormState>()); | ||||||
|  |     final submitting = useState(false); | ||||||
|  |  | ||||||
|  |     final nameController = useTextEditingController(); | ||||||
|  |     final slugController = useTextEditingController(); | ||||||
|  |     final descriptionController = useTextEditingController(); | ||||||
|  |  | ||||||
|  |     useEffect(() { | ||||||
|  |       if (projectData?.value != null) { | ||||||
|  |         nameController.text = projectData!.value!.name; | ||||||
|  |         slugController.text = projectData.value!.slug; | ||||||
|  |         descriptionController.text = projectData.value!.description ?? ''; | ||||||
|  |       } | ||||||
|  |       return null; | ||||||
|  |     }, [projectData]); | ||||||
|  |  | ||||||
|  |     void performAction() async { | ||||||
|  |       final client = ref.read(apiClientProvider); | ||||||
|  |       final data = { | ||||||
|  |         'name': nameController.text, | ||||||
|  |         'slug': slugController.text, | ||||||
|  |         'description': descriptionController.text, | ||||||
|  |       }; | ||||||
|  |       if (isNew) { | ||||||
|  |         await client.post( | ||||||
|  |           '/develop/developers/$publisherName/projects', | ||||||
|  |           data: data, | ||||||
|  |         ); | ||||||
|  |       } else { | ||||||
|  |         await client.put( | ||||||
|  |           '/develop/developers/$publisherName/projects/$id', | ||||||
|  |           data: data, | ||||||
|  |         ); | ||||||
|  |       } | ||||||
|  |       ref.invalidate(devProjectsProvider(publisherName)); | ||||||
|  |       if (context.mounted) { | ||||||
|  |         context.pop(); | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     return AppScaffold( | ||||||
|  |       appBar: AppBar( | ||||||
|  |         title: Text(isNew ? 'createProject'.tr() : 'editProject'.tr()), | ||||||
|  |       ), | ||||||
|  |       body: | ||||||
|  |           projectData == null && !isNew | ||||||
|  |               ? const Center(child: CircularProgressIndicator()) | ||||||
|  |               : projectData?.hasError == true && !isNew | ||||||
|  |               ? ResponseErrorWidget( | ||||||
|  |                 error: projectData!.error, | ||||||
|  |                 onRetry: | ||||||
|  |                     () => | ||||||
|  |                         ref.invalidate(devProjectProvider(publisherName, id!)), | ||||||
|  |               ) | ||||||
|  |               : SingleChildScrollView( | ||||||
|  |                 child: Form( | ||||||
|  |                   key: formKey, | ||||||
|  |                   child: Column( | ||||||
|  |                     children: [ | ||||||
|  |                       TextFormField( | ||||||
|  |                         controller: nameController, | ||||||
|  |                         decoration: InputDecoration(labelText: 'name'.tr()), | ||||||
|  |                       ), | ||||||
|  |                       const SizedBox(height: 16), | ||||||
|  |                       TextFormField( | ||||||
|  |                         controller: slugController, | ||||||
|  |                         decoration: InputDecoration( | ||||||
|  |                           labelText: 'slug'.tr(), | ||||||
|  |                           helperText: 'slugHint'.tr(), | ||||||
|  |                         ), | ||||||
|  |                       ), | ||||||
|  |                       const SizedBox(height: 16), | ||||||
|  |                       TextFormField( | ||||||
|  |                         controller: descriptionController, | ||||||
|  |                         decoration: InputDecoration( | ||||||
|  |                           labelText: 'description'.tr(), | ||||||
|  |                           alignLabelWithHint: true, | ||||||
|  |                         ), | ||||||
|  |                         maxLines: 3, | ||||||
|  |                       ), | ||||||
|  |                       const SizedBox(height: 16), | ||||||
|  |                       Align( | ||||||
|  |                         alignment: Alignment.centerRight, | ||||||
|  |                         child: TextButton.icon( | ||||||
|  |                           onPressed: submitting.value ? null : performAction, | ||||||
|  |                           label: Text('saveChanges'.tr()), | ||||||
|  |                           icon: const Icon(Symbols.save), | ||||||
|  |                         ), | ||||||
|  |                       ), | ||||||
|  |                     ], | ||||||
|  |                   ).padding(all: 24), | ||||||
|  |                 ), | ||||||
|  |               ), | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  | } | ||||||
							
								
								
									
										163
									
								
								lib/screens/developers/edit_project.g.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										163
									
								
								lib/screens/developers/edit_project.g.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,163 @@ | |||||||
|  | // GENERATED CODE - DO NOT MODIFY BY HAND | ||||||
|  |  | ||||||
|  | part of 'edit_project.dart'; | ||||||
|  |  | ||||||
|  | // ************************************************************************** | ||||||
|  | // RiverpodGenerator | ||||||
|  | // ************************************************************************** | ||||||
|  |  | ||||||
|  | String _$devProjectHash() => r'd92be3f5cdc510c2a377615ed5c70622a6842bf2'; | ||||||
|  |  | ||||||
|  | /// 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)); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /// See also [devProject]. | ||||||
|  | @ProviderFor(devProject) | ||||||
|  | const devProjectProvider = DevProjectFamily(); | ||||||
|  |  | ||||||
|  | /// See also [devProject]. | ||||||
|  | class DevProjectFamily extends Family<AsyncValue<DevProject?>> { | ||||||
|  |   /// See also [devProject]. | ||||||
|  |   const DevProjectFamily(); | ||||||
|  |  | ||||||
|  |   /// See also [devProject]. | ||||||
|  |   DevProjectProvider call(String pubName, String id) { | ||||||
|  |     return DevProjectProvider(pubName, id); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   DevProjectProvider getProviderOverride( | ||||||
|  |     covariant DevProjectProvider provider, | ||||||
|  |   ) { | ||||||
|  |     return call(provider.pubName, provider.id); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   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'devProjectProvider'; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /// See also [devProject]. | ||||||
|  | class DevProjectProvider extends AutoDisposeFutureProvider<DevProject?> { | ||||||
|  |   /// See also [devProject]. | ||||||
|  |   DevProjectProvider(String pubName, String id) | ||||||
|  |     : this._internal( | ||||||
|  |         (ref) => devProject(ref as DevProjectRef, pubName, id), | ||||||
|  |         from: devProjectProvider, | ||||||
|  |         name: r'devProjectProvider', | ||||||
|  |         debugGetCreateSourceHash: | ||||||
|  |             const bool.fromEnvironment('dart.vm.product') | ||||||
|  |                 ? null | ||||||
|  |                 : _$devProjectHash, | ||||||
|  |         dependencies: DevProjectFamily._dependencies, | ||||||
|  |         allTransitiveDependencies: DevProjectFamily._allTransitiveDependencies, | ||||||
|  |         pubName: pubName, | ||||||
|  |         id: id, | ||||||
|  |       ); | ||||||
|  |  | ||||||
|  |   DevProjectProvider._internal( | ||||||
|  |     super._createNotifier, { | ||||||
|  |     required super.name, | ||||||
|  |     required super.dependencies, | ||||||
|  |     required super.allTransitiveDependencies, | ||||||
|  |     required super.debugGetCreateSourceHash, | ||||||
|  |     required super.from, | ||||||
|  |     required this.pubName, | ||||||
|  |     required this.id, | ||||||
|  |   }) : super.internal(); | ||||||
|  |  | ||||||
|  |   final String pubName; | ||||||
|  |   final String id; | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   Override overrideWith( | ||||||
|  |     FutureOr<DevProject?> Function(DevProjectRef provider) create, | ||||||
|  |   ) { | ||||||
|  |     return ProviderOverride( | ||||||
|  |       origin: this, | ||||||
|  |       override: DevProjectProvider._internal( | ||||||
|  |         (ref) => create(ref as DevProjectRef), | ||||||
|  |         from: from, | ||||||
|  |         name: null, | ||||||
|  |         dependencies: null, | ||||||
|  |         allTransitiveDependencies: null, | ||||||
|  |         debugGetCreateSourceHash: null, | ||||||
|  |         pubName: pubName, | ||||||
|  |         id: id, | ||||||
|  |       ), | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   AutoDisposeFutureProviderElement<DevProject?> createElement() { | ||||||
|  |     return _DevProjectProviderElement(this); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   bool operator ==(Object other) { | ||||||
|  |     return other is DevProjectProvider && | ||||||
|  |         other.pubName == pubName && | ||||||
|  |         other.id == id; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   int get hashCode { | ||||||
|  |     var hash = _SystemHash.combine(0, runtimeType.hashCode); | ||||||
|  |     hash = _SystemHash.combine(hash, pubName.hashCode); | ||||||
|  |     hash = _SystemHash.combine(hash, id.hashCode); | ||||||
|  |  | ||||||
|  |     return _SystemHash.finish(hash); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @Deprecated('Will be removed in 3.0. Use Ref instead') | ||||||
|  | // ignore: unused_element | ||||||
|  | mixin DevProjectRef on AutoDisposeFutureProviderRef<DevProject?> { | ||||||
|  |   /// The parameter `pubName` of this provider. | ||||||
|  |   String get pubName; | ||||||
|  |  | ||||||
|  |   /// The parameter `id` of this provider. | ||||||
|  |   String get id; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | class _DevProjectProviderElement | ||||||
|  |     extends AutoDisposeFutureProviderElement<DevProject?> | ||||||
|  |     with DevProjectRef { | ||||||
|  |   _DevProjectProviderElement(super.provider); | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   String get pubName => (origin as DevProjectProvider).pubName; | ||||||
|  |   @override | ||||||
|  |   String get id => (origin as DevProjectProvider).id; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // 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 | ||||||
| @@ -235,15 +235,15 @@ class DeveloperHubScreen extends HookConsumerWidget { | |||||||
|                             ).padding(vertical: 12, horizontal: 12), |                             ).padding(vertical: 12, horizontal: 12), | ||||||
|                           ListTile( |                           ListTile( | ||||||
|                             minTileHeight: 48, |                             minTileHeight: 48, | ||||||
|                             title: Text('customApps').tr(), |                             title: Text('projects').tr(), | ||||||
|                             trailing: Icon(Symbols.chevron_right), |                             trailing: const Icon(Symbols.chevron_right), | ||||||
|                             leading: const Icon(Symbols.apps), |                             leading: const Icon(Symbols.folder_managed), | ||||||
|                             contentPadding: EdgeInsets.symmetric( |                             contentPadding: const EdgeInsets.symmetric( | ||||||
|                               horizontal: 24, |                               horizontal: 24, | ||||||
|                             ), |                             ), | ||||||
|                             onTap: () { |                             onTap: () { | ||||||
|                               context.pushNamed( |                               context.pushNamed( | ||||||
|                                 'developerApps', |                                 'developerProjects', | ||||||
|                                 pathParameters: { |                                 pathParameters: { | ||||||
|                                   'name': |                                   'name': | ||||||
|                                       currentDeveloper.value!.publisher!.name, |                                       currentDeveloper.value!.publisher!.name, | ||||||
|   | |||||||
| @@ -3,10 +3,11 @@ import 'package:island/screens/developers/edit_app.dart'; | |||||||
|  |  | ||||||
| class NewCustomAppScreen extends StatelessWidget { | class NewCustomAppScreen extends StatelessWidget { | ||||||
|   final String publisherName; |   final String publisherName; | ||||||
|   const NewCustomAppScreen({super.key, required this.publisherName}); |   final String projectId; | ||||||
|  |   const NewCustomAppScreen({super.key, required this.publisherName, required this.projectId}); | ||||||
|  |  | ||||||
|   @override |   @override | ||||||
|   Widget build(BuildContext context) { |   Widget build(BuildContext context) { | ||||||
|     return EditAppScreen(publisherName: publisherName); |     return EditAppScreen(publisherName: publisherName, projectId: projectId); | ||||||
|   } |   } | ||||||
| } | } | ||||||
|   | |||||||
							
								
								
									
										14
									
								
								lib/screens/developers/new_bot.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								lib/screens/developers/new_bot.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,14 @@ | |||||||
|  |  | ||||||
|  | import 'package:flutter/material.dart'; | ||||||
|  | import 'package:island/screens/developers/edit_bot.dart'; | ||||||
|  |  | ||||||
|  | class NewBotScreen extends StatelessWidget { | ||||||
|  |   final String publisherName; | ||||||
|  |   final String projectId; | ||||||
|  |   const NewBotScreen({super.key, required this.publisherName, required this.projectId}); | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   Widget build(BuildContext context) { | ||||||
|  |     return EditBotScreen(publisherName: publisherName, projectId: projectId); | ||||||
|  |   } | ||||||
|  | } | ||||||
							
								
								
									
										13
									
								
								lib/screens/developers/new_project.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								lib/screens/developers/new_project.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,13 @@ | |||||||
|  |  | ||||||
|  | import 'package:flutter/material.dart'; | ||||||
|  | import 'package:island/screens/developers/edit_project.dart'; | ||||||
|  |  | ||||||
|  | class NewProjectScreen extends StatelessWidget { | ||||||
|  |   final String publisherName; | ||||||
|  |   const NewProjectScreen({super.key, required this.publisherName}); | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   Widget build(BuildContext context) { | ||||||
|  |     return EditProjectScreen(publisherName: publisherName); | ||||||
|  |   } | ||||||
|  | } | ||||||
							
								
								
									
										73
									
								
								lib/screens/developers/project_detail.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										73
									
								
								lib/screens/developers/project_detail.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,73 @@ | |||||||
|  | import 'package:easy_localization/easy_localization.dart'; | ||||||
|  | import 'package:flutter/material.dart'; | ||||||
|  | import 'package:flutter_hooks/flutter_hooks.dart'; | ||||||
|  | import 'package:gap/gap.dart'; | ||||||
|  | import 'package:go_router/go_router.dart'; | ||||||
|  | import 'package:hooks_riverpod/hooks_riverpod.dart'; | ||||||
|  | import 'package:island/screens/developers/apps.dart'; | ||||||
|  | import 'package:island/screens/developers/bots.dart'; | ||||||
|  | import 'package:island/widgets/app_scaffold.dart'; | ||||||
|  | import 'package:material_symbols_icons/symbols.dart'; | ||||||
|  |  | ||||||
|  | class ProjectDetailScreen extends HookConsumerWidget { | ||||||
|  |   final String publisherName; | ||||||
|  |   final String projectId; | ||||||
|  |  | ||||||
|  |   const ProjectDetailScreen({ | ||||||
|  |     super.key, | ||||||
|  |     required this.publisherName, | ||||||
|  |     required this.projectId, | ||||||
|  |   }); | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   Widget build(BuildContext context, WidgetRef ref) { | ||||||
|  |     final tabController = useTabController(initialLength: 2); | ||||||
|  |  | ||||||
|  |     return AppScaffold( | ||||||
|  |       appBar: AppBar( | ||||||
|  |         title: Text('projectDetails').tr(), | ||||||
|  |         actions: [ | ||||||
|  |           IconButton( | ||||||
|  |             icon: const Icon(Symbols.add), | ||||||
|  |             onPressed: () { | ||||||
|  |               // Get current tab index | ||||||
|  |               final index = tabController.index; | ||||||
|  |               switch (index) { | ||||||
|  |                 case 0: | ||||||
|  |                   context.pushNamed( | ||||||
|  |                     'developerAppNew', | ||||||
|  |                     pathParameters: { | ||||||
|  |                       'name': publisherName, | ||||||
|  |                       'projectId': projectId, | ||||||
|  |                     }, | ||||||
|  |                   ); | ||||||
|  |                   break; | ||||||
|  |                 case 1: | ||||||
|  |                   context.pushNamed( | ||||||
|  |                     'developerBotNew', | ||||||
|  |                     pathParameters: { | ||||||
|  |                       'name': publisherName, | ||||||
|  |                       'projectId': projectId, | ||||||
|  |                     }, | ||||||
|  |                   ); | ||||||
|  |                   break; | ||||||
|  |               } | ||||||
|  |             }, | ||||||
|  |           ), | ||||||
|  |           const Gap(8), | ||||||
|  |         ], | ||||||
|  |         bottom: TabBar( | ||||||
|  |           controller: tabController, | ||||||
|  |           tabs: [Tab(text: 'customApps'.tr()), Tab(text: 'bots'.tr())], | ||||||
|  |         ), | ||||||
|  |       ), | ||||||
|  |       body: TabBarView( | ||||||
|  |         controller: tabController, | ||||||
|  |         children: [ | ||||||
|  |           CustomAppsScreen(publisherName: publisherName, projectId: projectId), | ||||||
|  |           BotsScreen(publisherName: publisherName, projectId: projectId), | ||||||
|  |         ], | ||||||
|  |       ), | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  | } | ||||||
							
								
								
									
										150
									
								
								lib/screens/developers/projects.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										150
									
								
								lib/screens/developers/projects.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,150 @@ | |||||||
|  | import 'package:flutter/material.dart'; | ||||||
|  | import 'package:easy_localization/easy_localization.dart'; | ||||||
|  | import 'package:gap/gap.dart'; | ||||||
|  | import 'package:go_router/go_router.dart'; | ||||||
|  | import 'package:hooks_riverpod/hooks_riverpod.dart'; | ||||||
|  | import 'package:island/models/dev_project.dart'; | ||||||
|  | import 'package:island/pods/network.dart'; | ||||||
|  | import 'package:island/widgets/alert.dart'; | ||||||
|  | import 'package:island/widgets/app_scaffold.dart'; | ||||||
|  | import 'package:island/widgets/response.dart'; | ||||||
|  | import 'package:material_symbols_icons/symbols.dart'; | ||||||
|  | import 'package:riverpod_annotation/riverpod_annotation.dart'; | ||||||
|  |  | ||||||
|  | part 'projects.g.dart'; | ||||||
|  |  | ||||||
|  | @riverpod | ||||||
|  | Future<List<DevProject>> devProjects(Ref ref, String pubName) async { | ||||||
|  |   final client = ref.watch(apiClientProvider); | ||||||
|  |   final resp = await client.get('/develop/developers/$pubName/projects'); | ||||||
|  |   return (resp.data as List) | ||||||
|  |       .map((e) => DevProject.fromJson(e)) | ||||||
|  |       .cast<DevProject>() | ||||||
|  |       .toList(); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | class DevProjectsScreen extends HookConsumerWidget { | ||||||
|  |   final String publisherName; | ||||||
|  |   const DevProjectsScreen({super.key, required this.publisherName}); | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   Widget build(BuildContext context, WidgetRef ref) { | ||||||
|  |     final projects = ref.watch(devProjectsProvider(publisherName)); | ||||||
|  |  | ||||||
|  |     return AppScaffold( | ||||||
|  |       appBar: AppBar( | ||||||
|  |         title: Text('projects').tr(), | ||||||
|  |         actions: [ | ||||||
|  |           IconButton( | ||||||
|  |             icon: const Icon(Symbols.add), | ||||||
|  |             onPressed: () { | ||||||
|  |               context.pushNamed( | ||||||
|  |                 'developerProjectNew', | ||||||
|  |                 pathParameters: {'name': publisherName}, | ||||||
|  |               ); | ||||||
|  |             }, | ||||||
|  |           ), | ||||||
|  |           const Gap(8), | ||||||
|  |         ], | ||||||
|  |       ), | ||||||
|  |       body: projects.when( | ||||||
|  |         data: (data) { | ||||||
|  |           if (data.isEmpty) { | ||||||
|  |             return Center(child: Text('noProjects').tr()); | ||||||
|  |           } | ||||||
|  |           return RefreshIndicator( | ||||||
|  |             onRefresh: | ||||||
|  |                 () => ref.refresh(devProjectsProvider(publisherName).future), | ||||||
|  |             child: ListView.builder( | ||||||
|  |               padding: EdgeInsets.only(top: 4), | ||||||
|  |               itemCount: data.length, | ||||||
|  |               itemBuilder: (context, index) { | ||||||
|  |                 final project = data[index]; | ||||||
|  |                 return Card( | ||||||
|  |                   margin: const EdgeInsets.all(8.0), | ||||||
|  |                   child: ListTile( | ||||||
|  |                     shape: RoundedRectangleBorder( | ||||||
|  |                       borderRadius: BorderRadius.circular(8.0), | ||||||
|  |                     ), | ||||||
|  |                     contentPadding: EdgeInsets.only(left: 20, right: 12), | ||||||
|  |                     title: Text(project.name), | ||||||
|  |                     subtitle: Text(project.description ?? ''), | ||||||
|  |                     trailing: PopupMenuButton( | ||||||
|  |                       itemBuilder: | ||||||
|  |                           (context) => [ | ||||||
|  |                             PopupMenuItem( | ||||||
|  |                               value: 'edit', | ||||||
|  |                               child: Row( | ||||||
|  |                                 children: [ | ||||||
|  |                                   const Icon(Symbols.edit), | ||||||
|  |                                   const SizedBox(width: 12), | ||||||
|  |                                   Text('edit').tr(), | ||||||
|  |                                 ], | ||||||
|  |                               ), | ||||||
|  |                             ), | ||||||
|  |                             PopupMenuItem( | ||||||
|  |                               value: 'delete', | ||||||
|  |                               child: Row( | ||||||
|  |                                 children: [ | ||||||
|  |                                   const Icon(Symbols.delete, color: Colors.red), | ||||||
|  |                                   const SizedBox(width: 12), | ||||||
|  |                                   Text( | ||||||
|  |                                     'delete', | ||||||
|  |                                     style: TextStyle(color: Colors.red), | ||||||
|  |                                   ).tr(), | ||||||
|  |                                 ], | ||||||
|  |                               ), | ||||||
|  |                             ), | ||||||
|  |                           ], | ||||||
|  |                       onSelected: (value) { | ||||||
|  |                         if (value == 'edit') { | ||||||
|  |                           context.pushNamed( | ||||||
|  |                             'developerProjectEdit', | ||||||
|  |                             pathParameters: { | ||||||
|  |                               'name': publisherName, | ||||||
|  |                               'id': project.id, | ||||||
|  |                             }, | ||||||
|  |                           ); | ||||||
|  |                         } else if (value == 'delete') { | ||||||
|  |                           showConfirmAlert( | ||||||
|  |                             'deleteProjectHint'.tr(), | ||||||
|  |                             'deleteProject'.tr(), | ||||||
|  |                           ).then((confirm) { | ||||||
|  |                             if (confirm) { | ||||||
|  |                               final client = ref.read(apiClientProvider); | ||||||
|  |                               client.delete( | ||||||
|  |                                 '/develop/developers/$publisherName/projects/${project.id}', | ||||||
|  |                               ); | ||||||
|  |                               ref.invalidate( | ||||||
|  |                                 devProjectsProvider(publisherName), | ||||||
|  |                               ); | ||||||
|  |                             } | ||||||
|  |                           }); | ||||||
|  |                         } | ||||||
|  |                       }, | ||||||
|  |                     ), | ||||||
|  |                     onTap: () { | ||||||
|  |                       context.pushNamed( | ||||||
|  |                         'developerProjectDetail', | ||||||
|  |                         pathParameters: { | ||||||
|  |                           'name': publisherName, | ||||||
|  |                           'projectId': project.id, | ||||||
|  |                         }, | ||||||
|  |                       ); | ||||||
|  |                     }, | ||||||
|  |                   ), | ||||||
|  |                 ); | ||||||
|  |               }, | ||||||
|  |             ), | ||||||
|  |           ); | ||||||
|  |         }, | ||||||
|  |         loading: () => const Center(child: CircularProgressIndicator()), | ||||||
|  |         error: | ||||||
|  |             (err, stack) => ResponseErrorWidget( | ||||||
|  |               error: err, | ||||||
|  |               onRetry: () => ref.invalidate(devProjectsProvider(publisherName)), | ||||||
|  |             ), | ||||||
|  |       ), | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  | } | ||||||
							
								
								
									
										151
									
								
								lib/screens/developers/projects.g.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										151
									
								
								lib/screens/developers/projects.g.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,151 @@ | |||||||
|  | // GENERATED CODE - DO NOT MODIFY BY HAND | ||||||
|  |  | ||||||
|  | part of 'projects.dart'; | ||||||
|  |  | ||||||
|  | // ************************************************************************** | ||||||
|  | // RiverpodGenerator | ||||||
|  | // ************************************************************************** | ||||||
|  |  | ||||||
|  | String _$devProjectsHash() => r'87fdcab47cd7d79ab019a5625617abeb1ffa1f39'; | ||||||
|  |  | ||||||
|  | /// 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)); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /// See also [devProjects]. | ||||||
|  | @ProviderFor(devProjects) | ||||||
|  | const devProjectsProvider = DevProjectsFamily(); | ||||||
|  |  | ||||||
|  | /// See also [devProjects]. | ||||||
|  | class DevProjectsFamily extends Family<AsyncValue<List<DevProject>>> { | ||||||
|  |   /// See also [devProjects]. | ||||||
|  |   const DevProjectsFamily(); | ||||||
|  |  | ||||||
|  |   /// See also [devProjects]. | ||||||
|  |   DevProjectsProvider call(String pubName) { | ||||||
|  |     return DevProjectsProvider(pubName); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   DevProjectsProvider getProviderOverride( | ||||||
|  |     covariant DevProjectsProvider provider, | ||||||
|  |   ) { | ||||||
|  |     return call(provider.pubName); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   static const Iterable<ProviderOrFamily>? _dependencies = null; | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   Iterable<ProviderOrFamily>? get dependencies => _dependencies; | ||||||
|  |  | ||||||
|  |   static const Iterable<ProviderOrFamily>? _allTransitiveDependencies = null; | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   Iterable<ProviderOrFamily>? get allTransitiveDependencies => | ||||||
|  |       _allTransitiveDependencies; | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   String? get name => r'devProjectsProvider'; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /// See also [devProjects]. | ||||||
|  | class DevProjectsProvider extends AutoDisposeFutureProvider<List<DevProject>> { | ||||||
|  |   /// See also [devProjects]. | ||||||
|  |   DevProjectsProvider(String pubName) | ||||||
|  |     : this._internal( | ||||||
|  |         (ref) => devProjects(ref as DevProjectsRef, pubName), | ||||||
|  |         from: devProjectsProvider, | ||||||
|  |         name: r'devProjectsProvider', | ||||||
|  |         debugGetCreateSourceHash: | ||||||
|  |             const bool.fromEnvironment('dart.vm.product') | ||||||
|  |                 ? null | ||||||
|  |                 : _$devProjectsHash, | ||||||
|  |         dependencies: DevProjectsFamily._dependencies, | ||||||
|  |         allTransitiveDependencies: DevProjectsFamily._allTransitiveDependencies, | ||||||
|  |         pubName: pubName, | ||||||
|  |       ); | ||||||
|  |  | ||||||
|  |   DevProjectsProvider._internal( | ||||||
|  |     super._createNotifier, { | ||||||
|  |     required super.name, | ||||||
|  |     required super.dependencies, | ||||||
|  |     required super.allTransitiveDependencies, | ||||||
|  |     required super.debugGetCreateSourceHash, | ||||||
|  |     required super.from, | ||||||
|  |     required this.pubName, | ||||||
|  |   }) : super.internal(); | ||||||
|  |  | ||||||
|  |   final String pubName; | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   Override overrideWith( | ||||||
|  |     FutureOr<List<DevProject>> Function(DevProjectsRef provider) create, | ||||||
|  |   ) { | ||||||
|  |     return ProviderOverride( | ||||||
|  |       origin: this, | ||||||
|  |       override: DevProjectsProvider._internal( | ||||||
|  |         (ref) => create(ref as DevProjectsRef), | ||||||
|  |         from: from, | ||||||
|  |         name: null, | ||||||
|  |         dependencies: null, | ||||||
|  |         allTransitiveDependencies: null, | ||||||
|  |         debugGetCreateSourceHash: null, | ||||||
|  |         pubName: pubName, | ||||||
|  |       ), | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   AutoDisposeFutureProviderElement<List<DevProject>> createElement() { | ||||||
|  |     return _DevProjectsProviderElement(this); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   bool operator ==(Object other) { | ||||||
|  |     return other is DevProjectsProvider && other.pubName == pubName; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   int get hashCode { | ||||||
|  |     var hash = _SystemHash.combine(0, runtimeType.hashCode); | ||||||
|  |     hash = _SystemHash.combine(hash, pubName.hashCode); | ||||||
|  |  | ||||||
|  |     return _SystemHash.finish(hash); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @Deprecated('Will be removed in 3.0. Use Ref instead') | ||||||
|  | // ignore: unused_element | ||||||
|  | mixin DevProjectsRef on AutoDisposeFutureProviderRef<List<DevProject>> { | ||||||
|  |   /// The parameter `pubName` of this provider. | ||||||
|  |   String get pubName; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | class _DevProjectsProviderElement | ||||||
|  |     extends AutoDisposeFutureProviderElement<List<DevProject>> | ||||||
|  |     with DevProjectsRef { | ||||||
|  |   _DevProjectsProviderElement(super.provider); | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   String get pubName => (origin as DevProjectsProvider).pubName; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // ignore_for_file: type=lint | ||||||
|  | // ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package | ||||||
| @@ -101,7 +101,7 @@ class SliverArticlesList extends ConsumerWidget { | |||||||
|             publisherId: publisherId, |             publisherId: publisherId, | ||||||
|           ).notifier, |           ).notifier, | ||||||
|       contentBuilder: |       contentBuilder: | ||||||
|           (data, widgetCount, endItemView) => SliverList.builder( |           (data, widgetCount, endItemView) => SliverList.separated( | ||||||
|             itemCount: widgetCount, |             itemCount: widgetCount, | ||||||
|             itemBuilder: (context, index) { |             itemBuilder: (context, index) { | ||||||
|               if (index == widgetCount - 1) { |               if (index == widgetCount - 1) { | ||||||
| @@ -111,38 +111,95 @@ class SliverArticlesList extends ConsumerWidget { | |||||||
|               final article = data.items[index]; |               final article = data.items[index]; | ||||||
|               return WebArticleCard(article: article, showDetails: true); |               return WebArticleCard(article: article, showDetails: true); | ||||||
|             }, |             }, | ||||||
|  |             separatorBuilder: (context, index) => const SizedBox(height: 12), | ||||||
|           ), |           ), | ||||||
|     ); |     ); | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @riverpod | ||||||
|  | Future<List<SnWebFeed>> subscribedFeeds(Ref ref) async { | ||||||
|  |   final client = ref.watch(apiClientProvider); | ||||||
|  |   final response = await client.get('/sphere/feeds/subscribed'); | ||||||
|  |   final data = response.data as List<dynamic>; | ||||||
|  |   return data.map((json) => SnWebFeed.fromJson(json)).toList(); | ||||||
|  | } | ||||||
|  |  | ||||||
| class ArticlesScreen extends ConsumerWidget { | class ArticlesScreen extends ConsumerWidget { | ||||||
|   final String? feedId; |   const ArticlesScreen({super.key}); | ||||||
|   final String? publisherId; |  | ||||||
|   final String? title; |  | ||||||
|  |  | ||||||
|   const ArticlesScreen({super.key, this.feedId, this.publisherId, this.title}); |  | ||||||
|  |  | ||||||
|   @override |   @override | ||||||
|   Widget build(BuildContext context, WidgetRef ref) { |   Widget build(BuildContext context, WidgetRef ref) { | ||||||
|     return AppScaffold( |     final subscribedFeedsAsync = ref.watch(subscribedFeedsProvider); | ||||||
|       appBar: AppBar(title: Text(title ?? 'Articles')), |  | ||||||
|       body: Center( |     return subscribedFeedsAsync.when( | ||||||
|         child: ConstrainedBox( |       data: (feeds) { | ||||||
|           constraints: const BoxConstraints(maxWidth: 560), |         return DefaultTabController( | ||||||
|           child: CustomScrollView( |           length: feeds.length + 1, | ||||||
|             slivers: [ |           child: AppScaffold( | ||||||
|               SliverPadding( |             appBar: AppBar( | ||||||
|                 padding: const EdgeInsets.only(top: 8, left: 8, right: 8), |               title: const Text('Articles'), | ||||||
|                 sliver: SliverArticlesList( |               bottom: TabBar( | ||||||
|                   feedId: feedId, |                 isScrollable: true, | ||||||
|                   publisherId: publisherId, |                 tabs: [ | ||||||
|                 ), |                   const Tab(text: 'All'), | ||||||
|  |                   ...feeds.map((feed) => Tab(text: feed.title)), | ||||||
|  |                 ], | ||||||
|               ), |               ), | ||||||
|             ], |             ), | ||||||
|  |             body: TabBarView( | ||||||
|  |               children: [ | ||||||
|  |                 Center( | ||||||
|  |                   child: ConstrainedBox( | ||||||
|  |                     constraints: const BoxConstraints(maxWidth: 560), | ||||||
|  |                     child: CustomScrollView( | ||||||
|  |                       slivers: [ | ||||||
|  |                         SliverPadding( | ||||||
|  |                           padding: const EdgeInsets.only( | ||||||
|  |                             top: 12, | ||||||
|  |                             left: 8, | ||||||
|  |                             right: 8, | ||||||
|  |                           ), | ||||||
|  |                           sliver: SliverArticlesList(), | ||||||
|  |                         ), | ||||||
|  |                       ], | ||||||
|  |                     ), | ||||||
|  |                   ), | ||||||
|  |                 ), | ||||||
|  |                 ...feeds.map((feed) { | ||||||
|  |                   return Center( | ||||||
|  |                     child: ConstrainedBox( | ||||||
|  |                       constraints: const BoxConstraints(maxWidth: 560), | ||||||
|  |                       child: CustomScrollView( | ||||||
|  |                         slivers: [ | ||||||
|  |                           SliverPadding( | ||||||
|  |                             padding: const EdgeInsets.only( | ||||||
|  |                               top: 8, | ||||||
|  |                               left: 8, | ||||||
|  |                               right: 8, | ||||||
|  |                             ), | ||||||
|  |                             sliver: SliverArticlesList(feedId: feed.id), | ||||||
|  |                           ), | ||||||
|  |                         ], | ||||||
|  |                       ), | ||||||
|  |                     ), | ||||||
|  |                   ); | ||||||
|  |                 }), | ||||||
|  |               ], | ||||||
|  |             ), | ||||||
|  |           ), | ||||||
|  |         ); | ||||||
|  |       }, | ||||||
|  |       loading: | ||||||
|  |           () => AppScaffold( | ||||||
|  |             appBar: AppBar(title: const Text('Articles')), | ||||||
|  |             body: const Center(child: CircularProgressIndicator()), | ||||||
|  |           ), | ||||||
|  |       error: | ||||||
|  |           (err, stack) => AppScaffold( | ||||||
|  |             appBar: AppBar(title: const Text('Articles')), | ||||||
|  |             body: Center(child: Text('Error: $err')), | ||||||
|           ), |           ), | ||||||
|         ), |  | ||||||
|       ), |  | ||||||
|     ); |     ); | ||||||
|   } |   } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -6,6 +6,25 @@ part of 'articles.dart'; | |||||||
| // RiverpodGenerator | // RiverpodGenerator | ||||||
| // ************************************************************************** | // ************************************************************************** | ||||||
|  |  | ||||||
|  | String _$subscribedFeedsHash() => r'5c0c8c30c5f543f6ea1d39786a6778f77ba5b3df'; | ||||||
|  |  | ||||||
|  | /// See also [subscribedFeeds]. | ||||||
|  | @ProviderFor(subscribedFeeds) | ||||||
|  | final subscribedFeedsProvider = | ||||||
|  |     AutoDisposeFutureProvider<List<SnWebFeed>>.internal( | ||||||
|  |       subscribedFeeds, | ||||||
|  |       name: r'subscribedFeedsProvider', | ||||||
|  |       debugGetCreateSourceHash: | ||||||
|  |           const bool.fromEnvironment('dart.vm.product') | ||||||
|  |               ? null | ||||||
|  |               : _$subscribedFeedsHash, | ||||||
|  |       dependencies: null, | ||||||
|  |       allTransitiveDependencies: null, | ||||||
|  |     ); | ||||||
|  |  | ||||||
|  | @Deprecated('Will be removed in 3.0. Use Ref instead') | ||||||
|  | // ignore: unused_element | ||||||
|  | typedef SubscribedFeedsRef = AutoDisposeFutureProviderRef<List<SnWebFeed>>; | ||||||
| String _$articlesListNotifierHash() => | String _$articlesListNotifierHash() => | ||||||
|     r'579741af4d90c7c81f2e2697e57c4895b7a9dabc'; |     r'579741af4d90c7c81f2e2697e57c4895b7a9dabc'; | ||||||
|  |  | ||||||
|   | |||||||
							
								
								
									
										240
									
								
								lib/screens/discovery/feeds/feed_detail.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										240
									
								
								lib/screens/discovery/feeds/feed_detail.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,240 @@ | |||||||
|  | import 'package:easy_localization/easy_localization.dart'; | ||||||
|  | import 'package:flutter/material.dart'; | ||||||
|  | import 'package:flutter/services.dart'; | ||||||
|  | import 'package:flutter_hooks/flutter_hooks.dart'; | ||||||
|  | import 'package:gap/gap.dart'; | ||||||
|  | import 'package:hooks_riverpod/hooks_riverpod.dart'; | ||||||
|  | import 'package:island/models/webfeed.dart'; | ||||||
|  | import 'package:island/pods/network.dart'; | ||||||
|  | import 'package:island/widgets/alert.dart'; | ||||||
|  | import 'package:island/widgets/app_scaffold.dart'; | ||||||
|  | import 'package:island/widgets/web_article_card.dart'; | ||||||
|  | import 'package:material_symbols_icons/symbols.dart'; | ||||||
|  | import 'package:riverpod_annotation/riverpod_annotation.dart'; | ||||||
|  | import 'package:riverpod_paging_utils/riverpod_paging_utils.dart'; | ||||||
|  | import 'package:styled_widget/styled_widget.dart'; | ||||||
|  |  | ||||||
|  | part 'feed_detail.g.dart'; | ||||||
|  |  | ||||||
|  | @riverpod | ||||||
|  | Future<SnWebFeed> marketplaceWebFeed(Ref ref, String feedId) async { | ||||||
|  |   final apiClient = ref.watch(apiClientProvider); | ||||||
|  |   final resp = await apiClient.get('/sphere/feeds/$feedId'); | ||||||
|  |   return SnWebFeed.fromJson(resp.data); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /// Provider for web feed articles content | ||||||
|  | @riverpod | ||||||
|  | class MarketplaceWebFeedContentNotifier | ||||||
|  |     extends _$MarketplaceWebFeedContentNotifier | ||||||
|  |     with CursorPagingNotifierMixin<SnWebArticle> { | ||||||
|  |   static const int _pageSize = 20; | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   Future<CursorPagingData<SnWebArticle>> build(String feedId) async { | ||||||
|  |     _feedId = feedId; | ||||||
|  |     return fetch(cursor: null); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   late final String _feedId; | ||||||
|  |   ValueNotifier<int> totalCount = ValueNotifier(0); | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   Future<CursorPagingData<SnWebArticle>> fetch({ | ||||||
|  |     required String? cursor, | ||||||
|  |   }) async { | ||||||
|  |     final client = ref.read(apiClientProvider); | ||||||
|  |     final offset = cursor == null ? 0 : int.parse(cursor); | ||||||
|  |  | ||||||
|  |     final queryParams = {'offset': offset, 'take': _pageSize}; | ||||||
|  |  | ||||||
|  |     final response = await client.get( | ||||||
|  |       '/sphere/feeds/$_feedId/articles', | ||||||
|  |       queryParameters: queryParams, | ||||||
|  |     ); | ||||||
|  |     final total = int.parse(response.headers.value('X-Total') ?? '0'); | ||||||
|  |     totalCount.value = total; | ||||||
|  |     final List<dynamic> data = response.data; | ||||||
|  |     final articles = data.map((json) => SnWebArticle.fromJson(json)).toList(); | ||||||
|  |  | ||||||
|  |     final hasMore = offset + articles.length < total; | ||||||
|  |     final nextCursor = hasMore ? (offset + articles.length).toString() : null; | ||||||
|  |  | ||||||
|  |     return CursorPagingData( | ||||||
|  |       items: articles, | ||||||
|  |       hasMore: hasMore, | ||||||
|  |       nextCursor: nextCursor, | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   void dispose() { | ||||||
|  |     totalCount.dispose(); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /// Provider for web feed subscription status | ||||||
|  | @riverpod | ||||||
|  | Future<bool> marketplaceWebFeedSubscription( | ||||||
|  |   Ref ref, { | ||||||
|  |   required String feedId, | ||||||
|  | }) async { | ||||||
|  |   final api = ref.watch(apiClientProvider); | ||||||
|  |   try { | ||||||
|  |     await api.get('/sphere/feeds/$feedId/subscription'); | ||||||
|  |     // If not 404, consider subscribed | ||||||
|  |     return true; | ||||||
|  |   } on Object catch (e) { | ||||||
|  |     // Dio error handling agnostic: treat 404 as not-subscribed, rethrow others | ||||||
|  |     final msg = e.toString(); | ||||||
|  |     if (msg.contains('404')) return false; | ||||||
|  |     rethrow; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | class MarketplaceWebFeedDetailScreen extends HookConsumerWidget { | ||||||
|  |   final String id; | ||||||
|  |   const MarketplaceWebFeedDetailScreen({super.key, required this.id}); | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   Widget build(BuildContext context, WidgetRef ref) { | ||||||
|  |     final feed = ref.watch(marketplaceWebFeedProvider(id)); | ||||||
|  |     final subscribed = ref.watch( | ||||||
|  |       marketplaceWebFeedSubscriptionProvider(feedId: id), | ||||||
|  |     ); | ||||||
|  |  | ||||||
|  |     // Subscribe to web feed | ||||||
|  |     Future<void> subscribeToFeed() async { | ||||||
|  |       final apiClient = ref.watch(apiClientProvider); | ||||||
|  |       await apiClient.post('/sphere/feeds/$id/subscribe'); | ||||||
|  |       HapticFeedback.selectionClick(); | ||||||
|  |       ref.invalidate(marketplaceWebFeedSubscriptionProvider(feedId: id)); | ||||||
|  |       if (!context.mounted) return; | ||||||
|  |       showSnackBar('webFeedSubscribed'.tr()); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     // Unsubscribe from web feed | ||||||
|  |     Future<void> unsubscribeFromFeed() async { | ||||||
|  |       final apiClient = ref.watch(apiClientProvider); | ||||||
|  |       await apiClient.delete('/sphere/feeds/$id/subscribe'); | ||||||
|  |       HapticFeedback.selectionClick(); | ||||||
|  |       ref.invalidate(marketplaceWebFeedSubscriptionProvider(feedId: id)); | ||||||
|  |       if (!context.mounted) return; | ||||||
|  |       showSnackBar('webFeedUnsubscribed'.tr()); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     final feedNotifier = ref.watch( | ||||||
|  |       marketplaceWebFeedContentNotifierProvider(id).notifier, | ||||||
|  |     ); | ||||||
|  |  | ||||||
|  |     useEffect(() { | ||||||
|  |       return feedNotifier.dispose; | ||||||
|  |     }, []); | ||||||
|  |  | ||||||
|  |     return AppScaffold( | ||||||
|  |       appBar: AppBar(title: Text(feed.value?.title ?? 'loading'.tr())), | ||||||
|  |       body: Column( | ||||||
|  |         crossAxisAlignment: CrossAxisAlignment.stretch, | ||||||
|  |         children: [ | ||||||
|  |           // Feed meta | ||||||
|  |           feed | ||||||
|  |               .when( | ||||||
|  |                 data: | ||||||
|  |                     (data) => Column( | ||||||
|  |                       crossAxisAlignment: CrossAxisAlignment.stretch, | ||||||
|  |                       children: [ | ||||||
|  |                         Text(data.description ?? 'descriptionNone'.tr()), | ||||||
|  |                         Row( | ||||||
|  |                           spacing: 4, | ||||||
|  |                           children: [ | ||||||
|  |                             const Icon(Symbols.rss_feed, size: 16), | ||||||
|  |                             ListenableBuilder( | ||||||
|  |                               listenable: feedNotifier.totalCount, | ||||||
|  |                               builder: | ||||||
|  |                                   (context, _) => Text( | ||||||
|  |                                     'webFeedArticleCount'.plural( | ||||||
|  |                                       feedNotifier.totalCount.value, | ||||||
|  |                                     ), | ||||||
|  |                                   ), | ||||||
|  |                             ), | ||||||
|  |                           ], | ||||||
|  |                         ).opacity(0.85), | ||||||
|  |                         Row( | ||||||
|  |                           spacing: 4, | ||||||
|  |                           children: [ | ||||||
|  |                             const Icon(Symbols.link, size: 16), | ||||||
|  |                             SelectableText(data.url), | ||||||
|  |                           ], | ||||||
|  |                         ).opacity(0.85), | ||||||
|  |                       ], | ||||||
|  |                     ), | ||||||
|  |                 error: (err, _) => Text(err.toString()), | ||||||
|  |                 loading: () => CircularProgressIndicator().center(), | ||||||
|  |               ) | ||||||
|  |               .padding(horizontal: 24, vertical: 24), | ||||||
|  |           const Divider(height: 1), | ||||||
|  |           // Articles list | ||||||
|  |           Expanded( | ||||||
|  |             child: PagingHelperView( | ||||||
|  |               provider: marketplaceWebFeedContentNotifierProvider(id), | ||||||
|  |               futureRefreshable: | ||||||
|  |                   marketplaceWebFeedContentNotifierProvider(id).future, | ||||||
|  |               notifierRefreshable: | ||||||
|  |                   marketplaceWebFeedContentNotifierProvider(id).notifier, | ||||||
|  |               contentBuilder: | ||||||
|  |                   (data, widgetCount, endItemView) => ListView.separated( | ||||||
|  |                     padding: const EdgeInsets.symmetric( | ||||||
|  |                       horizontal: 24, | ||||||
|  |                       vertical: 20, | ||||||
|  |                     ), | ||||||
|  |                     itemCount: widgetCount, | ||||||
|  |                     itemBuilder: (context, index) { | ||||||
|  |                       if (index == widgetCount - 1) { | ||||||
|  |                         return endItemView; | ||||||
|  |                       } | ||||||
|  |  | ||||||
|  |                       final article = data.items[index]; | ||||||
|  |                       return WebArticleCard(article: article); | ||||||
|  |                     }, | ||||||
|  |                     separatorBuilder: (context, index) => const Gap(12), | ||||||
|  |                   ), | ||||||
|  |             ), | ||||||
|  |           ), | ||||||
|  |           Container( | ||||||
|  |             padding: EdgeInsets.only( | ||||||
|  |               bottom: 16 + MediaQuery.of(context).padding.bottom, | ||||||
|  |               left: 24, | ||||||
|  |               right: 24, | ||||||
|  |               top: 16, | ||||||
|  |             ), | ||||||
|  |             color: Theme.of(context).colorScheme.surfaceContainer, | ||||||
|  |             child: subscribed.when( | ||||||
|  |               data: | ||||||
|  |                   (isSubscribed) => FilledButton.icon( | ||||||
|  |                     onPressed: | ||||||
|  |                         isSubscribed ? unsubscribeFromFeed : subscribeToFeed, | ||||||
|  |                     icon: Icon( | ||||||
|  |                       isSubscribed ? Symbols.remove_circle : Symbols.add_circle, | ||||||
|  |                     ), | ||||||
|  |                     label: Text( | ||||||
|  |                       isSubscribed ? 'unsubscribe'.tr() : 'subscribe'.tr(), | ||||||
|  |                     ), | ||||||
|  |                   ), | ||||||
|  |               loading: | ||||||
|  |                   () => const SizedBox( | ||||||
|  |                     height: 32, | ||||||
|  |                     width: 32, | ||||||
|  |                     child: CircularProgressIndicator(strokeWidth: 2), | ||||||
|  |                   ), | ||||||
|  |               error: | ||||||
|  |                   (_, _) => OutlinedButton.icon( | ||||||
|  |                     onPressed: subscribeToFeed, | ||||||
|  |                     icon: const Icon(Symbols.add_circle), | ||||||
|  |                     label: Text('subscribe').tr(), | ||||||
|  |                   ), | ||||||
|  |             ), | ||||||
|  |           ), | ||||||
|  |         ], | ||||||
|  |       ), | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  | } | ||||||
							
								
								
									
										458
									
								
								lib/screens/discovery/feeds/feed_detail.g.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										458
									
								
								lib/screens/discovery/feeds/feed_detail.g.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,458 @@ | |||||||
|  | // GENERATED CODE - DO NOT MODIFY BY HAND | ||||||
|  |  | ||||||
|  | part of 'feed_detail.dart'; | ||||||
|  |  | ||||||
|  | // ************************************************************************** | ||||||
|  | // RiverpodGenerator | ||||||
|  | // ************************************************************************** | ||||||
|  |  | ||||||
|  | String _$marketplaceWebFeedHash() => | ||||||
|  |     r'8383f94f1bc272b903c341b8d95000313b69d14c'; | ||||||
|  |  | ||||||
|  | /// 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)); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /// See also [marketplaceWebFeed]. | ||||||
|  | @ProviderFor(marketplaceWebFeed) | ||||||
|  | const marketplaceWebFeedProvider = MarketplaceWebFeedFamily(); | ||||||
|  |  | ||||||
|  | /// See also [marketplaceWebFeed]. | ||||||
|  | class MarketplaceWebFeedFamily extends Family<AsyncValue<SnWebFeed>> { | ||||||
|  |   /// See also [marketplaceWebFeed]. | ||||||
|  |   const MarketplaceWebFeedFamily(); | ||||||
|  |  | ||||||
|  |   /// See also [marketplaceWebFeed]. | ||||||
|  |   MarketplaceWebFeedProvider call(String feedId) { | ||||||
|  |     return MarketplaceWebFeedProvider(feedId); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   MarketplaceWebFeedProvider getProviderOverride( | ||||||
|  |     covariant MarketplaceWebFeedProvider provider, | ||||||
|  |   ) { | ||||||
|  |     return call(provider.feedId); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   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'marketplaceWebFeedProvider'; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /// See also [marketplaceWebFeed]. | ||||||
|  | class MarketplaceWebFeedProvider extends AutoDisposeFutureProvider<SnWebFeed> { | ||||||
|  |   /// See also [marketplaceWebFeed]. | ||||||
|  |   MarketplaceWebFeedProvider(String feedId) | ||||||
|  |     : this._internal( | ||||||
|  |         (ref) => marketplaceWebFeed(ref as MarketplaceWebFeedRef, feedId), | ||||||
|  |         from: marketplaceWebFeedProvider, | ||||||
|  |         name: r'marketplaceWebFeedProvider', | ||||||
|  |         debugGetCreateSourceHash: | ||||||
|  |             const bool.fromEnvironment('dart.vm.product') | ||||||
|  |                 ? null | ||||||
|  |                 : _$marketplaceWebFeedHash, | ||||||
|  |         dependencies: MarketplaceWebFeedFamily._dependencies, | ||||||
|  |         allTransitiveDependencies: | ||||||
|  |             MarketplaceWebFeedFamily._allTransitiveDependencies, | ||||||
|  |         feedId: feedId, | ||||||
|  |       ); | ||||||
|  |  | ||||||
|  |   MarketplaceWebFeedProvider._internal( | ||||||
|  |     super._createNotifier, { | ||||||
|  |     required super.name, | ||||||
|  |     required super.dependencies, | ||||||
|  |     required super.allTransitiveDependencies, | ||||||
|  |     required super.debugGetCreateSourceHash, | ||||||
|  |     required super.from, | ||||||
|  |     required this.feedId, | ||||||
|  |   }) : super.internal(); | ||||||
|  |  | ||||||
|  |   final String feedId; | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   Override overrideWith( | ||||||
|  |     FutureOr<SnWebFeed> Function(MarketplaceWebFeedRef provider) create, | ||||||
|  |   ) { | ||||||
|  |     return ProviderOverride( | ||||||
|  |       origin: this, | ||||||
|  |       override: MarketplaceWebFeedProvider._internal( | ||||||
|  |         (ref) => create(ref as MarketplaceWebFeedRef), | ||||||
|  |         from: from, | ||||||
|  |         name: null, | ||||||
|  |         dependencies: null, | ||||||
|  |         allTransitiveDependencies: null, | ||||||
|  |         debugGetCreateSourceHash: null, | ||||||
|  |         feedId: feedId, | ||||||
|  |       ), | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   AutoDisposeFutureProviderElement<SnWebFeed> createElement() { | ||||||
|  |     return _MarketplaceWebFeedProviderElement(this); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   bool operator ==(Object other) { | ||||||
|  |     return other is MarketplaceWebFeedProvider && other.feedId == feedId; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   int get hashCode { | ||||||
|  |     var hash = _SystemHash.combine(0, runtimeType.hashCode); | ||||||
|  |     hash = _SystemHash.combine(hash, feedId.hashCode); | ||||||
|  |  | ||||||
|  |     return _SystemHash.finish(hash); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @Deprecated('Will be removed in 3.0. Use Ref instead') | ||||||
|  | // ignore: unused_element | ||||||
|  | mixin MarketplaceWebFeedRef on AutoDisposeFutureProviderRef<SnWebFeed> { | ||||||
|  |   /// The parameter `feedId` of this provider. | ||||||
|  |   String get feedId; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | class _MarketplaceWebFeedProviderElement | ||||||
|  |     extends AutoDisposeFutureProviderElement<SnWebFeed> | ||||||
|  |     with MarketplaceWebFeedRef { | ||||||
|  |   _MarketplaceWebFeedProviderElement(super.provider); | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   String get feedId => (origin as MarketplaceWebFeedProvider).feedId; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | String _$marketplaceWebFeedSubscriptionHash() => | ||||||
|  |     r'2ff06a48ed7d4236b57412ecca55e94c0a0b6330'; | ||||||
|  |  | ||||||
|  | /// Provider for web feed subscription status | ||||||
|  | /// | ||||||
|  | /// Copied from [marketplaceWebFeedSubscription]. | ||||||
|  | @ProviderFor(marketplaceWebFeedSubscription) | ||||||
|  | const marketplaceWebFeedSubscriptionProvider = | ||||||
|  |     MarketplaceWebFeedSubscriptionFamily(); | ||||||
|  |  | ||||||
|  | /// Provider for web feed subscription status | ||||||
|  | /// | ||||||
|  | /// Copied from [marketplaceWebFeedSubscription]. | ||||||
|  | class MarketplaceWebFeedSubscriptionFamily extends Family<AsyncValue<bool>> { | ||||||
|  |   /// Provider for web feed subscription status | ||||||
|  |   /// | ||||||
|  |   /// Copied from [marketplaceWebFeedSubscription]. | ||||||
|  |   const MarketplaceWebFeedSubscriptionFamily(); | ||||||
|  |  | ||||||
|  |   /// Provider for web feed subscription status | ||||||
|  |   /// | ||||||
|  |   /// Copied from [marketplaceWebFeedSubscription]. | ||||||
|  |   MarketplaceWebFeedSubscriptionProvider call({required String feedId}) { | ||||||
|  |     return MarketplaceWebFeedSubscriptionProvider(feedId: feedId); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   MarketplaceWebFeedSubscriptionProvider getProviderOverride( | ||||||
|  |     covariant MarketplaceWebFeedSubscriptionProvider provider, | ||||||
|  |   ) { | ||||||
|  |     return call(feedId: provider.feedId); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   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'marketplaceWebFeedSubscriptionProvider'; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /// Provider for web feed subscription status | ||||||
|  | /// | ||||||
|  | /// Copied from [marketplaceWebFeedSubscription]. | ||||||
|  | class MarketplaceWebFeedSubscriptionProvider | ||||||
|  |     extends AutoDisposeFutureProvider<bool> { | ||||||
|  |   /// Provider for web feed subscription status | ||||||
|  |   /// | ||||||
|  |   /// Copied from [marketplaceWebFeedSubscription]. | ||||||
|  |   MarketplaceWebFeedSubscriptionProvider({required String feedId}) | ||||||
|  |     : this._internal( | ||||||
|  |         (ref) => marketplaceWebFeedSubscription( | ||||||
|  |           ref as MarketplaceWebFeedSubscriptionRef, | ||||||
|  |           feedId: feedId, | ||||||
|  |         ), | ||||||
|  |         from: marketplaceWebFeedSubscriptionProvider, | ||||||
|  |         name: r'marketplaceWebFeedSubscriptionProvider', | ||||||
|  |         debugGetCreateSourceHash: | ||||||
|  |             const bool.fromEnvironment('dart.vm.product') | ||||||
|  |                 ? null | ||||||
|  |                 : _$marketplaceWebFeedSubscriptionHash, | ||||||
|  |         dependencies: MarketplaceWebFeedSubscriptionFamily._dependencies, | ||||||
|  |         allTransitiveDependencies: | ||||||
|  |             MarketplaceWebFeedSubscriptionFamily._allTransitiveDependencies, | ||||||
|  |         feedId: feedId, | ||||||
|  |       ); | ||||||
|  |  | ||||||
|  |   MarketplaceWebFeedSubscriptionProvider._internal( | ||||||
|  |     super._createNotifier, { | ||||||
|  |     required super.name, | ||||||
|  |     required super.dependencies, | ||||||
|  |     required super.allTransitiveDependencies, | ||||||
|  |     required super.debugGetCreateSourceHash, | ||||||
|  |     required super.from, | ||||||
|  |     required this.feedId, | ||||||
|  |   }) : super.internal(); | ||||||
|  |  | ||||||
|  |   final String feedId; | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   Override overrideWith( | ||||||
|  |     FutureOr<bool> Function(MarketplaceWebFeedSubscriptionRef provider) create, | ||||||
|  |   ) { | ||||||
|  |     return ProviderOverride( | ||||||
|  |       origin: this, | ||||||
|  |       override: MarketplaceWebFeedSubscriptionProvider._internal( | ||||||
|  |         (ref) => create(ref as MarketplaceWebFeedSubscriptionRef), | ||||||
|  |         from: from, | ||||||
|  |         name: null, | ||||||
|  |         dependencies: null, | ||||||
|  |         allTransitiveDependencies: null, | ||||||
|  |         debugGetCreateSourceHash: null, | ||||||
|  |         feedId: feedId, | ||||||
|  |       ), | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   AutoDisposeFutureProviderElement<bool> createElement() { | ||||||
|  |     return _MarketplaceWebFeedSubscriptionProviderElement(this); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   bool operator ==(Object other) { | ||||||
|  |     return other is MarketplaceWebFeedSubscriptionProvider && | ||||||
|  |         other.feedId == feedId; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   int get hashCode { | ||||||
|  |     var hash = _SystemHash.combine(0, runtimeType.hashCode); | ||||||
|  |     hash = _SystemHash.combine(hash, feedId.hashCode); | ||||||
|  |  | ||||||
|  |     return _SystemHash.finish(hash); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @Deprecated('Will be removed in 3.0. Use Ref instead') | ||||||
|  | // ignore: unused_element | ||||||
|  | mixin MarketplaceWebFeedSubscriptionRef on AutoDisposeFutureProviderRef<bool> { | ||||||
|  |   /// The parameter `feedId` of this provider. | ||||||
|  |   String get feedId; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | class _MarketplaceWebFeedSubscriptionProviderElement | ||||||
|  |     extends AutoDisposeFutureProviderElement<bool> | ||||||
|  |     with MarketplaceWebFeedSubscriptionRef { | ||||||
|  |   _MarketplaceWebFeedSubscriptionProviderElement(super.provider); | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   String get feedId => | ||||||
|  |       (origin as MarketplaceWebFeedSubscriptionProvider).feedId; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | String _$marketplaceWebFeedContentNotifierHash() => | ||||||
|  |     r'25688082884cb824eeff300888ba38c9748295dc'; | ||||||
|  |  | ||||||
|  | abstract class _$MarketplaceWebFeedContentNotifier | ||||||
|  |     extends BuildlessAutoDisposeAsyncNotifier<CursorPagingData<SnWebArticle>> { | ||||||
|  |   late final String feedId; | ||||||
|  |  | ||||||
|  |   FutureOr<CursorPagingData<SnWebArticle>> build(String feedId); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /// Provider for web feed articles content | ||||||
|  | /// | ||||||
|  | /// Copied from [MarketplaceWebFeedContentNotifier]. | ||||||
|  | @ProviderFor(MarketplaceWebFeedContentNotifier) | ||||||
|  | const marketplaceWebFeedContentNotifierProvider = | ||||||
|  |     MarketplaceWebFeedContentNotifierFamily(); | ||||||
|  |  | ||||||
|  | /// Provider for web feed articles content | ||||||
|  | /// | ||||||
|  | /// Copied from [MarketplaceWebFeedContentNotifier]. | ||||||
|  | class MarketplaceWebFeedContentNotifierFamily | ||||||
|  |     extends Family<AsyncValue<CursorPagingData<SnWebArticle>>> { | ||||||
|  |   /// Provider for web feed articles content | ||||||
|  |   /// | ||||||
|  |   /// Copied from [MarketplaceWebFeedContentNotifier]. | ||||||
|  |   const MarketplaceWebFeedContentNotifierFamily(); | ||||||
|  |  | ||||||
|  |   /// Provider for web feed articles content | ||||||
|  |   /// | ||||||
|  |   /// Copied from [MarketplaceWebFeedContentNotifier]. | ||||||
|  |   MarketplaceWebFeedContentNotifierProvider call(String feedId) { | ||||||
|  |     return MarketplaceWebFeedContentNotifierProvider(feedId); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   MarketplaceWebFeedContentNotifierProvider getProviderOverride( | ||||||
|  |     covariant MarketplaceWebFeedContentNotifierProvider provider, | ||||||
|  |   ) { | ||||||
|  |     return call(provider.feedId); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   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'marketplaceWebFeedContentNotifierProvider'; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /// Provider for web feed articles content | ||||||
|  | /// | ||||||
|  | /// Copied from [MarketplaceWebFeedContentNotifier]. | ||||||
|  | class MarketplaceWebFeedContentNotifierProvider | ||||||
|  |     extends | ||||||
|  |         AutoDisposeAsyncNotifierProviderImpl< | ||||||
|  |           MarketplaceWebFeedContentNotifier, | ||||||
|  |           CursorPagingData<SnWebArticle> | ||||||
|  |         > { | ||||||
|  |   /// Provider for web feed articles content | ||||||
|  |   /// | ||||||
|  |   /// Copied from [MarketplaceWebFeedContentNotifier]. | ||||||
|  |   MarketplaceWebFeedContentNotifierProvider(String feedId) | ||||||
|  |     : this._internal( | ||||||
|  |         () => MarketplaceWebFeedContentNotifier()..feedId = feedId, | ||||||
|  |         from: marketplaceWebFeedContentNotifierProvider, | ||||||
|  |         name: r'marketplaceWebFeedContentNotifierProvider', | ||||||
|  |         debugGetCreateSourceHash: | ||||||
|  |             const bool.fromEnvironment('dart.vm.product') | ||||||
|  |                 ? null | ||||||
|  |                 : _$marketplaceWebFeedContentNotifierHash, | ||||||
|  |         dependencies: MarketplaceWebFeedContentNotifierFamily._dependencies, | ||||||
|  |         allTransitiveDependencies: | ||||||
|  |             MarketplaceWebFeedContentNotifierFamily._allTransitiveDependencies, | ||||||
|  |         feedId: feedId, | ||||||
|  |       ); | ||||||
|  |  | ||||||
|  |   MarketplaceWebFeedContentNotifierProvider._internal( | ||||||
|  |     super._createNotifier, { | ||||||
|  |     required super.name, | ||||||
|  |     required super.dependencies, | ||||||
|  |     required super.allTransitiveDependencies, | ||||||
|  |     required super.debugGetCreateSourceHash, | ||||||
|  |     required super.from, | ||||||
|  |     required this.feedId, | ||||||
|  |   }) : super.internal(); | ||||||
|  |  | ||||||
|  |   final String feedId; | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   FutureOr<CursorPagingData<SnWebArticle>> runNotifierBuild( | ||||||
|  |     covariant MarketplaceWebFeedContentNotifier notifier, | ||||||
|  |   ) { | ||||||
|  |     return notifier.build(feedId); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   Override overrideWith(MarketplaceWebFeedContentNotifier Function() create) { | ||||||
|  |     return ProviderOverride( | ||||||
|  |       origin: this, | ||||||
|  |       override: MarketplaceWebFeedContentNotifierProvider._internal( | ||||||
|  |         () => create()..feedId = feedId, | ||||||
|  |         from: from, | ||||||
|  |         name: null, | ||||||
|  |         dependencies: null, | ||||||
|  |         allTransitiveDependencies: null, | ||||||
|  |         debugGetCreateSourceHash: null, | ||||||
|  |         feedId: feedId, | ||||||
|  |       ), | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   AutoDisposeAsyncNotifierProviderElement< | ||||||
|  |     MarketplaceWebFeedContentNotifier, | ||||||
|  |     CursorPagingData<SnWebArticle> | ||||||
|  |   > | ||||||
|  |   createElement() { | ||||||
|  |     return _MarketplaceWebFeedContentNotifierProviderElement(this); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   bool operator ==(Object other) { | ||||||
|  |     return other is MarketplaceWebFeedContentNotifierProvider && | ||||||
|  |         other.feedId == feedId; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   int get hashCode { | ||||||
|  |     var hash = _SystemHash.combine(0, runtimeType.hashCode); | ||||||
|  |     hash = _SystemHash.combine(hash, feedId.hashCode); | ||||||
|  |  | ||||||
|  |     return _SystemHash.finish(hash); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @Deprecated('Will be removed in 3.0. Use Ref instead') | ||||||
|  | // ignore: unused_element | ||||||
|  | mixin MarketplaceWebFeedContentNotifierRef | ||||||
|  |     on AutoDisposeAsyncNotifierProviderRef<CursorPagingData<SnWebArticle>> { | ||||||
|  |   /// The parameter `feedId` of this provider. | ||||||
|  |   String get feedId; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | class _MarketplaceWebFeedContentNotifierProviderElement | ||||||
|  |     extends | ||||||
|  |         AutoDisposeAsyncNotifierProviderElement< | ||||||
|  |           MarketplaceWebFeedContentNotifier, | ||||||
|  |           CursorPagingData<SnWebArticle> | ||||||
|  |         > | ||||||
|  |     with MarketplaceWebFeedContentNotifierRef { | ||||||
|  |   _MarketplaceWebFeedContentNotifierProviderElement(super.provider); | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   String get feedId => | ||||||
|  |       (origin as MarketplaceWebFeedContentNotifierProvider).feedId; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // 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 | ||||||
							
								
								
									
										169
									
								
								lib/screens/discovery/feeds/feed_marketplace.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										169
									
								
								lib/screens/discovery/feeds/feed_marketplace.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,169 @@ | |||||||
|  | import 'package:easy_localization/easy_localization.dart'; | ||||||
|  | import 'package:flutter/material.dart'; | ||||||
|  | import 'package:flutter_hooks/flutter_hooks.dart'; | ||||||
|  | import 'package:go_router/go_router.dart'; | ||||||
|  | import 'package:gap/gap.dart'; | ||||||
|  | import 'package:hooks_riverpod/hooks_riverpod.dart'; | ||||||
|  | import 'package:island/models/webfeed.dart'; | ||||||
|  | import 'package:island/pods/network.dart'; | ||||||
|  | import 'package:island/widgets/app_scaffold.dart'; | ||||||
|  | import 'package:material_symbols_icons/symbols.dart'; | ||||||
|  | import 'dart:async'; | ||||||
|  |  | ||||||
|  | import 'package:riverpod_annotation/riverpod_annotation.dart'; | ||||||
|  | import 'package:riverpod_paging_utils/riverpod_paging_utils.dart'; | ||||||
|  |  | ||||||
|  | part 'feed_marketplace.g.dart'; | ||||||
|  |  | ||||||
|  | @riverpod | ||||||
|  | class MarketplaceWebFeedsNotifier extends _$MarketplaceWebFeedsNotifier | ||||||
|  |     with CursorPagingNotifierMixin<SnWebFeed> { | ||||||
|  |   String? _query; | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   Future<CursorPagingData<SnWebFeed>> build({required String? query}) { | ||||||
|  |     _query = query; | ||||||
|  |     return fetch(cursor: null); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   Future<CursorPagingData<SnWebFeed>> fetch({required String? cursor}) async { | ||||||
|  |     final client = ref.read(apiClientProvider); | ||||||
|  |     final offset = cursor == null ? 0 : int.parse(cursor); | ||||||
|  |  | ||||||
|  |     final response = await client.get( | ||||||
|  |       '/sphere/feeds/explore', | ||||||
|  |       queryParameters: { | ||||||
|  |         'offset': offset, | ||||||
|  |         'take': 20, | ||||||
|  |         if (_query != null && _query!.isNotEmpty) 'query': _query, | ||||||
|  |       }, | ||||||
|  |     ); | ||||||
|  |  | ||||||
|  |     final total = int.parse(response.headers.value('X-Total') ?? '0'); | ||||||
|  |     final List<dynamic> data = response.data; | ||||||
|  |     final feeds = data.map((e) => SnWebFeed.fromJson(e)).toList(); | ||||||
|  |  | ||||||
|  |     final hasMore = offset + feeds.length < total; | ||||||
|  |     final nextCursor = hasMore ? (offset + feeds.length).toString() : null; | ||||||
|  |  | ||||||
|  |     return CursorPagingData( | ||||||
|  |       items: feeds, | ||||||
|  |       hasMore: hasMore, | ||||||
|  |       nextCursor: nextCursor, | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /// Marketplace screen for browsing web feeds. | ||||||
|  | class MarketplaceWebFeedsScreen extends HookConsumerWidget { | ||||||
|  |   const MarketplaceWebFeedsScreen({super.key}); | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   Widget build(BuildContext context, WidgetRef ref) { | ||||||
|  |     final query = useState<String?>(null); | ||||||
|  |     final searchController = useTextEditingController(); | ||||||
|  |     final focusNode = useFocusNode(); | ||||||
|  |     final debounceTimer = useState<Timer?>(null); | ||||||
|  |  | ||||||
|  |     // Clear search when query is cleared | ||||||
|  |     useEffect(() { | ||||||
|  |       if (query.value == null || query.value!.isEmpty) { | ||||||
|  |         searchController.clear(); | ||||||
|  |       } | ||||||
|  |       return null; | ||||||
|  |     }, [query.value]); | ||||||
|  |  | ||||||
|  |     // Clean up timer on dispose | ||||||
|  |     useEffect(() { | ||||||
|  |       return () { | ||||||
|  |         debounceTimer.value?.cancel(); | ||||||
|  |       }; | ||||||
|  |     }, []); | ||||||
|  |  | ||||||
|  |     return AppScaffold( | ||||||
|  |       appBar: AppBar( | ||||||
|  |         title: const Text('webFeeds').tr(), | ||||||
|  |         actions: const [Gap(8)], | ||||||
|  |       ), | ||||||
|  |       body: PagingHelperView( | ||||||
|  |         provider: marketplaceWebFeedsNotifierProvider(query: query.value), | ||||||
|  |         futureRefreshable: | ||||||
|  |             marketplaceWebFeedsNotifierProvider(query: query.value).future, | ||||||
|  |         notifierRefreshable: | ||||||
|  |             marketplaceWebFeedsNotifierProvider(query: query.value).notifier, | ||||||
|  |         contentBuilder: | ||||||
|  |             (data, widgetCount, endItemView) => Column( | ||||||
|  |               children: [ | ||||||
|  |                 // Search bar above the list | ||||||
|  |                 Padding( | ||||||
|  |                   padding: const EdgeInsets.all(16), | ||||||
|  |                   child: SearchBar( | ||||||
|  |                     elevation: WidgetStateProperty.all(4), | ||||||
|  |                     controller: searchController, | ||||||
|  |                     focusNode: focusNode, | ||||||
|  |                     hintText: 'search'.tr(), | ||||||
|  |                     leading: const Icon(Symbols.search), | ||||||
|  |                     padding: WidgetStateProperty.all( | ||||||
|  |                       const EdgeInsets.symmetric(horizontal: 24), | ||||||
|  |                     ), | ||||||
|  |                     onTapOutside: | ||||||
|  |                         (_) => FocusManager.instance.primaryFocus?.unfocus(), | ||||||
|  |                     trailing: [ | ||||||
|  |                       if (query.value != null && query.value!.isNotEmpty) | ||||||
|  |                         IconButton( | ||||||
|  |                           icon: const Icon(Symbols.close), | ||||||
|  |                           onPressed: () { | ||||||
|  |                             query.value = null; | ||||||
|  |                             searchController.clear(); | ||||||
|  |                             focusNode.unfocus(); | ||||||
|  |                           }, | ||||||
|  |                         ), | ||||||
|  |                     ], | ||||||
|  |                     onChanged: (value) { | ||||||
|  |                       // Debounce search to avoid excessive API calls | ||||||
|  |                       debounceTimer.value?.cancel(); | ||||||
|  |                       debounceTimer.value = Timer( | ||||||
|  |                         const Duration(milliseconds: 500), | ||||||
|  |                         () { | ||||||
|  |                           query.value = value.isEmpty ? null : value; | ||||||
|  |                         }, | ||||||
|  |                       ); | ||||||
|  |                     }, | ||||||
|  |                     onSubmitted: (value) { | ||||||
|  |                       query.value = value.isEmpty ? null : value; | ||||||
|  |                       focusNode.unfocus(); | ||||||
|  |                     }, | ||||||
|  |                   ), | ||||||
|  |                 ), | ||||||
|  |                 Expanded( | ||||||
|  |                   child: ListView.builder( | ||||||
|  |                     padding: EdgeInsets.zero, | ||||||
|  |                     itemCount: widgetCount, | ||||||
|  |                     itemBuilder: (context, index) { | ||||||
|  |                       if (index == widgetCount - 1) { | ||||||
|  |                         return endItemView; | ||||||
|  |                       } | ||||||
|  |  | ||||||
|  |                       final feed = data.items[index]; | ||||||
|  |                       return ListTile( | ||||||
|  |                         title: Text(feed.title), | ||||||
|  |                         subtitle: Text(feed.description ?? ''), | ||||||
|  |                         trailing: const Icon(Symbols.chevron_right), | ||||||
|  |                         onTap: () { | ||||||
|  |                           // Navigate to web feed detail page | ||||||
|  |                           context.pushNamed( | ||||||
|  |                             'webFeedDetail', | ||||||
|  |                             pathParameters: {'feedId': feed.id}, | ||||||
|  |                           ); | ||||||
|  |                         }, | ||||||
|  |                       ); | ||||||
|  |                     }, | ||||||
|  |                   ), | ||||||
|  |                 ), | ||||||
|  |               ], | ||||||
|  |             ), | ||||||
|  |       ), | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  | } | ||||||
							
								
								
									
										180
									
								
								lib/screens/discovery/feeds/feed_marketplace.g.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										180
									
								
								lib/screens/discovery/feeds/feed_marketplace.g.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,180 @@ | |||||||
|  | // GENERATED CODE - DO NOT MODIFY BY HAND | ||||||
|  |  | ||||||
|  | part of 'feed_marketplace.dart'; | ||||||
|  |  | ||||||
|  | // ************************************************************************** | ||||||
|  | // RiverpodGenerator | ||||||
|  | // ************************************************************************** | ||||||
|  |  | ||||||
|  | String _$marketplaceWebFeedsNotifierHash() => | ||||||
|  |     r'774b2985f2f7d61fe958f534f84e39f814327c4e'; | ||||||
|  |  | ||||||
|  | /// 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 _$MarketplaceWebFeedsNotifier | ||||||
|  |     extends BuildlessAutoDisposeAsyncNotifier<CursorPagingData<SnWebFeed>> { | ||||||
|  |   late final String? query; | ||||||
|  |  | ||||||
|  |   FutureOr<CursorPagingData<SnWebFeed>> build({required String? query}); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /// See also [MarketplaceWebFeedsNotifier]. | ||||||
|  | @ProviderFor(MarketplaceWebFeedsNotifier) | ||||||
|  | const marketplaceWebFeedsNotifierProvider = MarketplaceWebFeedsNotifierFamily(); | ||||||
|  |  | ||||||
|  | /// See also [MarketplaceWebFeedsNotifier]. | ||||||
|  | class MarketplaceWebFeedsNotifierFamily | ||||||
|  |     extends Family<AsyncValue<CursorPagingData<SnWebFeed>>> { | ||||||
|  |   /// See also [MarketplaceWebFeedsNotifier]. | ||||||
|  |   const MarketplaceWebFeedsNotifierFamily(); | ||||||
|  |  | ||||||
|  |   /// See also [MarketplaceWebFeedsNotifier]. | ||||||
|  |   MarketplaceWebFeedsNotifierProvider call({required String? query}) { | ||||||
|  |     return MarketplaceWebFeedsNotifierProvider(query: query); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   MarketplaceWebFeedsNotifierProvider getProviderOverride( | ||||||
|  |     covariant MarketplaceWebFeedsNotifierProvider provider, | ||||||
|  |   ) { | ||||||
|  |     return call(query: provider.query); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   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'marketplaceWebFeedsNotifierProvider'; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /// See also [MarketplaceWebFeedsNotifier]. | ||||||
|  | class MarketplaceWebFeedsNotifierProvider | ||||||
|  |     extends | ||||||
|  |         AutoDisposeAsyncNotifierProviderImpl< | ||||||
|  |           MarketplaceWebFeedsNotifier, | ||||||
|  |           CursorPagingData<SnWebFeed> | ||||||
|  |         > { | ||||||
|  |   /// See also [MarketplaceWebFeedsNotifier]. | ||||||
|  |   MarketplaceWebFeedsNotifierProvider({required String? query}) | ||||||
|  |     : this._internal( | ||||||
|  |         () => MarketplaceWebFeedsNotifier()..query = query, | ||||||
|  |         from: marketplaceWebFeedsNotifierProvider, | ||||||
|  |         name: r'marketplaceWebFeedsNotifierProvider', | ||||||
|  |         debugGetCreateSourceHash: | ||||||
|  |             const bool.fromEnvironment('dart.vm.product') | ||||||
|  |                 ? null | ||||||
|  |                 : _$marketplaceWebFeedsNotifierHash, | ||||||
|  |         dependencies: MarketplaceWebFeedsNotifierFamily._dependencies, | ||||||
|  |         allTransitiveDependencies: | ||||||
|  |             MarketplaceWebFeedsNotifierFamily._allTransitiveDependencies, | ||||||
|  |         query: query, | ||||||
|  |       ); | ||||||
|  |  | ||||||
|  |   MarketplaceWebFeedsNotifierProvider._internal( | ||||||
|  |     super._createNotifier, { | ||||||
|  |     required super.name, | ||||||
|  |     required super.dependencies, | ||||||
|  |     required super.allTransitiveDependencies, | ||||||
|  |     required super.debugGetCreateSourceHash, | ||||||
|  |     required super.from, | ||||||
|  |     required this.query, | ||||||
|  |   }) : super.internal(); | ||||||
|  |  | ||||||
|  |   final String? query; | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   FutureOr<CursorPagingData<SnWebFeed>> runNotifierBuild( | ||||||
|  |     covariant MarketplaceWebFeedsNotifier notifier, | ||||||
|  |   ) { | ||||||
|  |     return notifier.build(query: query); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   Override overrideWith(MarketplaceWebFeedsNotifier Function() create) { | ||||||
|  |     return ProviderOverride( | ||||||
|  |       origin: this, | ||||||
|  |       override: MarketplaceWebFeedsNotifierProvider._internal( | ||||||
|  |         () => create()..query = query, | ||||||
|  |         from: from, | ||||||
|  |         name: null, | ||||||
|  |         dependencies: null, | ||||||
|  |         allTransitiveDependencies: null, | ||||||
|  |         debugGetCreateSourceHash: null, | ||||||
|  |         query: query, | ||||||
|  |       ), | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   AutoDisposeAsyncNotifierProviderElement< | ||||||
|  |     MarketplaceWebFeedsNotifier, | ||||||
|  |     CursorPagingData<SnWebFeed> | ||||||
|  |   > | ||||||
|  |   createElement() { | ||||||
|  |     return _MarketplaceWebFeedsNotifierProviderElement(this); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   bool operator ==(Object other) { | ||||||
|  |     return other is MarketplaceWebFeedsNotifierProvider && other.query == query; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   int get hashCode { | ||||||
|  |     var hash = _SystemHash.combine(0, runtimeType.hashCode); | ||||||
|  |     hash = _SystemHash.combine(hash, query.hashCode); | ||||||
|  |  | ||||||
|  |     return _SystemHash.finish(hash); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @Deprecated('Will be removed in 3.0. Use Ref instead') | ||||||
|  | // ignore: unused_element | ||||||
|  | mixin MarketplaceWebFeedsNotifierRef | ||||||
|  |     on AutoDisposeAsyncNotifierProviderRef<CursorPagingData<SnWebFeed>> { | ||||||
|  |   /// The parameter `query` of this provider. | ||||||
|  |   String? get query; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | class _MarketplaceWebFeedsNotifierProviderElement | ||||||
|  |     extends | ||||||
|  |         AutoDisposeAsyncNotifierProviderElement< | ||||||
|  |           MarketplaceWebFeedsNotifier, | ||||||
|  |           CursorPagingData<SnWebFeed> | ||||||
|  |         > | ||||||
|  |     with MarketplaceWebFeedsNotifierRef { | ||||||
|  |   _MarketplaceWebFeedsNotifierProviderElement(super.provider); | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   String? get query => (origin as MarketplaceWebFeedsNotifierProvider).query; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // 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 | ||||||
| @@ -173,12 +173,60 @@ class ExploreScreen extends HookConsumerWidget { | |||||||
|                     ), |                     ), | ||||||
|                     tooltip: 'webArticlesStand'.tr(), |                     tooltip: 'webArticlesStand'.tr(), | ||||||
|                   ), |                   ), | ||||||
|                   IconButton( |                   PopupMenuButton( | ||||||
|                     onPressed: () { |                     itemBuilder: | ||||||
|                       context.pushNamed('postSearch'); |                         (context) => [ | ||||||
|                     }, |                           PopupMenuItem( | ||||||
|  |                             child: Row( | ||||||
|  |                               children: [ | ||||||
|  |                                 const Icon(Symbols.category), | ||||||
|  |                                 const Gap(12), | ||||||
|  |                                 Text('categories').tr(), | ||||||
|  |                               ], | ||||||
|  |                             ), | ||||||
|  |                             onTap: () { | ||||||
|  |                               context.pushNamed('postCategories'); | ||||||
|  |                             }, | ||||||
|  |                           ), | ||||||
|  |                           PopupMenuItem( | ||||||
|  |                             child: Row( | ||||||
|  |                               children: [ | ||||||
|  |                                 const Icon(Symbols.label), | ||||||
|  |                                 const Gap(12), | ||||||
|  |                                 Text('tags').tr(), | ||||||
|  |                               ], | ||||||
|  |                             ), | ||||||
|  |                             onTap: () { | ||||||
|  |                               context.pushNamed('postTags'); | ||||||
|  |                             }, | ||||||
|  |                           ), | ||||||
|  |                           PopupMenuItem( | ||||||
|  |                             child: Row( | ||||||
|  |                               children: [ | ||||||
|  |                                 const Icon(Symbols.shuffle), | ||||||
|  |                                 const Gap(12), | ||||||
|  |                                 Text('postShuffle').tr(), | ||||||
|  |                               ], | ||||||
|  |                             ), | ||||||
|  |                             onTap: () { | ||||||
|  |                               context.pushNamed('postShuffle'); | ||||||
|  |                             }, | ||||||
|  |                           ), | ||||||
|  |                           PopupMenuItem( | ||||||
|  |                             child: Row( | ||||||
|  |                               children: [ | ||||||
|  |                                 const Icon(Symbols.search), | ||||||
|  |                                 const Gap(12), | ||||||
|  |                                 Text('search').tr(), | ||||||
|  |                               ], | ||||||
|  |                             ), | ||||||
|  |                             onTap: () { | ||||||
|  |                               context.pushNamed('postSearch'); | ||||||
|  |                             }, | ||||||
|  |                           ), | ||||||
|  |                         ], | ||||||
|                     icon: Icon( |                     icon: Icon( | ||||||
|                       Symbols.search, |                       Symbols.action_key, | ||||||
|                       color: Theme.of(context).appBarTheme.foregroundColor!, |                       color: Theme.of(context).appBarTheme.foregroundColor!, | ||||||
|                     ), |                     ), | ||||||
|                     tooltip: 'search'.tr(), |                     tooltip: 'search'.tr(), | ||||||
| @@ -447,7 +495,7 @@ class _ActivityListView extends HookConsumerWidget { | |||||||
|           SliverToBoxAdapter( |           SliverToBoxAdapter( | ||||||
|             child: PostFeaturedList().padding(horizontal: 8, bottom: 4, top: 4), |             child: PostFeaturedList().padding(horizontal: 8, bottom: 4, top: 4), | ||||||
|           ), |           ), | ||||||
|         if (!contentOnly) |         if (!contentOnly && (notificationCount.value ?? 0) > 0) | ||||||
|           SliverToBoxAdapter( |           SliverToBoxAdapter( | ||||||
|             child: notificationIndicatorWidget( |             child: notificationIndicatorWidget( | ||||||
|               context, |               context, | ||||||
|   | |||||||
| @@ -130,6 +130,7 @@ class NotificationScreen extends HookConsumerWidget { | |||||||
|  |  | ||||||
|     return AppScaffold( |     return AppScaffold( | ||||||
|       appBar: AppBar( |       appBar: AppBar( | ||||||
|  |         leading: const PageBackButton(), | ||||||
|         title: const Text('notifications').tr(), |         title: const Text('notifications').tr(), | ||||||
|         actions: [ |         actions: [ | ||||||
|           IconButton( |           IconButton( | ||||||
|   | |||||||
							
								
								
									
										242
									
								
								lib/screens/posts/post_categories_list.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										242
									
								
								lib/screens/posts/post_categories_list.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,242 @@ | |||||||
|  | import 'dart:async'; | ||||||
|  | import 'package:easy_localization/easy_localization.dart'; | ||||||
|  | import 'package:flutter/material.dart'; | ||||||
|  | import 'package:go_router/go_router.dart'; | ||||||
|  | import 'package:hooks_riverpod/hooks_riverpod.dart'; | ||||||
|  | import 'package:island/models/post_category.dart'; | ||||||
|  | import 'package:island/models/post_tag.dart'; | ||||||
|  | import 'package:island/pods/network.dart'; | ||||||
|  | import 'package:island/widgets/app_scaffold.dart'; | ||||||
|  | import 'package:island/widgets/response.dart'; | ||||||
|  | import 'package:material_symbols_icons/symbols.dart'; | ||||||
|  | import 'package:riverpod_paging_utils/riverpod_paging_utils.dart'; | ||||||
|  |  | ||||||
|  | // Post Categories Notifier | ||||||
|  | final postCategoriesNotifierProvider = StateNotifierProvider.autoDispose< | ||||||
|  |   PostCategoriesNotifier, | ||||||
|  |   AsyncValue<CursorPagingData<SnPostCategory>> | ||||||
|  | >((ref) { | ||||||
|  |   return PostCategoriesNotifier(ref); | ||||||
|  | }); | ||||||
|  |  | ||||||
|  | class PostCategoriesNotifier | ||||||
|  |     extends StateNotifier<AsyncValue<CursorPagingData<SnPostCategory>>> { | ||||||
|  |   final AutoDisposeRef ref; | ||||||
|  |   static const int _pageSize = 20; | ||||||
|  |   bool _isLoading = false; | ||||||
|  |  | ||||||
|  |   PostCategoriesNotifier(this.ref) : super(const AsyncValue.loading()) { | ||||||
|  |     state = const AsyncValue.data( | ||||||
|  |       CursorPagingData(items: [], hasMore: false, nextCursor: null), | ||||||
|  |     ); | ||||||
|  |     fetch(cursor: null); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   Future<void> fetch({String? cursor}) async { | ||||||
|  |     if (_isLoading) return; | ||||||
|  |  | ||||||
|  |     _isLoading = true; | ||||||
|  |     if (cursor == null) { | ||||||
|  |       state = const AsyncValue.loading(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     try { | ||||||
|  |       final client = ref.read(apiClientProvider); | ||||||
|  |       final offset = cursor == null ? 0 : int.parse(cursor); | ||||||
|  |  | ||||||
|  |       final response = await client.get( | ||||||
|  |         '/sphere/posts/categories', | ||||||
|  |         queryParameters: { | ||||||
|  |           'offset': offset, | ||||||
|  |           'take': _pageSize, | ||||||
|  |           'order': 'usage', | ||||||
|  |         }, | ||||||
|  |       ); | ||||||
|  |  | ||||||
|  |       final data = response.data as List; | ||||||
|  |       final categories = | ||||||
|  |           data.map((json) => SnPostCategory.fromJson(json)).toList(); | ||||||
|  |       final hasMore = categories.length == _pageSize; | ||||||
|  |       final nextCursor = | ||||||
|  |           hasMore ? (offset + categories.length).toString() : null; | ||||||
|  |  | ||||||
|  |       state = AsyncValue.data( | ||||||
|  |         CursorPagingData( | ||||||
|  |           items: [...(state.value?.items ?? []), ...categories], | ||||||
|  |           hasMore: hasMore, | ||||||
|  |           nextCursor: nextCursor, | ||||||
|  |         ), | ||||||
|  |       ); | ||||||
|  |     } catch (e, stack) { | ||||||
|  |       state = AsyncValue.error(e, stack); | ||||||
|  |     } finally { | ||||||
|  |       _isLoading = false; | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Post Tags Notifier | ||||||
|  | final postTagsNotifierProvider = StateNotifierProvider.autoDispose< | ||||||
|  |   PostTagsNotifier, | ||||||
|  |   AsyncValue<CursorPagingData<SnPostTag>> | ||||||
|  | >((ref) { | ||||||
|  |   return PostTagsNotifier(ref); | ||||||
|  | }); | ||||||
|  |  | ||||||
|  | class PostTagsNotifier | ||||||
|  |     extends StateNotifier<AsyncValue<CursorPagingData<SnPostTag>>> { | ||||||
|  |   final AutoDisposeRef ref; | ||||||
|  |   static const int _pageSize = 20; | ||||||
|  |   bool _isLoading = false; | ||||||
|  |  | ||||||
|  |   PostTagsNotifier(this.ref) : super(const AsyncValue.loading()) { | ||||||
|  |     state = const AsyncValue.data( | ||||||
|  |       CursorPagingData(items: [], hasMore: false, nextCursor: null), | ||||||
|  |     ); | ||||||
|  |     fetch(cursor: null); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   Future<void> fetch({String? cursor}) async { | ||||||
|  |     if (_isLoading) return; | ||||||
|  |  | ||||||
|  |     _isLoading = true; | ||||||
|  |     if (cursor == null) { | ||||||
|  |       state = const AsyncValue.loading(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     try { | ||||||
|  |       final client = ref.read(apiClientProvider); | ||||||
|  |       final offset = cursor == null ? 0 : int.parse(cursor); | ||||||
|  |  | ||||||
|  |       final response = await client.get( | ||||||
|  |         '/sphere/posts/tags', | ||||||
|  |         queryParameters: { | ||||||
|  |           'offset': offset, | ||||||
|  |           'take': _pageSize, | ||||||
|  |           'order': 'usage', | ||||||
|  |         }, | ||||||
|  |       ); | ||||||
|  |  | ||||||
|  |       final data = response.data as List; | ||||||
|  |       final tags = data.map((json) => SnPostTag.fromJson(json)).toList(); | ||||||
|  |       final hasMore = tags.length == _pageSize; | ||||||
|  |       final nextCursor = hasMore ? (offset + tags.length).toString() : null; | ||||||
|  |  | ||||||
|  |       state = AsyncValue.data( | ||||||
|  |         CursorPagingData( | ||||||
|  |           items: [...(state.value?.items ?? []), ...tags], | ||||||
|  |           hasMore: hasMore, | ||||||
|  |           nextCursor: nextCursor, | ||||||
|  |         ), | ||||||
|  |       ); | ||||||
|  |     } catch (e, stack) { | ||||||
|  |       state = AsyncValue.error(e, stack); | ||||||
|  |     } finally { | ||||||
|  |       _isLoading = false; | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | class PostCategoriesListScreen extends ConsumerWidget { | ||||||
|  |   const PostCategoriesListScreen({super.key}); | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   Widget build(BuildContext context, WidgetRef ref) { | ||||||
|  |     final categoriesState = ref.watch(postCategoriesNotifierProvider); | ||||||
|  |  | ||||||
|  |     return AppScaffold( | ||||||
|  |       appBar: AppBar(title: const Text('categories').tr()), | ||||||
|  |       body: categoriesState.when( | ||||||
|  |         data: (data) { | ||||||
|  |           if (data.items.isEmpty) { | ||||||
|  |             return const Center(child: Text('No categories found')); | ||||||
|  |           } | ||||||
|  |           return ListView.builder( | ||||||
|  |             padding: EdgeInsets.zero, | ||||||
|  |             itemCount: data.items.length + (data.hasMore ? 1 : 0), | ||||||
|  |             itemBuilder: (context, index) { | ||||||
|  |               if (index >= data.items.length) { | ||||||
|  |                 ref | ||||||
|  |                     .read(postCategoriesNotifierProvider.notifier) | ||||||
|  |                     .fetch(cursor: data.nextCursor); | ||||||
|  |                 return const Center(child: CircularProgressIndicator()); | ||||||
|  |               } | ||||||
|  |               final category = data.items[index]; | ||||||
|  |               return ListTile( | ||||||
|  |                 leading: const Icon(Symbols.category), | ||||||
|  |                 contentPadding: EdgeInsets.symmetric(horizontal: 24), | ||||||
|  |                 trailing: const Icon(Symbols.chevron_right), | ||||||
|  |                 title: Text(category.categoryDisplayTitle), | ||||||
|  |                 subtitle: Text('postCount'.plural(category.usage)), | ||||||
|  |                 onTap: () { | ||||||
|  |                   context.pushNamed( | ||||||
|  |                     'postCategoryDetail', | ||||||
|  |                     pathParameters: {'slug': category.slug}, | ||||||
|  |                   ); | ||||||
|  |                 }, | ||||||
|  |               ); | ||||||
|  |             }, | ||||||
|  |           ); | ||||||
|  |         }, | ||||||
|  |         loading: () => const Center(child: CircularProgressIndicator()), | ||||||
|  |         error: | ||||||
|  |             (error, stack) => ResponseErrorWidget( | ||||||
|  |               error: error, | ||||||
|  |               onRetry: () => ref.invalidate(postCategoriesNotifierProvider), | ||||||
|  |             ), | ||||||
|  |       ), | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | class PostTagsListScreen extends ConsumerWidget { | ||||||
|  |   const PostTagsListScreen({super.key}); | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   Widget build(BuildContext context, WidgetRef ref) { | ||||||
|  |     final tagsState = ref.watch(postTagsNotifierProvider); | ||||||
|  |  | ||||||
|  |     return AppScaffold( | ||||||
|  |       appBar: AppBar(title: const Text('tags').tr()), | ||||||
|  |       body: tagsState.when( | ||||||
|  |         data: (data) { | ||||||
|  |           if (data.items.isEmpty) { | ||||||
|  |             return const Center(child: Text('No tags found')); | ||||||
|  |           } | ||||||
|  |           return ListView.builder( | ||||||
|  |             padding: EdgeInsets.zero, | ||||||
|  |             itemCount: data.items.length + (data.hasMore ? 1 : 0), | ||||||
|  |             itemBuilder: (context, index) { | ||||||
|  |               if (index >= data.items.length) { | ||||||
|  |                 ref | ||||||
|  |                     .read(postTagsNotifierProvider.notifier) | ||||||
|  |                     .fetch(cursor: data.nextCursor); | ||||||
|  |                 return const Center(child: CircularProgressIndicator()); | ||||||
|  |               } | ||||||
|  |               final tag = data.items[index]; | ||||||
|  |               return ListTile( | ||||||
|  |                 title: Text(tag.name ?? '#${tag.slug}'), | ||||||
|  |                 contentPadding: EdgeInsets.symmetric(horizontal: 24), | ||||||
|  |                 leading: const Icon(Symbols.label), | ||||||
|  |                 trailing: const Icon(Symbols.chevron_right), | ||||||
|  |                 subtitle: Text('postCount'.plural(tag.usage)), | ||||||
|  |                 onTap: () { | ||||||
|  |                   context.pushNamed( | ||||||
|  |                     'postTagDetail', | ||||||
|  |                     pathParameters: {'slug': tag.slug}, | ||||||
|  |                   ); | ||||||
|  |                 }, | ||||||
|  |               ); | ||||||
|  |             }, | ||||||
|  |           ); | ||||||
|  |         }, | ||||||
|  |         loading: () => const Center(child: CircularProgressIndicator()), | ||||||
|  |         error: | ||||||
|  |             (error, stack) => ResponseErrorWidget( | ||||||
|  |               error: error, | ||||||
|  |               onRetry: () => ref.invalidate(postTagsNotifierProvider), | ||||||
|  |             ), | ||||||
|  |       ), | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  | } | ||||||
| @@ -1,3 +1,4 @@ | |||||||
|  | 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:hooks_riverpod/hooks_riverpod.dart'; | import 'package:hooks_riverpod/hooks_riverpod.dart'; | ||||||
| @@ -8,6 +9,7 @@ import 'package:island/widgets/app_scaffold.dart'; | |||||||
| import 'package:island/widgets/post/post_item.dart'; | import 'package:island/widgets/post/post_item.dart'; | ||||||
| import 'package:island/widgets/post/post_quick_reply.dart'; | import 'package:island/widgets/post/post_quick_reply.dart'; | ||||||
| import 'package:island/widgets/post/post_replies.dart'; | import 'package:island/widgets/post/post_replies.dart'; | ||||||
|  | import 'package:island/widgets/response.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'; | ||||||
|  |  | ||||||
| @@ -55,7 +57,10 @@ class PostDetailScreen extends HookConsumerWidget { | |||||||
|  |  | ||||||
|     return AppScaffold( |     return AppScaffold( | ||||||
|       isNoBackground: false, |       isNoBackground: false, | ||||||
|       appBar: AppBar(title: const Text('Post')), |       appBar: AppBar( | ||||||
|  |         leading: const PageBackButton(), | ||||||
|  |         title: Text('postDetail').tr(), | ||||||
|  |       ), | ||||||
|       body: postState.when( |       body: postState.when( | ||||||
|         data: (post) { |         data: (post) { | ||||||
|           return Stack( |           return Stack( | ||||||
| @@ -117,8 +122,12 @@ class PostDetailScreen extends HookConsumerWidget { | |||||||
|             ], |             ], | ||||||
|           ); |           ); | ||||||
|         }, |         }, | ||||||
|         loading: () => const Center(child: CircularProgressIndicator()), |         loading: () => ResponseLoadingWidget(), | ||||||
|         error: (e, _) => Text('Error: $e'), |         error: | ||||||
|  |             (e, _) => ResponseErrorWidget( | ||||||
|  |               error: e, | ||||||
|  |               onRetry: () => ref.invalidate(postStateProvider(id)), | ||||||
|  |             ), | ||||||
|       ), |       ), | ||||||
|     ); |     ); | ||||||
|   } |   } | ||||||
|   | |||||||
| @@ -196,7 +196,8 @@ class PublisherProfileScreen extends HookConsumerWidget { | |||||||
|                       'publisherBelongsTo'.tr(args: ['@${data.account!.name}']), |                       'publisherBelongsTo'.tr(args: ['@${data.account!.name}']), | ||||||
|                     ).fontSize(14), |                     ).fontSize(14), | ||||||
|                   ], |                   ], | ||||||
|                 ).opacity(0.85).padding(bottom: 6), |                 ).opacity(0.85), | ||||||
|  |               const Gap(4), | ||||||
|               if (data.type == 0 && data.account != null) |               if (data.type == 0 && data.account != null) | ||||||
|                 AccountStatusWidget( |                 AccountStatusWidget( | ||||||
|                   uname: data.account!.name, |                   uname: data.account!.name, | ||||||
|   | |||||||
| @@ -142,7 +142,9 @@ class RealmDetailScreen extends HookConsumerWidget { | |||||||
|             error: (error, _) => Center(child: Text('Error: $error')), |             error: (error, _) => Center(child: Text('Error: $error')), | ||||||
|             data: (rooms) { |             data: (rooms) { | ||||||
|               if (rooms.isEmpty) { |               if (rooms.isEmpty) { | ||||||
|                 return const SliverToBoxAdapter(child: SizedBox.shrink()); |                 return Text( | ||||||
|  |                   'dataEmpty', | ||||||
|  |                 ).tr().padding(horizontal: 24, bottom: 12); | ||||||
|               } |               } | ||||||
|               return Column( |               return Column( | ||||||
|                 children: [ |                 children: [ | ||||||
| @@ -518,9 +520,11 @@ class _RealmActionMenu extends HookConsumerWidget { | |||||||
| class RealmMemberListNotifier extends _$RealmMemberListNotifier | class RealmMemberListNotifier extends _$RealmMemberListNotifier | ||||||
|     with CursorPagingNotifierMixin<SnRealmMember> { |     with CursorPagingNotifierMixin<SnRealmMember> { | ||||||
|   static const int _pageSize = 20; |   static const int _pageSize = 20; | ||||||
|  |   ValueNotifier<int> totalCount = ValueNotifier(0); | ||||||
|  |  | ||||||
|   @override |   @override | ||||||
|   Future<CursorPagingData<SnRealmMember>> build(String realmSlug) async { |   Future<CursorPagingData<SnRealmMember>> build(String realmSlug) async { | ||||||
|  |     totalCount.value = 0; | ||||||
|     return fetch(); |     return fetch(); | ||||||
|   } |   } | ||||||
|  |  | ||||||
| @@ -539,6 +543,7 @@ class RealmMemberListNotifier extends _$RealmMemberListNotifier | |||||||
|     ); |     ); | ||||||
|  |  | ||||||
|     final total = int.parse(response.headers.value('X-Total') ?? '0'); |     final total = int.parse(response.headers.value('X-Total') ?? '0'); | ||||||
|  |     totalCount.value = total; | ||||||
|     final List<dynamic> data = response.data; |     final List<dynamic> data = response.data; | ||||||
|     final members = data.map((e) => SnRealmMember.fromJson(e)).toList(); |     final members = data.map((e) => SnRealmMember.fromJson(e)).toList(); | ||||||
|  |  | ||||||
| @@ -551,52 +556,9 @@ class RealmMemberListNotifier extends _$RealmMemberListNotifier | |||||||
|       nextCursor: nextCursor, |       nextCursor: nextCursor, | ||||||
|     ); |     ); | ||||||
|   } |   } | ||||||
| } |  | ||||||
|  |  | ||||||
| // Keep the old provider for backward compatibility |   void dispose() { | ||||||
| final realmMemberStateProvider = |     totalCount.dispose(); | ||||||
|     StateNotifierProvider.family<RealmMemberNotifier, RealmMemberState, String>( |  | ||||||
|       (ref, realmSlug) { |  | ||||||
|         final apiClient = ref.watch(apiClientProvider); |  | ||||||
|         return RealmMemberNotifier(apiClient, realmSlug); |  | ||||||
|       }, |  | ||||||
|     ); |  | ||||||
|  |  | ||||||
| class RealmMemberNotifier extends StateNotifier<RealmMemberState> { |  | ||||||
|   final String realmSlug; |  | ||||||
|   final Dio _apiClient; |  | ||||||
|  |  | ||||||
|   RealmMemberNotifier(this._apiClient, this.realmSlug) |  | ||||||
|     : super(const RealmMemberState(members: [], isLoading: false, total: 0)); |  | ||||||
|  |  | ||||||
|   Future<void> loadMore({int offset = 0, int take = 20}) async { |  | ||||||
|     if (state.isLoading) return; |  | ||||||
|     if (state.total > 0 && state.members.length >= state.total) return; |  | ||||||
|  |  | ||||||
|     state = state.copyWith(isLoading: true, error: null); |  | ||||||
|  |  | ||||||
|     try { |  | ||||||
|       final response = await _apiClient.get( |  | ||||||
|         '/sphere/realms/$realmSlug/members', |  | ||||||
|         queryParameters: {'offset': offset, 'take': take, 'withStatus': true}, |  | ||||||
|       ); |  | ||||||
|  |  | ||||||
|       final total = int.parse(response.headers.value('X-Total') ?? '0'); |  | ||||||
|       final List<dynamic> data = response.data; |  | ||||||
|       final members = data.map((e) => SnRealmMember.fromJson(e)).toList(); |  | ||||||
|  |  | ||||||
|       state = state.copyWith( |  | ||||||
|         members: [...state.members, ...members], |  | ||||||
|         total: total, |  | ||||||
|         isLoading: false, |  | ||||||
|       ); |  | ||||||
|     } catch (e) { |  | ||||||
|       state = state.copyWith(error: e.toString(), isLoading: false); |  | ||||||
|     } |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   void reset() { |  | ||||||
|     state = const RealmMemberState(members: [], isLoading: false, total: 0); |  | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -608,18 +570,10 @@ class _RealmMemberListSheet extends HookConsumerWidget { | |||||||
|   Widget build(BuildContext context, WidgetRef ref) { |   Widget build(BuildContext context, WidgetRef ref) { | ||||||
|     final realmIdentity = ref.watch(realmIdentityProvider(realmSlug)); |     final realmIdentity = ref.watch(realmIdentityProvider(realmSlug)); | ||||||
|     final memberListProvider = realmMemberListNotifierProvider(realmSlug); |     final memberListProvider = realmMemberListNotifierProvider(realmSlug); | ||||||
|  |     final memberListNotifier = ref.watch(memberListProvider.notifier); | ||||||
|     // For backward compatibility and to show total count in the header |  | ||||||
|     final memberState = ref.watch(realmMemberStateProvider(realmSlug)); |  | ||||||
|     final memberNotifier = ref.read( |  | ||||||
|       realmMemberStateProvider(realmSlug).notifier, |  | ||||||
|     ); |  | ||||||
|  |  | ||||||
|     useEffect(() { |     useEffect(() { | ||||||
|       Future(() { |       return memberListNotifier.dispose; | ||||||
|         memberNotifier.loadMore(); |  | ||||||
|       }); |  | ||||||
|       return null; |  | ||||||
|     }, []); |     }, []); | ||||||
|  |  | ||||||
|     Future<void> invitePerson() async { |     Future<void> invitePerson() async { | ||||||
| @@ -636,9 +590,7 @@ class _RealmMemberListSheet extends HookConsumerWidget { | |||||||
|           '/sphere/realms/invites/$realmSlug', |           '/sphere/realms/invites/$realmSlug', | ||||||
|           data: {'related_user_id': result.id, 'role': 0}, |           data: {'related_user_id': result.id, 'role': 0}, | ||||||
|         ); |         ); | ||||||
|         // Refresh both providers |         // Refresh the provider | ||||||
|         memberNotifier.reset(); |  | ||||||
|         await memberNotifier.loadMore(); |  | ||||||
|         ref.invalidate(memberListProvider); |         ref.invalidate(memberListProvider); | ||||||
|       } catch (err) { |       } catch (err) { | ||||||
|         showErrorAlert(err); |         showErrorAlert(err); | ||||||
| @@ -650,12 +602,17 @@ class _RealmMemberListSheet extends HookConsumerWidget { | |||||||
|         padding: EdgeInsets.only(top: 16, left: 20, right: 16, bottom: 12), |         padding: EdgeInsets.only(top: 16, left: 20, right: 16, bottom: 12), | ||||||
|         child: Row( |         child: Row( | ||||||
|           children: [ |           children: [ | ||||||
|             Text( |             ListenableBuilder( | ||||||
|               'members'.plural(memberState.total), |               listenable: memberListNotifier.totalCount, | ||||||
|               style: Theme.of(context).textTheme.headlineSmall?.copyWith( |               builder: | ||||||
|                 fontWeight: FontWeight.w600, |                   (context, _) => Text( | ||||||
|                 letterSpacing: -0.5, |                     'members'.plural(memberListNotifier.totalCount.value), | ||||||
|               ), |                     key: ValueKey(memberListNotifier), | ||||||
|  |                     style: Theme.of(context).textTheme.headlineSmall?.copyWith( | ||||||
|  |                       fontWeight: FontWeight.w600, | ||||||
|  |                       letterSpacing: -0.5, | ||||||
|  |                     ), | ||||||
|  |                   ), | ||||||
|             ), |             ), | ||||||
|             const Spacer(), |             const Spacer(), | ||||||
|             IconButton( |             IconButton( | ||||||
| @@ -666,9 +623,7 @@ class _RealmMemberListSheet extends HookConsumerWidget { | |||||||
|             IconButton( |             IconButton( | ||||||
|               icon: const Icon(Symbols.refresh), |               icon: const Icon(Symbols.refresh), | ||||||
|               onPressed: () { |               onPressed: () { | ||||||
|                 // Refresh both providers |                 // Refresh the provider | ||||||
|                 memberNotifier.reset(); |  | ||||||
|                 memberNotifier.loadMore(); |  | ||||||
|                 ref.invalidate(memberListProvider); |                 ref.invalidate(memberListProvider); | ||||||
|               }, |               }, | ||||||
|             ), |             ), | ||||||
| @@ -742,9 +697,7 @@ class _RealmMemberListSheet extends HookConsumerWidget { | |||||||
|                                   ), |                                   ), | ||||||
|                             ).then((value) { |                             ).then((value) { | ||||||
|                               if (value != null) { |                               if (value != null) { | ||||||
|                                 // Refresh both providers |                                 // Refresh the provider | ||||||
|                                 memberNotifier.reset(); |  | ||||||
|                                 memberNotifier.loadMore(); |  | ||||||
|                                 ref.invalidate(memberListProvider); |                                 ref.invalidate(memberListProvider); | ||||||
|                               } |                               } | ||||||
|                             }); |                             }); | ||||||
| @@ -764,9 +717,7 @@ class _RealmMemberListSheet extends HookConsumerWidget { | |||||||
|                                 await apiClient.delete( |                                 await apiClient.delete( | ||||||
|                                   '/sphere/realms/$realmSlug/members/${member.accountId}', |                                   '/sphere/realms/$realmSlug/members/${member.accountId}', | ||||||
|                                 ); |                                 ); | ||||||
|                                 // Refresh both providers |                                 // Refresh the provider | ||||||
|                                 memberNotifier.reset(); |  | ||||||
|                                 memberNotifier.loadMore(); |  | ||||||
|                                 ref.invalidate(memberListProvider); |                                 ref.invalidate(memberListProvider); | ||||||
|                               } catch (err) { |                               } catch (err) { | ||||||
|                                 showErrorAlert(err); |                                 showErrorAlert(err); | ||||||
| @@ -799,34 +750,6 @@ class _RealmMemberListSheet extends HookConsumerWidget { | |||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
| class RealmMemberState { |  | ||||||
|   final List<SnRealmMember> members; |  | ||||||
|   final bool isLoading; |  | ||||||
|   final int total; |  | ||||||
|   final String? error; |  | ||||||
|  |  | ||||||
|   const RealmMemberState({ |  | ||||||
|     required this.members, |  | ||||||
|     required this.isLoading, |  | ||||||
|     required this.total, |  | ||||||
|     this.error, |  | ||||||
|   }); |  | ||||||
|  |  | ||||||
|   RealmMemberState copyWith({ |  | ||||||
|     List<SnRealmMember>? members, |  | ||||||
|     bool? isLoading, |  | ||||||
|     int? total, |  | ||||||
|     String? error, |  | ||||||
|   }) { |  | ||||||
|     return RealmMemberState( |  | ||||||
|       members: members ?? this.members, |  | ||||||
|       isLoading: isLoading ?? this.isLoading, |  | ||||||
|       total: total ?? this.total, |  | ||||||
|       error: error ?? this.error, |  | ||||||
|     ); |  | ||||||
|   } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| class _RealmMemberRoleSheet extends HookConsumerWidget { | class _RealmMemberRoleSheet extends HookConsumerWidget { | ||||||
|   final String realmSlug; |   final String realmSlug; | ||||||
|   final SnRealmMember member; |   final SnRealmMember member; | ||||||
|   | |||||||
| @@ -399,7 +399,7 @@ class _RealmChatRoomsProviderElement | |||||||
| } | } | ||||||
|  |  | ||||||
| String _$realmMemberListNotifierHash() => | String _$realmMemberListNotifierHash() => | ||||||
|     r'022bcef5a90cbae05ff23b937851afc3ef913d42'; |     r'db1fd8a6741dfb3d5bb921d5d965f0cfdc0e7bcc'; | ||||||
|  |  | ||||||
| abstract class _$RealmMemberListNotifier | abstract class _$RealmMemberListNotifier | ||||||
|     extends BuildlessAutoDisposeAsyncNotifier<CursorPagingData<SnRealmMember>> { |     extends BuildlessAutoDisposeAsyncNotifier<CursorPagingData<SnRealmMember>> { | ||||||
|   | |||||||
| @@ -106,7 +106,7 @@ class RealmListScreen extends HookConsumerWidget { | |||||||
|                         return ConstrainedBox( |                         return ConstrainedBox( | ||||||
|                           constraints: const BoxConstraints(maxWidth: 540), |                           constraints: const BoxConstraints(maxWidth: 540), | ||||||
|                           child: RealmListTile(realm: value[item]), |                           child: RealmListTile(realm: value[item]), | ||||||
|                         ).center(); |                         ).padding(horizontal: 8).center(); | ||||||
|                       }, |                       }, | ||||||
|                       separatorBuilder: (_, _) => const Gap(8), |                       separatorBuilder: (_, _) => const Gap(8), | ||||||
|                     ), |                     ), | ||||||
|   | |||||||
| @@ -1,103 +0,0 @@ | |||||||
| import 'package:easy_localization/easy_localization.dart'; |  | ||||||
| import 'package:flutter/material.dart'; |  | ||||||
| import 'package:go_router/go_router.dart'; |  | ||||||
| import 'package:gap/gap.dart'; |  | ||||||
| import 'package:hooks_riverpod/hooks_riverpod.dart'; |  | ||||||
| import 'package:island/models/sticker.dart'; |  | ||||||
| import 'package:island/pods/network.dart'; |  | ||||||
| import 'package:island/widgets/app_scaffold.dart'; |  | ||||||
| import 'package:material_symbols_icons/symbols.dart'; |  | ||||||
| import 'package:riverpod_annotation/riverpod_annotation.dart'; |  | ||||||
| import 'package:riverpod_paging_utils/riverpod_paging_utils.dart'; |  | ||||||
|  |  | ||||||
| part 'marketplace.g.dart'; |  | ||||||
|  |  | ||||||
| @riverpod |  | ||||||
| class MarketplaceStickerPacksNotifier extends _$MarketplaceStickerPacksNotifier |  | ||||||
|     with CursorPagingNotifierMixin<SnStickerPack> { |  | ||||||
|   @override |  | ||||||
|   Future<CursorPagingData<SnStickerPack>> build() { |  | ||||||
|     return fetch(cursor: null); |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   @override |  | ||||||
|   Future<CursorPagingData<SnStickerPack>> fetch({ |  | ||||||
|     required String? cursor, |  | ||||||
|   }) async { |  | ||||||
|     final client = ref.read(apiClientProvider); |  | ||||||
|     final offset = cursor == null ? 0 : int.parse(cursor); |  | ||||||
|  |  | ||||||
|     final response = await client.get( |  | ||||||
|       '/sphere/stickers', |  | ||||||
|       queryParameters: {'offset': offset, 'take': 20}, |  | ||||||
|     ); |  | ||||||
|  |  | ||||||
|     final total = int.parse(response.headers.value('X-Total') ?? '0'); |  | ||||||
|     final List<dynamic> data = response.data; |  | ||||||
|     final stickers = data.map((e) => SnStickerPack.fromJson(e)).toList(); |  | ||||||
|  |  | ||||||
|     final hasMore = offset + stickers.length < total; |  | ||||||
|     final nextCursor = hasMore ? (offset + stickers.length).toString() : null; |  | ||||||
|  |  | ||||||
|     return CursorPagingData( |  | ||||||
|       items: stickers, |  | ||||||
|       hasMore: hasMore, |  | ||||||
|       nextCursor: nextCursor, |  | ||||||
|     ); |  | ||||||
|   } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| /// User-facing marketplace screen for browsing sticker packs. |  | ||||||
| /// This version does NOT rely on publisher name (no pubName). |  | ||||||
| class MarketplaceStickersScreen extends HookConsumerWidget { |  | ||||||
|   const MarketplaceStickersScreen({super.key}); |  | ||||||
|  |  | ||||||
|   @override |  | ||||||
|   Widget build(BuildContext context, WidgetRef ref) { |  | ||||||
|     return AppScaffold( |  | ||||||
|       appBar: AppBar( |  | ||||||
|         title: const Text('stickers').tr(), |  | ||||||
|         actions: const [Gap(8)], |  | ||||||
|       ), |  | ||||||
|       body: const SliverMarketplaceStickerPacksList(), |  | ||||||
|     ); |  | ||||||
|   } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| class SliverMarketplaceStickerPacksList extends HookConsumerWidget { |  | ||||||
|   const SliverMarketplaceStickerPacksList({super.key}); |  | ||||||
|  |  | ||||||
|   @override |  | ||||||
|   Widget build(BuildContext context, WidgetRef ref) { |  | ||||||
|     return PagingHelperView( |  | ||||||
|       provider: marketplaceStickerPacksNotifierProvider, |  | ||||||
|       futureRefreshable: marketplaceStickerPacksNotifierProvider.future, |  | ||||||
|       notifierRefreshable: marketplaceStickerPacksNotifierProvider.notifier, |  | ||||||
|       contentBuilder: |  | ||||||
|           (data, widgetCount, endItemView) => ListView.builder( |  | ||||||
|             padding: EdgeInsets.zero, |  | ||||||
|             itemCount: widgetCount, |  | ||||||
|             itemBuilder: (context, index) { |  | ||||||
|               if (index == widgetCount - 1) { |  | ||||||
|                 return endItemView; |  | ||||||
|               } |  | ||||||
|  |  | ||||||
|               final pack = data.items[index]; |  | ||||||
|               return ListTile( |  | ||||||
|                 title: Text(pack.name), |  | ||||||
|                 subtitle: Text(pack.description), |  | ||||||
|                 trailing: const Icon(Symbols.chevron_right), |  | ||||||
|                 onTap: () { |  | ||||||
|                   // Navigate to user-facing sticker pack detail page. |  | ||||||
|                   // Adjust the route name/parameters if your app uses different ones. |  | ||||||
|                   context.pushNamed( |  | ||||||
|                     'stickerPackDetail', |  | ||||||
|                     pathParameters: {'packId': pack.id}, |  | ||||||
|                   ); |  | ||||||
|                 }, |  | ||||||
|               ); |  | ||||||
|             }, |  | ||||||
|           ), |  | ||||||
|     ); |  | ||||||
|   } |  | ||||||
| } |  | ||||||
| @@ -1,32 +0,0 @@ | |||||||
| // GENERATED CODE - DO NOT MODIFY BY HAND |  | ||||||
|  |  | ||||||
| part of 'marketplace.dart'; |  | ||||||
|  |  | ||||||
| // ************************************************************************** |  | ||||||
| // RiverpodGenerator |  | ||||||
| // ************************************************************************** |  | ||||||
|  |  | ||||||
| String _$marketplaceStickerPacksNotifierHash() => |  | ||||||
|     r'b62ae8b7f5c4f8bb3be8c17fc005ea26da355187'; |  | ||||||
|  |  | ||||||
| /// See also [MarketplaceStickerPacksNotifier]. |  | ||||||
| @ProviderFor(MarketplaceStickerPacksNotifier) |  | ||||||
| final marketplaceStickerPacksNotifierProvider = |  | ||||||
|     AutoDisposeAsyncNotifierProvider< |  | ||||||
|       MarketplaceStickerPacksNotifier, |  | ||||||
|       CursorPagingData<SnStickerPack> |  | ||||||
|     >.internal( |  | ||||||
|       MarketplaceStickerPacksNotifier.new, |  | ||||||
|       name: r'marketplaceStickerPacksNotifierProvider', |  | ||||||
|       debugGetCreateSourceHash: |  | ||||||
|           const bool.fromEnvironment('dart.vm.product') |  | ||||||
|               ? null |  | ||||||
|               : _$marketplaceStickerPacksNotifierHash, |  | ||||||
|       dependencies: null, |  | ||||||
|       allTransitiveDependencies: null, |  | ||||||
|     ); |  | ||||||
|  |  | ||||||
| typedef _$MarketplaceStickerPacksNotifier = |  | ||||||
|     AutoDisposeAsyncNotifier<CursorPagingData<SnStickerPack>>; |  | ||||||
| // 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 |  | ||||||
| @@ -216,7 +216,7 @@ class MarketplaceStickerPackDetailScreen extends HookConsumerWidget { | |||||||
|                       ), |                       ), | ||||||
|                 ), |                 ), | ||||||
|               ), |               ), | ||||||
|               Gap(MediaQuery.of(context).padding.bottom), |               Gap(MediaQuery.of(context).padding.bottom + 16), | ||||||
|             ], |             ], | ||||||
|           ); |           ); | ||||||
|         }, |         }, | ||||||
|   | |||||||
							
								
								
									
										199
									
								
								lib/screens/stickers/sticker_marketplace.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										199
									
								
								lib/screens/stickers/sticker_marketplace.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,199 @@ | |||||||
|  | import 'package:easy_localization/easy_localization.dart'; | ||||||
|  | import 'package:flutter/material.dart'; | ||||||
|  | import 'package:flutter_hooks/flutter_hooks.dart'; | ||||||
|  | import 'package:go_router/go_router.dart'; | ||||||
|  | import 'package:gap/gap.dart'; | ||||||
|  | import 'package:hooks_riverpod/hooks_riverpod.dart'; | ||||||
|  | import 'package:island/models/sticker.dart'; | ||||||
|  | import 'package:island/pods/network.dart'; | ||||||
|  | import 'package:island/widgets/app_scaffold.dart'; | ||||||
|  | import 'package:material_symbols_icons/symbols.dart'; | ||||||
|  | import 'dart:async'; | ||||||
|  |  | ||||||
|  | import 'package:riverpod_annotation/riverpod_annotation.dart'; | ||||||
|  | import 'package:riverpod_paging_utils/riverpod_paging_utils.dart'; | ||||||
|  |  | ||||||
|  | part 'sticker_marketplace.g.dart'; | ||||||
|  |  | ||||||
|  | @riverpod | ||||||
|  | class MarketplaceStickerPacksNotifier extends _$MarketplaceStickerPacksNotifier | ||||||
|  |     with CursorPagingNotifierMixin<SnStickerPack> { | ||||||
|  |   @override | ||||||
|  |   Future<CursorPagingData<SnStickerPack>> build({ | ||||||
|  |     required String? query, | ||||||
|  |     required bool byUsage, | ||||||
|  |   }) { | ||||||
|  |     return fetch(cursor: null); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   Future<CursorPagingData<SnStickerPack>> fetch({ | ||||||
|  |     required String? cursor, | ||||||
|  |   }) async { | ||||||
|  |     final client = ref.read(apiClientProvider); | ||||||
|  |     final offset = cursor == null ? 0 : int.parse(cursor); | ||||||
|  |  | ||||||
|  |     final response = await client.get( | ||||||
|  |       '/sphere/stickers', | ||||||
|  |       queryParameters: { | ||||||
|  |         'offset': offset, | ||||||
|  |         'take': 20, | ||||||
|  |         'order': byUsage ? 'usage' : 'date', | ||||||
|  |         if (query != null && query!.isNotEmpty) 'query': query, | ||||||
|  |       }, | ||||||
|  |     ); | ||||||
|  |  | ||||||
|  |     final total = int.parse(response.headers.value('X-Total') ?? '0'); | ||||||
|  |     final List<dynamic> data = response.data; | ||||||
|  |     final stickers = data.map((e) => SnStickerPack.fromJson(e)).toList(); | ||||||
|  |  | ||||||
|  |     final hasMore = offset + stickers.length < total; | ||||||
|  |     final nextCursor = hasMore ? (offset + stickers.length).toString() : null; | ||||||
|  |  | ||||||
|  |     return CursorPagingData( | ||||||
|  |       items: stickers, | ||||||
|  |       hasMore: hasMore, | ||||||
|  |       nextCursor: nextCursor, | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /// User-facing marketplace screen for browsing sticker packs. | ||||||
|  | /// This version does NOT rely on publisher name (no pubName). | ||||||
|  | class MarketplaceStickersScreen extends HookConsumerWidget { | ||||||
|  |   const MarketplaceStickersScreen({super.key}); | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   Widget build(BuildContext context, WidgetRef ref) { | ||||||
|  |     final byUsage = useState(true); | ||||||
|  |     final query = useState<String?>(null); | ||||||
|  |     final searchController = useTextEditingController(); | ||||||
|  |     final focusNode = useFocusNode(); | ||||||
|  |     final debounceTimer = useState<Timer?>(null); | ||||||
|  |  | ||||||
|  |     // Clear search when query is cleared | ||||||
|  |     useEffect(() { | ||||||
|  |       if (query.value == null || query.value!.isEmpty) { | ||||||
|  |         searchController.clear(); | ||||||
|  |       } | ||||||
|  |       return null; | ||||||
|  |     }, [query.value]); | ||||||
|  |  | ||||||
|  |     // Clean up timer on dispose | ||||||
|  |     useEffect(() { | ||||||
|  |       return () { | ||||||
|  |         debounceTimer.value?.cancel(); | ||||||
|  |       }; | ||||||
|  |     }, []); | ||||||
|  |  | ||||||
|  |     return AppScaffold( | ||||||
|  |       appBar: AppBar( | ||||||
|  |         title: const Text('stickers').tr(), | ||||||
|  |         actions: [ | ||||||
|  |           IconButton( | ||||||
|  |             onPressed: () { | ||||||
|  |               byUsage.value = !byUsage.value; | ||||||
|  |             }, | ||||||
|  |             icon: | ||||||
|  |                 byUsage.value | ||||||
|  |                     ? const Icon(Symbols.local_fire_department) | ||||||
|  |                     : const Icon(Symbols.access_time), | ||||||
|  |             tooltip: | ||||||
|  |                 byUsage.value | ||||||
|  |                     ? 'orderByPopularity'.tr() | ||||||
|  |                     : 'orderByReleaseDate'.tr(), | ||||||
|  |           ), | ||||||
|  |           const Gap(8), | ||||||
|  |         ], | ||||||
|  |       ), | ||||||
|  |       body: PagingHelperView( | ||||||
|  |         provider: marketplaceStickerPacksNotifierProvider( | ||||||
|  |           byUsage: byUsage.value, | ||||||
|  |           query: query.value, | ||||||
|  |         ), | ||||||
|  |         futureRefreshable: | ||||||
|  |             marketplaceStickerPacksNotifierProvider( | ||||||
|  |               byUsage: byUsage.value, | ||||||
|  |               query: query.value, | ||||||
|  |             ).future, | ||||||
|  |         notifierRefreshable: | ||||||
|  |             marketplaceStickerPacksNotifierProvider( | ||||||
|  |               byUsage: byUsage.value, | ||||||
|  |               query: query.value, | ||||||
|  |             ).notifier, | ||||||
|  |         contentBuilder: | ||||||
|  |             (data, widgetCount, endItemView) => Column( | ||||||
|  |               children: [ | ||||||
|  |                 // Search bar above the list | ||||||
|  |                 Padding( | ||||||
|  |                   padding: const EdgeInsets.all(16), | ||||||
|  |                   child: SearchBar( | ||||||
|  |                     elevation: WidgetStateProperty.all(4), | ||||||
|  |                     controller: searchController, | ||||||
|  |                     focusNode: focusNode, | ||||||
|  |                     hintText: 'search'.tr(), | ||||||
|  |                     leading: const Icon(Symbols.search), | ||||||
|  |                     padding: WidgetStateProperty.all( | ||||||
|  |                       const EdgeInsets.symmetric(horizontal: 24), | ||||||
|  |                     ), | ||||||
|  |                     onTapOutside: | ||||||
|  |                         (_) => FocusManager.instance.primaryFocus?.unfocus(), | ||||||
|  |                     trailing: [ | ||||||
|  |                       if (query.value != null && query.value!.isNotEmpty) | ||||||
|  |                         IconButton( | ||||||
|  |                           icon: const Icon(Symbols.close), | ||||||
|  |                           onPressed: () { | ||||||
|  |                             query.value = null; | ||||||
|  |                             searchController.clear(); | ||||||
|  |                             focusNode.unfocus(); | ||||||
|  |                           }, | ||||||
|  |                         ), | ||||||
|  |                     ], | ||||||
|  |                     onChanged: (value) { | ||||||
|  |                       // Debounce search to avoid excessive API calls | ||||||
|  |                       debounceTimer.value?.cancel(); | ||||||
|  |                       debounceTimer.value = Timer( | ||||||
|  |                         const Duration(milliseconds: 500), | ||||||
|  |                         () { | ||||||
|  |                           query.value = value.isEmpty ? null : value; | ||||||
|  |                         }, | ||||||
|  |                       ); | ||||||
|  |                     }, | ||||||
|  |                     onSubmitted: (value) { | ||||||
|  |                       query.value = value.isEmpty ? null : value; | ||||||
|  |                       focusNode.unfocus(); | ||||||
|  |                     }, | ||||||
|  |                   ), | ||||||
|  |                 ), | ||||||
|  |                 Expanded( | ||||||
|  |                   child: ListView.builder( | ||||||
|  |                     padding: EdgeInsets.zero, | ||||||
|  |                     itemCount: widgetCount, | ||||||
|  |                     itemBuilder: (context, index) { | ||||||
|  |                       if (index == widgetCount - 1) { | ||||||
|  |                         return endItemView; | ||||||
|  |                       } | ||||||
|  |  | ||||||
|  |                       final pack = data.items[index]; | ||||||
|  |                       return ListTile( | ||||||
|  |                         title: Text(pack.name), | ||||||
|  |                         subtitle: Text(pack.description), | ||||||
|  |                         trailing: const Icon(Symbols.chevron_right), | ||||||
|  |                         onTap: () { | ||||||
|  |                           // Navigate to user-facing sticker pack detail page. | ||||||
|  |                           // Adjust the route name/parameters if your app uses different ones. | ||||||
|  |                           context.pushNamed( | ||||||
|  |                             'stickerPackDetail', | ||||||
|  |                             pathParameters: {'packId': pack.id}, | ||||||
|  |                           ); | ||||||
|  |                         }, | ||||||
|  |                       ); | ||||||
|  |                     }, | ||||||
|  |                   ), | ||||||
|  |                 ), | ||||||
|  |               ], | ||||||
|  |             ), | ||||||
|  |       ), | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  | } | ||||||
							
								
								
									
										213
									
								
								lib/screens/stickers/sticker_marketplace.g.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										213
									
								
								lib/screens/stickers/sticker_marketplace.g.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,213 @@ | |||||||
|  | // GENERATED CODE - DO NOT MODIFY BY HAND | ||||||
|  |  | ||||||
|  | part of 'sticker_marketplace.dart'; | ||||||
|  |  | ||||||
|  | // ************************************************************************** | ||||||
|  | // RiverpodGenerator | ||||||
|  | // ************************************************************************** | ||||||
|  |  | ||||||
|  | String _$marketplaceStickerPacksNotifierHash() => | ||||||
|  |     r'3bde76e18bb024f45ff6261fe735cdba97b02808'; | ||||||
|  |  | ||||||
|  | /// 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 _$MarketplaceStickerPacksNotifier | ||||||
|  |     extends BuildlessAutoDisposeAsyncNotifier<CursorPagingData<SnStickerPack>> { | ||||||
|  |   late final String? query; | ||||||
|  |   late final bool byUsage; | ||||||
|  |  | ||||||
|  |   FutureOr<CursorPagingData<SnStickerPack>> build({ | ||||||
|  |     required String? query, | ||||||
|  |     required bool byUsage, | ||||||
|  |   }); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /// See also [MarketplaceStickerPacksNotifier]. | ||||||
|  | @ProviderFor(MarketplaceStickerPacksNotifier) | ||||||
|  | const marketplaceStickerPacksNotifierProvider = | ||||||
|  |     MarketplaceStickerPacksNotifierFamily(); | ||||||
|  |  | ||||||
|  | /// See also [MarketplaceStickerPacksNotifier]. | ||||||
|  | class MarketplaceStickerPacksNotifierFamily | ||||||
|  |     extends Family<AsyncValue<CursorPagingData<SnStickerPack>>> { | ||||||
|  |   /// See also [MarketplaceStickerPacksNotifier]. | ||||||
|  |   const MarketplaceStickerPacksNotifierFamily(); | ||||||
|  |  | ||||||
|  |   /// See also [MarketplaceStickerPacksNotifier]. | ||||||
|  |   MarketplaceStickerPacksNotifierProvider call({ | ||||||
|  |     required String? query, | ||||||
|  |     required bool byUsage, | ||||||
|  |   }) { | ||||||
|  |     return MarketplaceStickerPacksNotifierProvider( | ||||||
|  |       query: query, | ||||||
|  |       byUsage: byUsage, | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   MarketplaceStickerPacksNotifierProvider getProviderOverride( | ||||||
|  |     covariant MarketplaceStickerPacksNotifierProvider provider, | ||||||
|  |   ) { | ||||||
|  |     return call(query: provider.query, byUsage: provider.byUsage); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   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'marketplaceStickerPacksNotifierProvider'; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /// See also [MarketplaceStickerPacksNotifier]. | ||||||
|  | class MarketplaceStickerPacksNotifierProvider | ||||||
|  |     extends | ||||||
|  |         AutoDisposeAsyncNotifierProviderImpl< | ||||||
|  |           MarketplaceStickerPacksNotifier, | ||||||
|  |           CursorPagingData<SnStickerPack> | ||||||
|  |         > { | ||||||
|  |   /// See also [MarketplaceStickerPacksNotifier]. | ||||||
|  |   MarketplaceStickerPacksNotifierProvider({ | ||||||
|  |     required String? query, | ||||||
|  |     required bool byUsage, | ||||||
|  |   }) : this._internal( | ||||||
|  |          () => | ||||||
|  |              MarketplaceStickerPacksNotifier() | ||||||
|  |                ..query = query | ||||||
|  |                ..byUsage = byUsage, | ||||||
|  |          from: marketplaceStickerPacksNotifierProvider, | ||||||
|  |          name: r'marketplaceStickerPacksNotifierProvider', | ||||||
|  |          debugGetCreateSourceHash: | ||||||
|  |              const bool.fromEnvironment('dart.vm.product') | ||||||
|  |                  ? null | ||||||
|  |                  : _$marketplaceStickerPacksNotifierHash, | ||||||
|  |          dependencies: MarketplaceStickerPacksNotifierFamily._dependencies, | ||||||
|  |          allTransitiveDependencies: | ||||||
|  |              MarketplaceStickerPacksNotifierFamily._allTransitiveDependencies, | ||||||
|  |          query: query, | ||||||
|  |          byUsage: byUsage, | ||||||
|  |        ); | ||||||
|  |  | ||||||
|  |   MarketplaceStickerPacksNotifierProvider._internal( | ||||||
|  |     super._createNotifier, { | ||||||
|  |     required super.name, | ||||||
|  |     required super.dependencies, | ||||||
|  |     required super.allTransitiveDependencies, | ||||||
|  |     required super.debugGetCreateSourceHash, | ||||||
|  |     required super.from, | ||||||
|  |     required this.query, | ||||||
|  |     required this.byUsage, | ||||||
|  |   }) : super.internal(); | ||||||
|  |  | ||||||
|  |   final String? query; | ||||||
|  |   final bool byUsage; | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   FutureOr<CursorPagingData<SnStickerPack>> runNotifierBuild( | ||||||
|  |     covariant MarketplaceStickerPacksNotifier notifier, | ||||||
|  |   ) { | ||||||
|  |     return notifier.build(query: query, byUsage: byUsage); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   Override overrideWith(MarketplaceStickerPacksNotifier Function() create) { | ||||||
|  |     return ProviderOverride( | ||||||
|  |       origin: this, | ||||||
|  |       override: MarketplaceStickerPacksNotifierProvider._internal( | ||||||
|  |         () => | ||||||
|  |             create() | ||||||
|  |               ..query = query | ||||||
|  |               ..byUsage = byUsage, | ||||||
|  |         from: from, | ||||||
|  |         name: null, | ||||||
|  |         dependencies: null, | ||||||
|  |         allTransitiveDependencies: null, | ||||||
|  |         debugGetCreateSourceHash: null, | ||||||
|  |         query: query, | ||||||
|  |         byUsage: byUsage, | ||||||
|  |       ), | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   AutoDisposeAsyncNotifierProviderElement< | ||||||
|  |     MarketplaceStickerPacksNotifier, | ||||||
|  |     CursorPagingData<SnStickerPack> | ||||||
|  |   > | ||||||
|  |   createElement() { | ||||||
|  |     return _MarketplaceStickerPacksNotifierProviderElement(this); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   bool operator ==(Object other) { | ||||||
|  |     return other is MarketplaceStickerPacksNotifierProvider && | ||||||
|  |         other.query == query && | ||||||
|  |         other.byUsage == byUsage; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   int get hashCode { | ||||||
|  |     var hash = _SystemHash.combine(0, runtimeType.hashCode); | ||||||
|  |     hash = _SystemHash.combine(hash, query.hashCode); | ||||||
|  |     hash = _SystemHash.combine(hash, byUsage.hashCode); | ||||||
|  |  | ||||||
|  |     return _SystemHash.finish(hash); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @Deprecated('Will be removed in 3.0. Use Ref instead') | ||||||
|  | // ignore: unused_element | ||||||
|  | mixin MarketplaceStickerPacksNotifierRef | ||||||
|  |     on AutoDisposeAsyncNotifierProviderRef<CursorPagingData<SnStickerPack>> { | ||||||
|  |   /// The parameter `query` of this provider. | ||||||
|  |   String? get query; | ||||||
|  |  | ||||||
|  |   /// The parameter `byUsage` of this provider. | ||||||
|  |   bool get byUsage; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | class _MarketplaceStickerPacksNotifierProviderElement | ||||||
|  |     extends | ||||||
|  |         AutoDisposeAsyncNotifierProviderElement< | ||||||
|  |           MarketplaceStickerPacksNotifier, | ||||||
|  |           CursorPagingData<SnStickerPack> | ||||||
|  |         > | ||||||
|  |     with MarketplaceStickerPacksNotifierRef { | ||||||
|  |   _MarketplaceStickerPacksNotifierProviderElement(super.provider); | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   String? get query => | ||||||
|  |       (origin as MarketplaceStickerPacksNotifierProvider).query; | ||||||
|  |   @override | ||||||
|  |   bool get byUsage => | ||||||
|  |       (origin as MarketplaceStickerPacksNotifierProvider).byUsage; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // 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 | ||||||
| @@ -235,7 +235,11 @@ class PageBackButton extends StatelessWidget { | |||||||
|     return IconButton( |     return IconButton( | ||||||
|       onPressed: () { |       onPressed: () { | ||||||
|         onWillPop?.call(); |         onWillPop?.call(); | ||||||
|         context.pop(); |         if (context.canPop()) { | ||||||
|  |           context.pop(); | ||||||
|  |         } else { | ||||||
|  |           context.go('/'); | ||||||
|  |         } | ||||||
|       }, |       }, | ||||||
|       icon: Icon( |       icon: Icon( | ||||||
|         color: color, |         color: color, | ||||||
|   | |||||||
| @@ -50,6 +50,6 @@ class AppWrapper extends HookConsumerWidget { | |||||||
|       } |       } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     return TourTriggerWidget(child: child); |     return TourTriggerWidget(key: UniqueKey(), child: child); | ||||||
|   } |   } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -1,9 +1,11 @@ | |||||||
|  | import 'dart:convert'; | ||||||
| import 'dart:math' as math; | import 'dart:math' as math; | ||||||
| import 'dart:ui'; | import 'dart:ui'; | ||||||
|  |  | ||||||
| import 'package:dismissible_page/dismissible_page.dart'; | import 'package:dismissible_page/dismissible_page.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/services.dart'; | ||||||
| import 'package:flutter_blurhash/flutter_blurhash.dart'; | import 'package:flutter_blurhash/flutter_blurhash.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'; | ||||||
| @@ -326,7 +328,11 @@ class CloudFileZoomIn extends HookConsumerWidget { | |||||||
|  |  | ||||||
|         // Create a temporary file to save the image |         // Create a temporary file to save the image | ||||||
|         final tempDir = await getTemporaryDirectory(); |         final tempDir = await getTemporaryDirectory(); | ||||||
|         final filePath = '${tempDir.path}/${item.id}.${extension(item.name)}'; |         var extName = extension(item.name).trim(); | ||||||
|  |         if (extName.isEmpty) { | ||||||
|  |           extName = item.mimeType?.split('/').lastOrNull ?? 'jpeg'; | ||||||
|  |         } | ||||||
|  |         final filePath = '${tempDir.path}/${item.id}.$extName'; | ||||||
|  |  | ||||||
|         await client.download( |         await client.download( | ||||||
|           '/drive/files/${item.id}', |           '/drive/files/${item.id}', | ||||||
| @@ -342,39 +348,6 @@ class CloudFileZoomIn extends HookConsumerWidget { | |||||||
|       } |       } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     Widget buildInfoRow(IconData icon, String label, String value) { |  | ||||||
|       return Padding( |  | ||||||
|         padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 24), |  | ||||||
|         child: Row( |  | ||||||
|           children: [ |  | ||||||
|             Icon( |  | ||||||
|               icon, |  | ||||||
|               size: 20, |  | ||||||
|               color: Theme.of(context).colorScheme.onSurface, |  | ||||||
|             ), |  | ||||||
|             const SizedBox(width: 12), |  | ||||||
|             Text( |  | ||||||
|               label, |  | ||||||
|               style: Theme.of(context).textTheme.bodyMedium?.copyWith( |  | ||||||
|                 color: Theme.of(context).textTheme.bodySmall?.color, |  | ||||||
|               ), |  | ||||||
|             ), |  | ||||||
|             const Spacer(), |  | ||||||
|             Flexible( |  | ||||||
|               child: Text( |  | ||||||
|                 value, |  | ||||||
|                 style: Theme.of( |  | ||||||
|                   context, |  | ||||||
|                 ).textTheme.bodyMedium?.copyWith(fontWeight: FontWeight.w500), |  | ||||||
|                 textAlign: TextAlign.end, |  | ||||||
|                 overflow: TextOverflow.ellipsis, |  | ||||||
|               ), |  | ||||||
|             ), |  | ||||||
|           ], |  | ||||||
|         ), |  | ||||||
|       ); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     String formatFileSize(int bytes) { |     String formatFileSize(int bytes) { | ||||||
|       if (bytes <= 0) return '0 B'; |       if (bytes <= 0) return '0 B'; | ||||||
|       if (bytes < 1024) return '$bytes B'; |       if (bytes < 1024) return '$bytes B'; | ||||||
| @@ -400,57 +373,247 @@ class CloudFileZoomIn extends HookConsumerWidget { | |||||||
|                 child: Column( |                 child: Column( | ||||||
|                   crossAxisAlignment: CrossAxisAlignment.start, |                   crossAxisAlignment: CrossAxisAlignment.start, | ||||||
|                   children: [ |                   children: [ | ||||||
|                     buildInfoRow(Icons.description, 'Name', item.name), |                     Row( | ||||||
|  |                       children: [ | ||||||
|  |                         Expanded( | ||||||
|  |                           child: Column( | ||||||
|  |                             crossAxisAlignment: CrossAxisAlignment.center, | ||||||
|  |                             mainAxisSize: MainAxisSize.min, | ||||||
|  |                             children: [ | ||||||
|  |                               Text('mimeType').tr(), | ||||||
|  |                               Text( | ||||||
|  |                                 item.mimeType ?? 'unknown'.tr(), | ||||||
|  |                                 maxLines: 1, | ||||||
|  |                                 overflow: TextOverflow.ellipsis, | ||||||
|  |                                 style: theme.textTheme.titleMedium?.copyWith( | ||||||
|  |                                   fontWeight: FontWeight.bold, | ||||||
|  |                                 ), | ||||||
|  |                               ), | ||||||
|  |                             ], | ||||||
|  |                           ), | ||||||
|  |                         ), | ||||||
|  |                         SizedBox(height: 28, child: const VerticalDivider()), | ||||||
|  |                         Expanded( | ||||||
|  |                           child: Column( | ||||||
|  |                             crossAxisAlignment: CrossAxisAlignment.center, | ||||||
|  |                             mainAxisSize: MainAxisSize.min, | ||||||
|  |                             children: [ | ||||||
|  |                               Text('fileSize').tr(), | ||||||
|  |                               Text( | ||||||
|  |                                 formatFileSize(item.size), | ||||||
|  |                                 style: theme.textTheme.titleMedium?.copyWith( | ||||||
|  |                                   fontWeight: FontWeight.bold, | ||||||
|  |                                 ), | ||||||
|  |                               ), | ||||||
|  |                             ], | ||||||
|  |                           ), | ||||||
|  |                         ), | ||||||
|  |                         if (item.hash != null) | ||||||
|  |                           SizedBox(height: 28, child: const VerticalDivider()), | ||||||
|  |                         if (item.hash != null) | ||||||
|  |                           Expanded( | ||||||
|  |                             child: GestureDetector( | ||||||
|  |                               child: Column( | ||||||
|  |                                 crossAxisAlignment: CrossAxisAlignment.center, | ||||||
|  |                                 mainAxisSize: MainAxisSize.min, | ||||||
|  |                                 children: [ | ||||||
|  |                                   Text('fileHash').tr(), | ||||||
|  |                                   Text( | ||||||
|  |                                     '${item.hash!.substring(0, 6)}...', | ||||||
|  |                                     style: theme.textTheme.titleMedium | ||||||
|  |                                         ?.copyWith(fontWeight: FontWeight.bold), | ||||||
|  |                                   ), | ||||||
|  |                                 ], | ||||||
|  |                               ), | ||||||
|  |                               onLongPress: () { | ||||||
|  |                                 Clipboard.setData( | ||||||
|  |                                   ClipboardData(text: item.hash!), | ||||||
|  |                                 ); | ||||||
|  |                                 showSnackBar('File hash copied to clipboard'); | ||||||
|  |                               }, | ||||||
|  |                             ), | ||||||
|  |                           ), | ||||||
|  |                       ], | ||||||
|  |                     ).padding(horizontal: 24, vertical: 16), | ||||||
|                     const Divider(height: 1), |                     const Divider(height: 1), | ||||||
|                     buildInfoRow( |                     ListTile( | ||||||
|                       Icons.storage, |                       leading: const Icon(Icons.file_present), | ||||||
|                       'Size', |                       title: Text('Name').tr(), | ||||||
|                       formatFileSize(item.size), |                       subtitle: Text( | ||||||
|                     ), |                         item.name, | ||||||
|                     const Divider(height: 1), |                         maxLines: 1, | ||||||
|                     buildInfoRow( |                         overflow: TextOverflow.ellipsis, | ||||||
|                       Icons.category, |                       ), | ||||||
|                       'Type', |                       contentPadding: EdgeInsets.symmetric(horizontal: 24), | ||||||
|                       item.mimeType?.toUpperCase() ?? 'UNKNOWN', |                       trailing: IconButton( | ||||||
|  |                         icon: const Icon(Icons.copy), | ||||||
|  |                         onPressed: () { | ||||||
|  |                           Clipboard.setData(ClipboardData(text: item.name)); | ||||||
|  |                           showSnackBar('File name copied to clipboard'); | ||||||
|  |                         }, | ||||||
|  |                       ), | ||||||
|                     ), |                     ), | ||||||
|                     if (exifData.isNotEmpty) ...[ |                     if (exifData.isNotEmpty) ...[ | ||||||
|                       const SizedBox(height: 16), |                       const Divider(height: 1), | ||||||
|                       Text( |                       Theme( | ||||||
|                         'EXIF Data', |                         data: theme.copyWith(dividerColor: Colors.transparent), | ||||||
|                         style: theme.textTheme.titleMedium?.copyWith( |                         child: ExpansionTile( | ||||||
|                           fontWeight: FontWeight.bold, |                           tilePadding: const EdgeInsets.symmetric( | ||||||
|                         ), |                             horizontal: 24, | ||||||
|                       ).padding(horizontal: 24), |                           ), | ||||||
|                       const SizedBox(height: 8), |                           title: Text( | ||||||
|                       Column( |                             'exifData'.tr(), | ||||||
|                         crossAxisAlignment: CrossAxisAlignment.start, |                             style: theme.textTheme.titleMedium?.copyWith( | ||||||
|                         children: [ |                               fontWeight: FontWeight.bold, | ||||||
|                           ...exifData.entries.map( |                             ), | ||||||
|                             (entry) => Padding( |                           ), | ||||||
|                               padding: const EdgeInsets.symmetric(vertical: 4), |                           children: [ | ||||||
|                               child: Row( |                             Column( | ||||||
|                                 crossAxisAlignment: CrossAxisAlignment.start, |                               crossAxisAlignment: CrossAxisAlignment.start, | ||||||
|                                 children: [ |                               children: [ | ||||||
|                                   Text( |                                 ...exifData.entries.map( | ||||||
|                                     '• ${entry.key.contains('-') ? entry.key.split('-').last : entry.key}: ', |                                   (entry) => ListTile( | ||||||
|                                     style: theme.textTheme.bodyMedium?.copyWith( |                                     dense: true, | ||||||
|                                       fontWeight: FontWeight.w500, |                                     contentPadding: EdgeInsets.symmetric( | ||||||
|  |                                       horizontal: 24, | ||||||
|                                     ), |                                     ), | ||||||
|                                   ), |                                     title: | ||||||
|                                   Expanded( |                                         Text( | ||||||
|                                     child: Text( |                                           entry.key.contains('-') | ||||||
|  |                                               ? entry.key.split('-').last | ||||||
|  |                                               : entry.key, | ||||||
|  |                                           style: theme.textTheme.bodyMedium | ||||||
|  |                                               ?.copyWith( | ||||||
|  |                                                 fontWeight: FontWeight.w500, | ||||||
|  |                                               ), | ||||||
|  |                                         ).bold(), | ||||||
|  |                                     subtitle: Text( | ||||||
|                                       '${entry.value}'.isNotEmpty |                                       '${entry.value}'.isNotEmpty | ||||||
|                                           ? '${entry.value}' |                                           ? '${entry.value}' | ||||||
|                                           : 'N/A', |                                           : 'N/A', | ||||||
|                                       style: theme.textTheme.bodyMedium, |                                       style: theme.textTheme.bodyMedium, | ||||||
|                                     ), |                                     ), | ||||||
|  |                                     onTap: () { | ||||||
|  |                                       Clipboard.setData( | ||||||
|  |                                         ClipboardData(text: '${entry.value}'), | ||||||
|  |                                       ); | ||||||
|  |                                       showSnackBar('Value copied to clipboard'); | ||||||
|  |                                     }, | ||||||
|                                   ), |                                   ), | ||||||
|                                 ], |                                 ), | ||||||
|                               ), |                               ], | ||||||
|  |                             ), | ||||||
|  |                           ], | ||||||
|  |                         ), | ||||||
|  |                       ), | ||||||
|  |                     ], | ||||||
|  |                     if (item.fileMeta != null && item.fileMeta!.isNotEmpty) ...[ | ||||||
|  |                       const Divider(height: 1), | ||||||
|  |                       Theme( | ||||||
|  |                         data: theme.copyWith(dividerColor: Colors.transparent), | ||||||
|  |                         child: ExpansionTile( | ||||||
|  |                           tilePadding: const EdgeInsets.symmetric( | ||||||
|  |                             horizontal: 24, | ||||||
|  |                           ), | ||||||
|  |                           title: Text( | ||||||
|  |                             'File Metadata', | ||||||
|  |                             style: theme.textTheme.titleMedium?.copyWith( | ||||||
|  |                               fontWeight: FontWeight.bold, | ||||||
|                             ), |                             ), | ||||||
|                           ), |                           ), | ||||||
|                         ], |                           children: [ | ||||||
|                       ).padding(horizontal: 24), |                             Column( | ||||||
|  |                               crossAxisAlignment: CrossAxisAlignment.start, | ||||||
|  |                               children: [ | ||||||
|  |                                 ...item.fileMeta!.entries.map( | ||||||
|  |                                   (entry) => ListTile( | ||||||
|  |                                     dense: true, | ||||||
|  |                                     contentPadding: EdgeInsets.symmetric( | ||||||
|  |                                       horizontal: 24, | ||||||
|  |                                     ), | ||||||
|  |                                     title: | ||||||
|  |                                         Text( | ||||||
|  |                                           entry.key, | ||||||
|  |                                           style: theme.textTheme.bodyMedium | ||||||
|  |                                               ?.copyWith( | ||||||
|  |                                                 fontWeight: FontWeight.w500, | ||||||
|  |                                               ), | ||||||
|  |                                         ).bold(), | ||||||
|  |                                     subtitle: Text( | ||||||
|  |                                       jsonEncode(entry.value), | ||||||
|  |                                       style: theme.textTheme.bodyMedium, | ||||||
|  |                                       maxLines: 3, | ||||||
|  |                                       overflow: TextOverflow.ellipsis, | ||||||
|  |                                     ), | ||||||
|  |                                     onTap: () { | ||||||
|  |                                       Clipboard.setData( | ||||||
|  |                                         ClipboardData( | ||||||
|  |                                           text: jsonEncode(entry.value), | ||||||
|  |                                         ), | ||||||
|  |                                       ); | ||||||
|  |                                       showSnackBar('Value copied to clipboard'); | ||||||
|  |                                     }, | ||||||
|  |                                   ), | ||||||
|  |                                 ), | ||||||
|  |                               ], | ||||||
|  |                             ), | ||||||
|  |                           ], | ||||||
|  |                         ), | ||||||
|  |                       ), | ||||||
|  |                     ], | ||||||
|  |                     if (item.userMeta != null && item.userMeta!.isNotEmpty) ...[ | ||||||
|  |                       const Divider(height: 1), | ||||||
|  |                       Theme( | ||||||
|  |                         data: theme.copyWith(dividerColor: Colors.transparent), | ||||||
|  |                         child: ExpansionTile( | ||||||
|  |                           tilePadding: const EdgeInsets.symmetric( | ||||||
|  |                             horizontal: 24, | ||||||
|  |                           ), | ||||||
|  |                           title: Text( | ||||||
|  |                             'User Metadata', | ||||||
|  |                             style: theme.textTheme.titleMedium?.copyWith( | ||||||
|  |                               fontWeight: FontWeight.bold, | ||||||
|  |                             ), | ||||||
|  |                           ), | ||||||
|  |                           children: [ | ||||||
|  |                             Column( | ||||||
|  |                               crossAxisAlignment: CrossAxisAlignment.start, | ||||||
|  |                               children: [ | ||||||
|  |                                 ...item.userMeta!.entries.map( | ||||||
|  |                                   (entry) => ListTile( | ||||||
|  |                                     dense: true, | ||||||
|  |                                     contentPadding: EdgeInsets.symmetric( | ||||||
|  |                                       horizontal: 24, | ||||||
|  |                                     ), | ||||||
|  |                                     title: | ||||||
|  |                                         Text( | ||||||
|  |                                           entry.key, | ||||||
|  |                                           style: theme.textTheme.bodyMedium | ||||||
|  |                                               ?.copyWith( | ||||||
|  |                                                 fontWeight: FontWeight.w500, | ||||||
|  |                                               ), | ||||||
|  |                                         ).bold(), | ||||||
|  |                                     subtitle: Text( | ||||||
|  |                                       jsonEncode(entry.value), | ||||||
|  |                                       style: theme.textTheme.bodyMedium, | ||||||
|  |                                       maxLines: 3, | ||||||
|  |                                       overflow: TextOverflow.ellipsis, | ||||||
|  |                                     ), | ||||||
|  |                                     onTap: () { | ||||||
|  |                                       Clipboard.setData( | ||||||
|  |                                         ClipboardData( | ||||||
|  |                                           text: jsonEncode(entry.value), | ||||||
|  |                                         ), | ||||||
|  |                                       ); | ||||||
|  |                                       showSnackBar('Value copied to clipboard'); | ||||||
|  |                                     }, | ||||||
|  |                                   ), | ||||||
|  |                                 ), | ||||||
|  |                               ], | ||||||
|  |                             ), | ||||||
|  |                           ], | ||||||
|  |                         ), | ||||||
|  |                       ), | ||||||
|                     ], |                     ], | ||||||
|                     const SizedBox(height: 16), |                     const SizedBox(height: 16), | ||||||
|                   ], |                   ], | ||||||
|   | |||||||
| @@ -8,6 +8,52 @@ import 'package:island/pods/websocket.dart'; | |||||||
| import 'package:island/widgets/content/network_status_sheet.dart'; | import 'package:island/widgets/content/network_status_sheet.dart'; | ||||||
| import 'package:island/widgets/content/sheet.dart'; | import 'package:island/widgets/content/sheet.dart'; | ||||||
| import 'package:material_symbols_icons/symbols.dart'; | import 'package:material_symbols_icons/symbols.dart'; | ||||||
|  | import 'package:island/pods/config.dart'; | ||||||
|  |  | ||||||
|  | Future<void> _showSetTokenDialog(BuildContext context, WidgetRef ref) async { | ||||||
|  |   final TextEditingController controller = TextEditingController(); | ||||||
|  |   final prefs = ref.read(sharedPreferencesProvider); | ||||||
|  |  | ||||||
|  |   return showDialog<void>( | ||||||
|  |     context: context, | ||||||
|  |     builder: (BuildContext context) { | ||||||
|  |       return AlertDialog( | ||||||
|  |         title: const Text('Set Access Token'), | ||||||
|  |         content: TextField( | ||||||
|  |           controller: controller, | ||||||
|  |           decoration: const InputDecoration( | ||||||
|  |             hintText: 'Enter access token', | ||||||
|  |             border: OutlineInputBorder(), | ||||||
|  |           ), | ||||||
|  |           autofocus: true, | ||||||
|  |         ), | ||||||
|  |         actions: <Widget>[ | ||||||
|  |           TextButton( | ||||||
|  |             child: const Text('Cancel'), | ||||||
|  |             onPressed: () { | ||||||
|  |               Navigator.of(context).pop(); | ||||||
|  |             }, | ||||||
|  |           ), | ||||||
|  |           TextButton( | ||||||
|  |             child: const Text('Set'), | ||||||
|  |             onPressed: () async { | ||||||
|  |               final token = controller.text.trim(); | ||||||
|  |               if (token.isNotEmpty) { | ||||||
|  |                 await setToken(prefs, token); | ||||||
|  |                 ref.invalidate(tokenProvider); | ||||||
|  |                 // Store context in local variable to avoid async gap issue | ||||||
|  |                 final navigatorContext = context; | ||||||
|  |                 if (navigatorContext.mounted) { | ||||||
|  |                   Navigator.of(navigatorContext).pop(); | ||||||
|  |                 } | ||||||
|  |               } | ||||||
|  |             }, | ||||||
|  |           ), | ||||||
|  |         ], | ||||||
|  |       ); | ||||||
|  |     }, | ||||||
|  |   ); | ||||||
|  | } | ||||||
|  |  | ||||||
| class DebugSheet extends HookConsumerWidget { | class DebugSheet extends HookConsumerWidget { | ||||||
|   const DebugSheet({super.key}); |   const DebugSheet({super.key}); | ||||||
| @@ -49,6 +95,17 @@ class DebugSheet extends HookConsumerWidget { | |||||||
|               Clipboard.setData(ClipboardData(text: tk!.token)); |               Clipboard.setData(ClipboardData(text: tk!.token)); | ||||||
|             }, |             }, | ||||||
|           ), |           ), | ||||||
|  |           ListTile( | ||||||
|  |             minTileHeight: 48, | ||||||
|  |             leading: const Icon(Symbols.edit), | ||||||
|  |             trailing: const Icon(Symbols.chevron_right), | ||||||
|  |             contentPadding: EdgeInsets.symmetric(horizontal: 24), | ||||||
|  |             title: Text('Set access token'), | ||||||
|  |             onTap: () async { | ||||||
|  |               await _showSetTokenDialog(context, ref); | ||||||
|  |             }, | ||||||
|  |           ), | ||||||
|  |           const Divider(height: 1), | ||||||
|           ListTile( |           ListTile( | ||||||
|             minTileHeight: 48, |             minTileHeight: 48, | ||||||
|             leading: const Icon(Symbols.delete), |             leading: const Icon(Symbols.delete), | ||||||
|   | |||||||
| @@ -94,7 +94,7 @@ class PostItemCreator extends HookConsumerWidget { | |||||||
|           borderRadius: BorderRadius.circular(12), |           borderRadius: BorderRadius.circular(12), | ||||||
|           onTap: () { |           onTap: () { | ||||||
|             if (isOpenable) { |             if (isOpenable) { | ||||||
|               context.pushNamed('postDetail', pathParameters: {'id': item.id}); |               context.goNamed('postDetail', pathParameters: {'id': item.id}); | ||||||
|             } |             } | ||||||
|           }, |           }, | ||||||
|           child: Padding( |           child: Padding( | ||||||
|   | |||||||
| @@ -21,6 +21,7 @@ class PostListNotifier extends _$PostListNotifier | |||||||
|     int? type, |     int? type, | ||||||
|     List<String>? categories, |     List<String>? categories, | ||||||
|     List<String>? tags, |     List<String>? tags, | ||||||
|  |     bool shuffle = false, | ||||||
|   }) { |   }) { | ||||||
|     return fetch(cursor: null); |     return fetch(cursor: null); | ||||||
|   } |   } | ||||||
| @@ -38,6 +39,7 @@ class PostListNotifier extends _$PostListNotifier | |||||||
|       if (type != null) 'type': type, |       if (type != null) 'type': type, | ||||||
|       if (tags != null) 'tags': tags, |       if (tags != null) 'tags': tags, | ||||||
|       if (categories != null) 'categories': categories, |       if (categories != null) 'categories': categories, | ||||||
|  |       if (shuffle) 'shuffle': true, | ||||||
|     }; |     }; | ||||||
|  |  | ||||||
|     final response = await client.get( |     final response = await client.get( | ||||||
| @@ -74,6 +76,7 @@ class SliverPostList extends HookConsumerWidget { | |||||||
|   final int? type; |   final int? type; | ||||||
|   final List<String>? categories; |   final List<String>? categories; | ||||||
|   final List<String>? tags; |   final List<String>? tags; | ||||||
|  |   final bool shuffle; | ||||||
|   final PostItemType itemType; |   final PostItemType itemType; | ||||||
|   final Color? backgroundColor; |   final Color? backgroundColor; | ||||||
|   final EdgeInsets? padding; |   final EdgeInsets? padding; | ||||||
| @@ -88,6 +91,7 @@ class SliverPostList extends HookConsumerWidget { | |||||||
|     this.type, |     this.type, | ||||||
|     this.categories, |     this.categories, | ||||||
|     this.tags, |     this.tags, | ||||||
|  |     this.shuffle = false, | ||||||
|     this.itemType = PostItemType.regular, |     this.itemType = PostItemType.regular, | ||||||
|     this.backgroundColor, |     this.backgroundColor, | ||||||
|     this.padding, |     this.padding, | ||||||
| @@ -105,6 +109,7 @@ class SliverPostList extends HookConsumerWidget { | |||||||
|         type: type, |         type: type, | ||||||
|         categories: categories, |         categories: categories, | ||||||
|         tags: tags, |         tags: tags, | ||||||
|  |         shuffle: shuffle, | ||||||
|       ), |       ), | ||||||
|       futureRefreshable: |       futureRefreshable: | ||||||
|           postListNotifierProvider( |           postListNotifierProvider( | ||||||
| @@ -113,6 +118,7 @@ class SliverPostList extends HookConsumerWidget { | |||||||
|             type: type, |             type: type, | ||||||
|             categories: categories, |             categories: categories, | ||||||
|             tags: tags, |             tags: tags, | ||||||
|  |             shuffle: shuffle, | ||||||
|           ).future, |           ).future, | ||||||
|       notifierRefreshable: |       notifierRefreshable: | ||||||
|           postListNotifierProvider( |           postListNotifierProvider( | ||||||
| @@ -121,6 +127,7 @@ class SliverPostList extends HookConsumerWidget { | |||||||
|             type: type, |             type: type, | ||||||
|             categories: categories, |             categories: categories, | ||||||
|             tags: tags, |             tags: tags, | ||||||
|  |             shuffle: shuffle, | ||||||
|           ).notifier, |           ).notifier, | ||||||
|       contentBuilder: |       contentBuilder: | ||||||
|           (data, widgetCount, endItemView) => SliverList.builder( |           (data, widgetCount, endItemView) => SliverList.builder( | ||||||
|   | |||||||
| @@ -6,7 +6,7 @@ part of 'post_list.dart'; | |||||||
| // RiverpodGenerator | // RiverpodGenerator | ||||||
| // ************************************************************************** | // ************************************************************************** | ||||||
|  |  | ||||||
| String _$postListNotifierHash() => r'9784b282b3ee14b7109e263c5841a082cf0be78e'; | String _$postListNotifierHash() => r'faa0b939fae56367ff120ce63d9deb17b1995c9c'; | ||||||
|  |  | ||||||
| /// Copied from Dart SDK | /// Copied from Dart SDK | ||||||
| class _SystemHash { | class _SystemHash { | ||||||
| @@ -36,6 +36,7 @@ abstract class _$PostListNotifier | |||||||
|   late final int? type; |   late final int? type; | ||||||
|   late final List<String>? categories; |   late final List<String>? categories; | ||||||
|   late final List<String>? tags; |   late final List<String>? tags; | ||||||
|  |   late final bool shuffle; | ||||||
|  |  | ||||||
|   FutureOr<CursorPagingData<SnPost>> build({ |   FutureOr<CursorPagingData<SnPost>> build({ | ||||||
|     String? pubName, |     String? pubName, | ||||||
| @@ -43,6 +44,7 @@ abstract class _$PostListNotifier | |||||||
|     int? type, |     int? type, | ||||||
|     List<String>? categories, |     List<String>? categories, | ||||||
|     List<String>? tags, |     List<String>? tags, | ||||||
|  |     bool shuffle = false, | ||||||
|   }); |   }); | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -63,6 +65,7 @@ class PostListNotifierFamily | |||||||
|     int? type, |     int? type, | ||||||
|     List<String>? categories, |     List<String>? categories, | ||||||
|     List<String>? tags, |     List<String>? tags, | ||||||
|  |     bool shuffle = false, | ||||||
|   }) { |   }) { | ||||||
|     return PostListNotifierProvider( |     return PostListNotifierProvider( | ||||||
|       pubName: pubName, |       pubName: pubName, | ||||||
| @@ -70,6 +73,7 @@ class PostListNotifierFamily | |||||||
|       type: type, |       type: type, | ||||||
|       categories: categories, |       categories: categories, | ||||||
|       tags: tags, |       tags: tags, | ||||||
|  |       shuffle: shuffle, | ||||||
|     ); |     ); | ||||||
|   } |   } | ||||||
|  |  | ||||||
| @@ -83,6 +87,7 @@ class PostListNotifierFamily | |||||||
|       type: provider.type, |       type: provider.type, | ||||||
|       categories: provider.categories, |       categories: provider.categories, | ||||||
|       tags: provider.tags, |       tags: provider.tags, | ||||||
|  |       shuffle: provider.shuffle, | ||||||
|     ); |     ); | ||||||
|   } |   } | ||||||
|  |  | ||||||
| @@ -115,6 +120,7 @@ class PostListNotifierProvider | |||||||
|     int? type, |     int? type, | ||||||
|     List<String>? categories, |     List<String>? categories, | ||||||
|     List<String>? tags, |     List<String>? tags, | ||||||
|  |     bool shuffle = false, | ||||||
|   }) : this._internal( |   }) : this._internal( | ||||||
|          () => |          () => | ||||||
|              PostListNotifier() |              PostListNotifier() | ||||||
| @@ -122,7 +128,8 @@ class PostListNotifierProvider | |||||||
|                ..realm = realm |                ..realm = realm | ||||||
|                ..type = type |                ..type = type | ||||||
|                ..categories = categories |                ..categories = categories | ||||||
|                ..tags = tags, |                ..tags = tags | ||||||
|  |                ..shuffle = shuffle, | ||||||
|          from: postListNotifierProvider, |          from: postListNotifierProvider, | ||||||
|          name: r'postListNotifierProvider', |          name: r'postListNotifierProvider', | ||||||
|          debugGetCreateSourceHash: |          debugGetCreateSourceHash: | ||||||
| @@ -137,6 +144,7 @@ class PostListNotifierProvider | |||||||
|          type: type, |          type: type, | ||||||
|          categories: categories, |          categories: categories, | ||||||
|          tags: tags, |          tags: tags, | ||||||
|  |          shuffle: shuffle, | ||||||
|        ); |        ); | ||||||
|  |  | ||||||
|   PostListNotifierProvider._internal( |   PostListNotifierProvider._internal( | ||||||
| @@ -151,6 +159,7 @@ class PostListNotifierProvider | |||||||
|     required this.type, |     required this.type, | ||||||
|     required this.categories, |     required this.categories, | ||||||
|     required this.tags, |     required this.tags, | ||||||
|  |     required this.shuffle, | ||||||
|   }) : super.internal(); |   }) : super.internal(); | ||||||
|  |  | ||||||
|   final String? pubName; |   final String? pubName; | ||||||
| @@ -158,6 +167,7 @@ class PostListNotifierProvider | |||||||
|   final int? type; |   final int? type; | ||||||
|   final List<String>? categories; |   final List<String>? categories; | ||||||
|   final List<String>? tags; |   final List<String>? tags; | ||||||
|  |   final bool shuffle; | ||||||
|  |  | ||||||
|   @override |   @override | ||||||
|   FutureOr<CursorPagingData<SnPost>> runNotifierBuild( |   FutureOr<CursorPagingData<SnPost>> runNotifierBuild( | ||||||
| @@ -169,6 +179,7 @@ class PostListNotifierProvider | |||||||
|       type: type, |       type: type, | ||||||
|       categories: categories, |       categories: categories, | ||||||
|       tags: tags, |       tags: tags, | ||||||
|  |       shuffle: shuffle, | ||||||
|     ); |     ); | ||||||
|   } |   } | ||||||
|  |  | ||||||
| @@ -183,7 +194,8 @@ class PostListNotifierProvider | |||||||
|               ..realm = realm |               ..realm = realm | ||||||
|               ..type = type |               ..type = type | ||||||
|               ..categories = categories |               ..categories = categories | ||||||
|               ..tags = tags, |               ..tags = tags | ||||||
|  |               ..shuffle = shuffle, | ||||||
|         from: from, |         from: from, | ||||||
|         name: null, |         name: null, | ||||||
|         dependencies: null, |         dependencies: null, | ||||||
| @@ -194,6 +206,7 @@ class PostListNotifierProvider | |||||||
|         type: type, |         type: type, | ||||||
|         categories: categories, |         categories: categories, | ||||||
|         tags: tags, |         tags: tags, | ||||||
|  |         shuffle: shuffle, | ||||||
|       ), |       ), | ||||||
|     ); |     ); | ||||||
|   } |   } | ||||||
| @@ -214,7 +227,8 @@ class PostListNotifierProvider | |||||||
|         other.realm == realm && |         other.realm == realm && | ||||||
|         other.type == type && |         other.type == type && | ||||||
|         other.categories == categories && |         other.categories == categories && | ||||||
|         other.tags == tags; |         other.tags == tags && | ||||||
|  |         other.shuffle == shuffle; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   @override |   @override | ||||||
| @@ -225,6 +239,7 @@ class PostListNotifierProvider | |||||||
|     hash = _SystemHash.combine(hash, type.hashCode); |     hash = _SystemHash.combine(hash, type.hashCode); | ||||||
|     hash = _SystemHash.combine(hash, categories.hashCode); |     hash = _SystemHash.combine(hash, categories.hashCode); | ||||||
|     hash = _SystemHash.combine(hash, tags.hashCode); |     hash = _SystemHash.combine(hash, tags.hashCode); | ||||||
|  |     hash = _SystemHash.combine(hash, shuffle.hashCode); | ||||||
|  |  | ||||||
|     return _SystemHash.finish(hash); |     return _SystemHash.finish(hash); | ||||||
|   } |   } | ||||||
| @@ -248,6 +263,9 @@ mixin PostListNotifierRef | |||||||
|  |  | ||||||
|   /// The parameter `tags` of this provider. |   /// The parameter `tags` of this provider. | ||||||
|   List<String>? get tags; |   List<String>? get tags; | ||||||
|  |  | ||||||
|  |   /// The parameter `shuffle` of this provider. | ||||||
|  |   bool get shuffle; | ||||||
| } | } | ||||||
|  |  | ||||||
| class _PostListNotifierProviderElement | class _PostListNotifierProviderElement | ||||||
| @@ -270,6 +288,8 @@ class _PostListNotifierProviderElement | |||||||
|       (origin as PostListNotifierProvider).categories; |       (origin as PostListNotifierProvider).categories; | ||||||
|   @override |   @override | ||||||
|   List<String>? get tags => (origin as PostListNotifierProvider).tags; |   List<String>? get tags => (origin as PostListNotifierProvider).tags; | ||||||
|  |   @override | ||||||
|  |   bool get shuffle => (origin as PostListNotifierProvider).shuffle; | ||||||
| } | } | ||||||
|  |  | ||||||
| // ignore_for_file: type=lint | // ignore_for_file: type=lint | ||||||
|   | |||||||
							
								
								
									
										113
									
								
								lib/widgets/post/post_shuffle.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										113
									
								
								lib/widgets/post/post_shuffle.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,113 @@ | |||||||
|  | import 'package:easy_localization/easy_localization.dart'; | ||||||
|  | import 'package:flutter/material.dart'; | ||||||
|  | import 'package:flutter_card_swiper/flutter_card_swiper.dart'; | ||||||
|  | import 'package:flutter_hooks/flutter_hooks.dart'; | ||||||
|  | import 'package:hooks_riverpod/hooks_riverpod.dart'; | ||||||
|  | import 'package:island/widgets/app_scaffold.dart'; | ||||||
|  | import 'package:island/widgets/post/post_item.dart'; | ||||||
|  | import 'package:island/widgets/post/post_list.dart'; | ||||||
|  | import 'package:material_symbols_icons/symbols.dart'; | ||||||
|  | import 'package:styled_widget/styled_widget.dart'; | ||||||
|  |  | ||||||
|  | class PostShuffleScreen extends HookConsumerWidget { | ||||||
|  |   const PostShuffleScreen({super.key}); | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   Widget build(BuildContext context, WidgetRef ref) { | ||||||
|  |     final postListState = ref.watch(postListNotifierProvider(shuffle: true)); | ||||||
|  |     final postListNotifier = ref.watch( | ||||||
|  |       postListNotifierProvider(shuffle: true).notifier, | ||||||
|  |     ); | ||||||
|  |  | ||||||
|  |     final cardSwiperController = useMemoized(() => CardSwiperController(), []); | ||||||
|  |  | ||||||
|  |     useEffect(() { | ||||||
|  |       return cardSwiperController.dispose; | ||||||
|  |     }, []); | ||||||
|  |  | ||||||
|  |     const kBottomControlHeight = 64.0; | ||||||
|  |  | ||||||
|  |     return AppScaffold( | ||||||
|  |       appBar: AppBar(title: const Text('postShuffle').tr()), | ||||||
|  |       body: Stack( | ||||||
|  |         children: [ | ||||||
|  |           Padding( | ||||||
|  |             padding: EdgeInsets.only( | ||||||
|  |               bottom: | ||||||
|  |                   kBottomControlHeight + MediaQuery.of(context).padding.bottom, | ||||||
|  |             ), | ||||||
|  |             child: | ||||||
|  |                 (postListState.value?.items.length ?? 0) > 0 | ||||||
|  |                     ? CardSwiper( | ||||||
|  |                       controller: cardSwiperController, | ||||||
|  |                       cardsCount: postListState.value!.items.length, | ||||||
|  |                       cardBuilder: ( | ||||||
|  |                         context, | ||||||
|  |                         index, | ||||||
|  |                         horizontalOffsetPercentage, | ||||||
|  |                         verticalOffsetPercentage, | ||||||
|  |                       ) { | ||||||
|  |                         return Center( | ||||||
|  |                           child: ConstrainedBox( | ||||||
|  |                             constraints: BoxConstraints(maxWidth: 540), | ||||||
|  |                             child: SingleChildScrollView( | ||||||
|  |                               child: Card( | ||||||
|  |                                 margin: EdgeInsets.zero, | ||||||
|  |                                 child: ClipRRect( | ||||||
|  |                                   borderRadius: const BorderRadius.all( | ||||||
|  |                                     Radius.circular(8), | ||||||
|  |                                   ), | ||||||
|  |                                   child: PostActionableItem( | ||||||
|  |                                     item: postListState.value!.items[index], | ||||||
|  |                                   ), | ||||||
|  |                                 ), | ||||||
|  |                               ), | ||||||
|  |                             ), | ||||||
|  |                           ), | ||||||
|  |                         ); | ||||||
|  |                       }, | ||||||
|  |                       onEnd: () { | ||||||
|  |                         if (postListState.value?.hasMore ?? true) { | ||||||
|  |                           postListNotifier.fetch( | ||||||
|  |                             cursor: postListState.value?.nextCursor, | ||||||
|  |                           ); | ||||||
|  |                         } | ||||||
|  |                       }, | ||||||
|  |                     ) | ||||||
|  |                     : Center(child: CircularProgressIndicator()), | ||||||
|  |           ), | ||||||
|  |           Positioned( | ||||||
|  |             left: 0, | ||||||
|  |             right: 0, | ||||||
|  |             bottom: 0, | ||||||
|  |             child: Container( | ||||||
|  |               color: Theme.of(context).colorScheme.surfaceContainer, | ||||||
|  |               padding: EdgeInsets.only( | ||||||
|  |                 bottom: MediaQuery.of(context).padding.bottom, | ||||||
|  |               ), | ||||||
|  |               height: kBottomControlHeight, | ||||||
|  |               child: | ||||||
|  |                   Row( | ||||||
|  |                     mainAxisAlignment: MainAxisAlignment.center, | ||||||
|  |                     children: [ | ||||||
|  |                       IconButton( | ||||||
|  |                         onPressed: () { | ||||||
|  |                           cardSwiperController.undo(); | ||||||
|  |                         }, | ||||||
|  |                         icon: const Icon(Symbols.arrow_left_alt), | ||||||
|  |                       ), | ||||||
|  |                       IconButton( | ||||||
|  |                         onPressed: () { | ||||||
|  |                           cardSwiperController.swipe(CardSwiperDirection.right); | ||||||
|  |                         }, | ||||||
|  |                         icon: const Icon(Symbols.arrow_right_alt), | ||||||
|  |                       ), | ||||||
|  |                     ], | ||||||
|  |                   ).padding(all: 8).center(), | ||||||
|  |             ), | ||||||
|  |           ), | ||||||
|  |         ], | ||||||
|  |       ), | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  | } | ||||||
							
								
								
									
										72
									
								
								pubspec.lock
									
									
									
									
									
								
							
							
						
						
									
										72
									
								
								pubspec.lock
									
									
									
									
									
								
							| @@ -565,10 +565,10 @@ packages: | |||||||
|     dependency: "direct main" |     dependency: "direct main" | ||||||
|     description: |     description: | ||||||
|       name: file_picker |       name: file_picker | ||||||
|       sha256: ef7d2a085c1b1d69d17b6842d0734aad90156de08df6bd3c12496d0bd6ddf8e2 |       sha256: e7e16c9d15c36330b94ca0e2ad8cb61f93cd5282d0158c09805aed13b5452f22 | ||||||
|       url: "https://pub.dev" |       url: "https://pub.dev" | ||||||
|     source: hosted |     source: hosted | ||||||
|     version: "10.3.1" |     version: "10.3.2" | ||||||
|   file_selector_linux: |   file_selector_linux: | ||||||
|     dependency: transitive |     dependency: transitive | ||||||
|     description: |     description: | ||||||
| @@ -581,10 +581,10 @@ packages: | |||||||
|     dependency: transitive |     dependency: transitive | ||||||
|     description: |     description: | ||||||
|       name: file_selector_macos |       name: file_selector_macos | ||||||
|       sha256: "8c9250b2bd2d8d4268e39c82543bacbaca0fda7d29e0728c3c4bbb7c820fd711" |       sha256: "19124ff4a3d8864fdc62072b6a2ef6c222d55a3404fe14893a3c02744907b60c" | ||||||
|       url: "https://pub.dev" |       url: "https://pub.dev" | ||||||
|     source: hosted |     source: hosted | ||||||
|     version: "0.9.4+3" |     version: "0.9.4+4" | ||||||
|   file_selector_platform_interface: |   file_selector_platform_interface: | ||||||
|     dependency: transitive |     dependency: transitive | ||||||
|     description: |     description: | ||||||
| @@ -734,6 +734,14 @@ packages: | |||||||
|       url: "https://pub.dev" |       url: "https://pub.dev" | ||||||
|     source: hosted |     source: hosted | ||||||
|     version: "3.4.1" |     version: "3.4.1" | ||||||
|  |   flutter_card_swiper: | ||||||
|  |     dependency: "direct main" | ||||||
|  |     description: | ||||||
|  |       name: flutter_card_swiper | ||||||
|  |       sha256: "1eacbfab31b572223042e03409726553aec431abe48af48c8d591d376d070d3d" | ||||||
|  |       url: "https://pub.dev" | ||||||
|  |     source: hosted | ||||||
|  |     version: "7.0.2" | ||||||
|   flutter_colorpicker: |   flutter_colorpicker: | ||||||
|     dependency: "direct main" |     dependency: "direct main" | ||||||
|     description: |     description: | ||||||
| @@ -754,10 +762,10 @@ packages: | |||||||
|     dependency: "direct main" |     dependency: "direct main" | ||||||
|     description: |     description: | ||||||
|       name: flutter_hooks |       name: flutter_hooks | ||||||
|       sha256: c3df76c62bb3a9f9bee75c57cdab40abab6123b734c1cd7e9b26a5dbd436eceb |       sha256: "8ae1f090e5f4ef5cfa6670ce1ab5dddadd33f3533a7f9ba19d9f958aa2a89f42" | ||||||
|       url: "https://pub.dev" |       url: "https://pub.dev" | ||||||
|     source: hosted |     source: hosted | ||||||
|     version: "0.21.3" |     version: "0.21.3+1" | ||||||
|   flutter_inappwebview: |   flutter_inappwebview: | ||||||
|     dependency: "direct main" |     dependency: "direct main" | ||||||
|     description: |     description: | ||||||
| @@ -1073,10 +1081,10 @@ packages: | |||||||
|     dependency: transitive |     dependency: transitive | ||||||
|     description: |     description: | ||||||
|       name: font_awesome_flutter |       name: font_awesome_flutter | ||||||
|       sha256: f50ce90dbe26d977415b9540400d6778bef00894aced6358ae578abd92b14b10 |       sha256: b738e35f8bb4957896c34957baf922f99c5d415b38ddc8b070d14b7fa95715d4 | ||||||
|       url: "https://pub.dev" |       url: "https://pub.dev" | ||||||
|     source: hosted |     source: hosted | ||||||
|     version: "10.9.0" |     version: "10.9.1" | ||||||
|   freezed: |   freezed: | ||||||
|     dependency: "direct dev" |     dependency: "direct dev" | ||||||
|     description: |     description: | ||||||
| @@ -1137,10 +1145,10 @@ packages: | |||||||
|     dependency: "direct main" |     dependency: "direct main" | ||||||
|     description: |     description: | ||||||
|       name: go_router |       name: go_router | ||||||
|       sha256: "8b1f37dfaf6e958c6b872322db06f946509433bec3de753c3491a42ae9ec2b48" |       sha256: ced3fdc143c1437234ac3b8e985f3286cf138968bb83ca9a6f94d22f2951c6b9 | ||||||
|       url: "https://pub.dev" |       url: "https://pub.dev" | ||||||
|     source: hosted |     source: hosted | ||||||
|     version: "16.1.0" |     version: "16.2.0" | ||||||
|   google_fonts: |   google_fonts: | ||||||
|     dependency: "direct main" |     dependency: "direct main" | ||||||
|     description: |     description: | ||||||
| @@ -1497,10 +1505,10 @@ packages: | |||||||
|     dependency: "direct main" |     dependency: "direct main" | ||||||
|     description: |     description: | ||||||
|       name: material_symbols_icons |       name: material_symbols_icons | ||||||
|       sha256: ef20d86fb34c2b59eb7553c4d795bb8a7ec8c890c53ffd3148c64f7adc46ae50 |       sha256: b1342194e859b2774f920b484c46f54a37a845488e23d570385fbe3ede92ee9f | ||||||
|       url: "https://pub.dev" |       url: "https://pub.dev" | ||||||
|     source: hosted |     source: hosted | ||||||
|     version: "4.2858.1" |     version: "4.2867.0" | ||||||
|   media_kit: |   media_kit: | ||||||
|     dependency: "direct main" |     dependency: "direct main" | ||||||
|     description: |     description: | ||||||
| @@ -1713,10 +1721,10 @@ packages: | |||||||
|     dependency: transitive |     dependency: transitive | ||||||
|     description: |     description: | ||||||
|       name: path_provider_foundation |       name: path_provider_foundation | ||||||
|       sha256: "4843174df4d288f5e29185bd6e72a6fbdf5a4a4602717eed565497429f179942" |       sha256: "16eef174aacb07e09c351502740fa6254c165757638eba1e9116b0a781201bbd" | ||||||
|       url: "https://pub.dev" |       url: "https://pub.dev" | ||||||
|     source: hosted |     source: hosted | ||||||
|     version: "2.4.1" |     version: "2.4.2" | ||||||
|   path_provider_linux: |   path_provider_linux: | ||||||
|     dependency: transitive |     dependency: transitive | ||||||
|     description: |     description: | ||||||
| @@ -1841,10 +1849,10 @@ packages: | |||||||
|     dependency: transitive |     dependency: transitive | ||||||
|     description: |     description: | ||||||
|       name: provider |       name: provider | ||||||
|       sha256: "4abbd070a04e9ddc287673bf5a030c7ca8b685ff70218720abab8b092f53dd84" |       sha256: "4e82183fa20e5ca25703ead7e05de9e4cceed1fbd1eadc1ac3cb6f565a09f272" | ||||||
|       url: "https://pub.dev" |       url: "https://pub.dev" | ||||||
|     source: hosted |     source: hosted | ||||||
|     version: "6.1.5" |     version: "6.1.5+1" | ||||||
|   pub_semver: |   pub_semver: | ||||||
|     dependency: transitive |     dependency: transitive | ||||||
|     description: |     description: | ||||||
| @@ -1897,42 +1905,42 @@ packages: | |||||||
|     dependency: "direct main" |     dependency: "direct main" | ||||||
|     description: |     description: | ||||||
|       name: record |       name: record | ||||||
|       sha256: "3d08502b77edf2a864aa6e4cd7874b983d42a80f3689431da053cc5e85c1ad21" |       sha256: "9dbc6ff3e784612f90a9b001373c45ff76b7a08abd2bd9fdf72c242320c8911c" | ||||||
|       url: "https://pub.dev" |       url: "https://pub.dev" | ||||||
|     source: hosted |     source: hosted | ||||||
|     version: "6.1.0" |     version: "6.1.1" | ||||||
|   record_android: |   record_android: | ||||||
|     dependency: transitive |     dependency: transitive | ||||||
|     description: |     description: | ||||||
|       name: record_android |       name: record_android | ||||||
|       sha256: "8b170e33d9866f9b51e01a767d7e1ecb97b9ecd629950bd87a47c79359ec57f8" |       sha256: "8361a791c9a3fa5c065f0b8b5adb10f12531f8538c86b19474cf7b56ea80d426" | ||||||
|       url: "https://pub.dev" |       url: "https://pub.dev" | ||||||
|     source: hosted |     source: hosted | ||||||
|     version: "1.4.0" |     version: "1.4.1" | ||||||
|   record_ios: |   record_ios: | ||||||
|     dependency: transitive |     dependency: transitive | ||||||
|     description: |     description: | ||||||
|       name: record_ios |       name: record_ios | ||||||
|       sha256: ad97d0a75933c44bcf5aff648e86e32fc05eb61f8fbef190f14968c8eaf86692 |       sha256: "895c9467faec72d8e718a3142b51114958f42f18053836a8b484a74f9372f51a" | ||||||
|       url: "https://pub.dev" |       url: "https://pub.dev" | ||||||
|     source: hosted |     source: hosted | ||||||
|     version: "1.1.0" |     version: "1.1.1" | ||||||
|   record_linux: |   record_linux: | ||||||
|     dependency: transitive |     dependency: transitive | ||||||
|     description: |     description: | ||||||
|       name: record_linux |       name: record_linux | ||||||
|       sha256: "785e8e8d6db109aa606d0669d95aaae416458aaa39782bb0abe0bee74eee17d7" |       sha256: "235b1f1fb84e810f8149cc0c2c731d7d697f8d1c333b32cb820c449bf7bb72d8" | ||||||
|       url: "https://pub.dev" |       url: "https://pub.dev" | ||||||
|     source: hosted |     source: hosted | ||||||
|     version: "1.2.0" |     version: "1.2.1" | ||||||
|   record_macos: |   record_macos: | ||||||
|     dependency: transitive |     dependency: transitive | ||||||
|     description: |     description: | ||||||
|       name: record_macos |       name: record_macos | ||||||
|       sha256: f1399bca76a1634da109e5b0cba764ed8332a2b4da49c704c66d2c553405ed81 |       sha256: "2849068bb59072f300ad63ed146e543d66afaef8263edba4de4834fc7c8d4d35" | ||||||
|       url: "https://pub.dev" |       url: "https://pub.dev" | ||||||
|     source: hosted |     source: hosted | ||||||
|     version: "1.1.0" |     version: "1.1.1" | ||||||
|   record_platform_interface: |   record_platform_interface: | ||||||
|     dependency: transitive |     dependency: transitive | ||||||
|     description: |     description: | ||||||
| @@ -1953,10 +1961,10 @@ packages: | |||||||
|     dependency: transitive |     dependency: transitive | ||||||
|     description: |     description: | ||||||
|       name: record_windows |       name: record_windows | ||||||
|       sha256: "85a22fc97f6d73ecd67c8ba5f2f472b74ef1d906f795b7970f771a0914167e99" |       sha256: "223258060a1d25c62bae18282c16783f28581ec19401d17e56b5205b9f039d78" | ||||||
|       url: "https://pub.dev" |       url: "https://pub.dev" | ||||||
|     source: hosted |     source: hosted | ||||||
|     version: "1.0.6" |     version: "1.0.7" | ||||||
|   relative_time: |   relative_time: | ||||||
|     dependency: "direct main" |     dependency: "direct main" | ||||||
|     description: |     description: | ||||||
| @@ -2496,10 +2504,10 @@ packages: | |||||||
|     dependency: transitive |     dependency: transitive | ||||||
|     description: |     description: | ||||||
|       name: url_launcher_ios |       name: url_launcher_ios | ||||||
|       sha256: "7f2022359d4c099eea7df3fdf739f7d3d3b9faf3166fb1dd390775176e0b76cb" |       sha256: d80b3f567a617cb923546034cc94bfe44eb15f989fe670b37f26abdb9d939cb7 | ||||||
|       url: "https://pub.dev" |       url: "https://pub.dev" | ||||||
|     source: hosted |     source: hosted | ||||||
|     version: "6.3.3" |     version: "6.3.4" | ||||||
|   url_launcher_linux: |   url_launcher_linux: | ||||||
|     dependency: transitive |     dependency: transitive | ||||||
|     description: |     description: | ||||||
| @@ -2512,10 +2520,10 @@ packages: | |||||||
|     dependency: transitive |     dependency: transitive | ||||||
|     description: |     description: | ||||||
|       name: url_launcher_macos |       name: url_launcher_macos | ||||||
|       sha256: "17ba2000b847f334f16626a574c702b196723af2a289e7a93ffcb79acff855c2" |       sha256: c043a77d6600ac9c38300567f33ef12b0ef4f4783a2c1f00231d2b1941fea13f | ||||||
|       url: "https://pub.dev" |       url: "https://pub.dev" | ||||||
|     source: hosted |     source: hosted | ||||||
|     version: "3.2.2" |     version: "3.2.3" | ||||||
|   url_launcher_platform_interface: |   url_launcher_platform_interface: | ||||||
|     dependency: transitive |     dependency: transitive | ||||||
|     description: |     description: | ||||||
|   | |||||||
							
								
								
									
										13
									
								
								pubspec.yaml
									
									
									
									
									
								
							
							
						
						
									
										13
									
								
								pubspec.yaml
									
									
									
									
									
								
							| @@ -16,7 +16,7 @@ publish_to: "none" # Remove this line if you wish to publish to pub.dev | |||||||
| # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html | # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html | ||||||
| # In Windows, build-name is used as the major, minor, and patch parts | # In Windows, build-name is used as the major, minor, and patch parts | ||||||
| # of the product and file versions while build-number is used as the build suffix. | # of the product and file versions while build-number is used as the build suffix. | ||||||
| version: 3.2.0+126 | version: 3.2.0+128 | ||||||
|  |  | ||||||
| environment: | environment: | ||||||
|   sdk: ^3.7.2 |   sdk: ^3.7.2 | ||||||
| @@ -36,10 +36,10 @@ dependencies: | |||||||
|   # The following adds the Cupertino Icons font to your application. |   # The following adds the Cupertino Icons font to your application. | ||||||
|   # Use with the CupertinoIcons class for iOS style icons. |   # Use with the CupertinoIcons class for iOS style icons. | ||||||
|   cupertino_icons: ^1.0.8 |   cupertino_icons: ^1.0.8 | ||||||
|   flutter_hooks: ^0.21.3 |   flutter_hooks: ^0.21.3+1 | ||||||
|   hooks_riverpod: ^2.6.1 |   hooks_riverpod: ^2.6.1 | ||||||
|   bitsdojo_window: ^0.1.6 |   bitsdojo_window: ^0.1.6 | ||||||
|   go_router: ^16.1.0 |   go_router: ^16.2.0 | ||||||
|   styled_widget: ^0.4.1 |   styled_widget: ^0.4.1 | ||||||
|   shared_preferences: ^2.5.3 |   shared_preferences: ^2.5.3 | ||||||
|   flutter_riverpod: ^2.6.1 |   flutter_riverpod: ^2.6.1 | ||||||
| @@ -73,7 +73,7 @@ dependencies: | |||||||
|     git: https://github.com/LittleSheep2Code/tus_client.git |     git: https://github.com/LittleSheep2Code/tus_client.git | ||||||
|   cross_file: ^0.3.4+2 |   cross_file: ^0.3.4+2 | ||||||
|   image_picker: ^1.2.0 |   image_picker: ^1.2.0 | ||||||
|   file_picker: ^10.3.1 |   file_picker: ^10.3.2 | ||||||
|   riverpod_annotation: ^2.6.1 |   riverpod_annotation: ^2.6.1 | ||||||
|   image_picker_platform_interface: ^2.11.0 |   image_picker_platform_interface: ^2.11.0 | ||||||
|   image_picker_android: ^0.8.13 |   image_picker_android: ^0.8.13 | ||||||
| @@ -83,7 +83,7 @@ dependencies: | |||||||
|   flutter_udid: ^4.0.0 |   flutter_udid: ^4.0.0 | ||||||
|   firebase_core: ^4.0.0 |   firebase_core: ^4.0.0 | ||||||
|   web_socket_channel: ^3.0.3 |   web_socket_channel: ^3.0.3 | ||||||
|   material_symbols_icons: ^4.2858.1 |   material_symbols_icons: ^4.2867.0 | ||||||
|   drift: ^2.28.1 |   drift: ^2.28.1 | ||||||
|   drift_flutter: ^0.2.5 |   drift_flutter: ^0.2.5 | ||||||
|   path: ^1.9.1 |   path: ^1.9.1 | ||||||
| @@ -107,7 +107,7 @@ dependencies: | |||||||
|   livekit_client: ^2.5.0+hotfix.1 |   livekit_client: ^2.5.0+hotfix.1 | ||||||
|   pasteboard: ^0.4.0 |   pasteboard: ^0.4.0 | ||||||
|   flutter_colorpicker: ^1.1.0 |   flutter_colorpicker: ^1.1.0 | ||||||
|   record: ^6.1.0 |   record: ^6.1.1 | ||||||
|   qr_flutter: ^4.1.0 |   qr_flutter: ^4.1.0 | ||||||
|   flutter_otp_text_field: ^1.5.1+1 |   flutter_otp_text_field: ^1.5.1+1 | ||||||
|   palette_generator: ^0.3.3+7 |   palette_generator: ^0.3.3+7 | ||||||
| @@ -138,6 +138,7 @@ dependencies: | |||||||
|   firebase_analytics: ^12.0.0 |   firebase_analytics: ^12.0.0 | ||||||
|   material_color_utilities: ^0.11.1 |   material_color_utilities: ^0.11.1 | ||||||
|   screenshot: ^3.0.0 |   screenshot: ^3.0.0 | ||||||
|  |   flutter_card_swiper: ^7.0.2 | ||||||
|  |  | ||||||
| dev_dependencies: | dev_dependencies: | ||||||
|   flutter_test: |   flutter_test: | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user