Compare commits
	
		
			32 Commits
		
	
	
		
			3.2.0+127
			...
			abf395ff9a
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 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 | 
@@ -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",
 | 
				
			||||||
@@ -854,5 +866,53 @@
 | 
				
			|||||||
  "failedToLoadUserInfo": "Failed to load user info",
 | 
					  "failedToLoadUserInfo": "Failed to load user info",
 | 
				
			||||||
  "failedToLoadUserInfoNetwork": "It seems be network issue, you can tap the button below to try again.",
 | 
					  "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.",
 | 
					  "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"
 | 
					  "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"
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -345,7 +345,7 @@
 | 
				
			|||||||
  "accountSettingsHelpContent": "此页面允许您管理您的帐户安全性、隐私和其他设置。如果您需要帮助,请联系管理员。",
 | 
					  "accountSettingsHelpContent": "此页面允许您管理您的帐户安全性、隐私和其他设置。如果您需要帮助,请联系管理员。",
 | 
				
			||||||
  "unauthorized": "未授权",
 | 
					  "unauthorized": "未授权",
 | 
				
			||||||
  "unauthorizedHint": "您未登录或会话已过期,请重新登录。",
 | 
					  "unauthorizedHint": "您未登录或会话已过期,请重新登录。",
 | 
				
			||||||
  "publisherBelongsTo": "属于",
 | 
					  "publisherBelongsTo": "属于 {}",
 | 
				
			||||||
  "postContent": "内容",
 | 
					  "postContent": "内容",
 | 
				
			||||||
  "postSettings": "设置",
 | 
					  "postSettings": "设置",
 | 
				
			||||||
  "postPublisherUnselected": "未指定发布者",
 | 
					  "postPublisherUnselected": "未指定发布者",
 | 
				
			||||||
@@ -828,5 +828,20 @@
 | 
				
			|||||||
  "failedToLoadUserInfo": "加载用户信息失败",
 | 
					  "failedToLoadUserInfo": "加载用户信息失败",
 | 
				
			||||||
  "failedToLoadUserInfoNetwork": "这看起来是个网络问题,你可以按下面的按钮来重试",
 | 
					  "failedToLoadUserInfoNetwork": "这看起来是个网络问题,你可以按下面的按钮来重试",
 | 
				
			||||||
  "failedToLoadUserInfoUnauthorized": "看来您的会话已被注销或不再可用,如果您愿意,您仍然可以再次尝试获取用户信息。",
 | 
					  "failedToLoadUserInfoUnauthorized": "看来您的会话已被注销或不再可用,如果您愿意,您仍然可以再次尝试获取用户信息。",
 | 
				
			||||||
  "okay": "了解"
 | 
					  "okay": "了解",
 | 
				
			||||||
 | 
					  "postDetails": "帖子详情",
 | 
				
			||||||
 | 
					  "mimeType": "类型",
 | 
				
			||||||
 | 
					  "fileSize": "大小",
 | 
				
			||||||
 | 
					  "fileHash": "哈希",
 | 
				
			||||||
 | 
					  "exifData": "EXIF 数据",
 | 
				
			||||||
 | 
					  "leveling": "等级",
 | 
				
			||||||
 | 
					  "levelingHistory": "经验记录",
 | 
				
			||||||
 | 
					  "stellarProgram": "恒星计划",
 | 
				
			||||||
 | 
					  "socialCredits": "社会信用点",
 | 
				
			||||||
 | 
					  "credits": "信用",
 | 
				
			||||||
 | 
					  "socialCreditsDescription": "社会信用是 Solar Network 评价用户的一种方式。它基于用户的行为和互动来计算。以 100 分为基准,分数越高表示用户在社区中的信誉越好。分数会随着时间的推移而变化,反映用户的最新行为。信用等级高的用户可以享受到更多的福利,反之的用户部份功能可能受到限制。",
 | 
				
			||||||
 | 
					  "socialCreditsLevelPoor": "糟糕",
 | 
				
			||||||
 | 
					  "socialCreditsLevelNormal": "正常",
 | 
				
			||||||
 | 
					  "socialCreditsLevelGood": "良好",
 | 
				
			||||||
 | 
					  "socialCreditsLevelExcellent": "优秀"
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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 null;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  return $SnChatMessageCopyWith<$Res>(_self.lastMessage, (value) {
 | 
					  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 null;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  return $SnChatMessageCopyWith<$Res>(_self.lastMessage, (value) {
 | 
					  return $SnChatMessageCopyWith<$Res>(_self.lastMessage!, (value) {
 | 
				
			||||||
    return _then(_self.copyWith(lastMessage: value));
 | 
					    return _then(_self.copyWith(lastMessage: value));
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -213,7 +213,10 @@ 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'] == null
 | 
				
			||||||
 | 
					              ? null
 | 
				
			||||||
 | 
					              : SnChatMessage.fromJson(
 | 
				
			||||||
                json['last_message'] as Map<String, dynamic>,
 | 
					                json['last_message'] as Map<String, dynamic>,
 | 
				
			||||||
              ),
 | 
					              ),
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
@@ -221,7 +224,7 @@ _SnChatSummary _$SnChatSummaryFromJson(Map<String, dynamic> json) =>
 | 
				
			|||||||
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) =>
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										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,5 +1,6 @@
 | 
				
			|||||||
import 'dart:convert';
 | 
					import 'dart:convert';
 | 
				
			||||||
import 'dart:developer';
 | 
					import 'dart:developer';
 | 
				
			||||||
 | 
					import 'dart:io' show Platform;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import 'package:dio/dio.dart';
 | 
					import 'package:dio/dio.dart';
 | 
				
			||||||
import 'package:easy_localization/easy_localization.dart';
 | 
					import 'package:easy_localization/easy_localization.dart';
 | 
				
			||||||
@@ -28,7 +29,10 @@ class UserInfoNotifier extends StateNotifier<AsyncValue<SnAccount?>> {
 | 
				
			|||||||
      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);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      if (kIsWeb || !Platform.isLinux) {
 | 
				
			||||||
        FirebaseAnalytics.instance.setUserId(id: user.id);
 | 
					        FirebaseAnalytics.instance.setUserId(id: user.id);
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
    } catch (error, stackTrace) {
 | 
					    } catch (error, stackTrace) {
 | 
				
			||||||
      if (!kIsWeb) {
 | 
					      if (!kIsWeb) {
 | 
				
			||||||
        if (error is DioException) {
 | 
					        if (error is DioException) {
 | 
				
			||||||
@@ -83,8 +87,10 @@ 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);
 | 
				
			||||||
 | 
					    if (kIsWeb || !Platform.isLinux) {
 | 
				
			||||||
      FirebaseAnalytics.instance.setUserId(id: null);
 | 
					      FirebaseAnalytics.instance.setUserId(id: null);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
final userInfoProvider =
 | 
					final userInfoProvider =
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										132
									
								
								lib/route.dart
									
									
									
									
									
								
							
							
						
						
									
										132
									
								
								lib/route.dart
									
									
									
									
									
								
							@@ -6,11 +6,19 @@ 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/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 +41,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 +62,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 +297,89 @@ 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(
 | 
				
			||||||
 | 
					                name: 'developerProjectNew',
 | 
				
			||||||
 | 
					                path: '/developers/:name/projects/new',
 | 
				
			||||||
 | 
					                builder:
 | 
				
			||||||
 | 
					                    (context, state) => NewProjectScreen(
 | 
				
			||||||
 | 
					                      publisherName: state.pathParameters['name']!,
 | 
				
			||||||
 | 
					                    ),
 | 
				
			||||||
 | 
					              ),
 | 
				
			||||||
 | 
					              GoRoute(
 | 
				
			||||||
 | 
					                name: 'developerProjectEdit',
 | 
				
			||||||
 | 
					                path: '/developers/:name/projects/:id/edit',
 | 
				
			||||||
 | 
					                builder:
 | 
				
			||||||
 | 
					                    (context, state) => EditProjectScreen(
 | 
				
			||||||
 | 
					                      publisherName: state.pathParameters['name']!,
 | 
				
			||||||
 | 
					                      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(
 | 
					                  GoRoute(
 | 
				
			||||||
                    name: 'developerAppNew',
 | 
					                    name: 'developerAppNew',
 | 
				
			||||||
                path: '/developers/:name/apps/new',
 | 
					                    path: 'apps/new',
 | 
				
			||||||
                    builder:
 | 
					                    builder:
 | 
				
			||||||
                        (context, state) => NewCustomAppScreen(
 | 
					                        (context, state) => NewCustomAppScreen(
 | 
				
			||||||
                          publisherName: state.pathParameters['name']!,
 | 
					                          publisherName: state.pathParameters['name']!,
 | 
				
			||||||
 | 
					                          projectId: state.pathParameters['projectId']!,
 | 
				
			||||||
                        ),
 | 
					                        ),
 | 
				
			||||||
                  ),
 | 
					                  ),
 | 
				
			||||||
                  GoRoute(
 | 
					                  GoRoute(
 | 
				
			||||||
                    name: 'developerAppEdit',
 | 
					                    name: 'developerAppEdit',
 | 
				
			||||||
                path: '/developers/:name/apps/:id',
 | 
					                    path: 'apps/:id/edit',
 | 
				
			||||||
                    builder:
 | 
					                    builder:
 | 
				
			||||||
                        (context, state) => EditAppScreen(
 | 
					                        (context, state) => EditAppScreen(
 | 
				
			||||||
                          publisherName: state.pathParameters['name']!,
 | 
					                          publisherName: state.pathParameters['name']!,
 | 
				
			||||||
 | 
					                          projectId: state.pathParameters['projectId']!,
 | 
				
			||||||
                          id: state.pathParameters['id']!,
 | 
					                          id: state.pathParameters['id']!,
 | 
				
			||||||
                        ),
 | 
					                        ),
 | 
				
			||||||
                  ),
 | 
					                  ),
 | 
				
			||||||
 | 
					                  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 +446,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 +463,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 +479,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 +613,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 +639,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,13 +88,140 @@ 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,
 | 
				
			||||||
 | 
					    SnAccount user,
 | 
				
			||||||
 | 
					  ) {
 | 
				
			||||||
 | 
					    final currentLevel = user.profile.level;
 | 
				
			||||||
 | 
					    final currentExp = user.profile.experience;
 | 
				
			||||||
 | 
					    final progress = user.profile.levelingProgress;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return Center(
 | 
				
			||||||
 | 
					      child: Container(
 | 
				
			||||||
 | 
					        padding: const EdgeInsets.symmetric(horizontal: 20),
 | 
				
			||||||
 | 
					        constraints: const BoxConstraints(maxWidth: 480),
 | 
				
			||||||
 | 
					        child: CustomScrollView(
 | 
				
			||||||
 | 
					          slivers: [
 | 
				
			||||||
 | 
					            const SliverGap(20),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Current Progress Card
 | 
				
			||||||
 | 
					            SliverToBoxAdapter(
 | 
				
			||||||
 | 
					              child: LevelingProgressCard(
 | 
				
			||||||
 | 
					                level: currentLevel,
 | 
				
			||||||
 | 
					                experience: currentExp,
 | 
				
			||||||
 | 
					                progress: progress,
 | 
				
			||||||
 | 
					              ),
 | 
				
			||||||
 | 
					            ),
 | 
				
			||||||
 | 
					            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),
 | 
					      padding: getTabbedPadding(context, horizontal: 20, vertical: 20),
 | 
				
			||||||
      child: Center(
 | 
					      child: Center(
 | 
				
			||||||
        child: ConstrainedBox(
 | 
					        child: ConstrainedBox(
 | 
				
			||||||
@@ -64,36 +229,12 @@ class LevelingScreen extends HookConsumerWidget {
 | 
				
			|||||||
          child: Column(
 | 
					          child: Column(
 | 
				
			||||||
            crossAxisAlignment: CrossAxisAlignment.stretch,
 | 
					            crossAxisAlignment: CrossAxisAlignment.stretch,
 | 
				
			||||||
            children: [
 | 
					            children: [
 | 
				
			||||||
                // Current Progress Card
 | 
					 | 
				
			||||||
                LevelingProgressCard(
 | 
					 | 
				
			||||||
                  level: currentLevel,
 | 
					 | 
				
			||||||
                  experience: currentExp,
 | 
					 | 
				
			||||||
                  progress: progress,
 | 
					 | 
				
			||||||
                ),
 | 
					 | 
				
			||||||
                const Gap(24),
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                // Level Stairs Graph
 | 
					 | 
				
			||||||
                Text(
 | 
					 | 
				
			||||||
                  'levelProgress'.tr(),
 | 
					 | 
				
			||||||
                  style: Theme.of(context).textTheme.headlineSmall?.copyWith(
 | 
					 | 
				
			||||||
                    fontWeight: FontWeight.bold,
 | 
					 | 
				
			||||||
                  ),
 | 
					 | 
				
			||||||
                ),
 | 
					 | 
				
			||||||
                const Gap(16),
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                // Stairs visualization with fixed height and horizontal scroll
 | 
					 | 
				
			||||||
                _buildLevelStairs(context, currentLevel),
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                const Gap(24),
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                // Membership section
 | 
					 | 
				
			||||||
              _buildMembershipSection(context, ref, stellarSubscription),
 | 
					              _buildMembershipSection(context, ref, stellarSubscription),
 | 
				
			||||||
              const Gap(16),
 | 
					              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,18 +79,21 @@ class ChatRoomListTile extends HookConsumerWidget {
 | 
				
			|||||||
                    color: Theme.of(context).colorScheme.primary,
 | 
					                    color: Theme.of(context).colorScheme.primary,
 | 
				
			||||||
                  ),
 | 
					                  ),
 | 
				
			||||||
                ),
 | 
					                ),
 | 
				
			||||||
 | 
					              if (data.lastMessage == null)
 | 
				
			||||||
 | 
					                Text(room.description ?? 'descriptionNone'.tr(), maxLines: 1)
 | 
				
			||||||
 | 
					              else
 | 
				
			||||||
                Row(
 | 
					                Row(
 | 
				
			||||||
                  spacing: 4,
 | 
					                  spacing: 4,
 | 
				
			||||||
                  children: [
 | 
					                  children: [
 | 
				
			||||||
                    Badge(
 | 
					                    Badge(
 | 
				
			||||||
                    label: Text(data.lastMessage.sender.account.nick),
 | 
					                      label: Text(data.lastMessage!.sender.account.nick),
 | 
				
			||||||
                      textColor: Theme.of(context).colorScheme.onPrimary,
 | 
					                      textColor: Theme.of(context).colorScheme.onPrimary,
 | 
				
			||||||
                      backgroundColor: Theme.of(context).colorScheme.primary,
 | 
					                      backgroundColor: Theme.of(context).colorScheme.primary,
 | 
				
			||||||
                    ),
 | 
					                    ),
 | 
				
			||||||
                    Expanded(
 | 
					                    Expanded(
 | 
				
			||||||
                      child: Text(
 | 
					                      child: Text(
 | 
				
			||||||
                      (data.lastMessage.content?.isNotEmpty ?? false)
 | 
					                        (data.lastMessage!.content?.isNotEmpty ?? false)
 | 
				
			||||||
                          ? data.lastMessage.content!
 | 
					                            ? data.lastMessage!.content!
 | 
				
			||||||
                            : 'messageNone'.tr(),
 | 
					                            : 'messageNone'.tr(),
 | 
				
			||||||
                        maxLines: 1,
 | 
					                        maxLines: 1,
 | 
				
			||||||
                        overflow: TextOverflow.ellipsis,
 | 
					                        overflow: TextOverflow.ellipsis,
 | 
				
			||||||
@@ -100,7 +103,9 @@ class ChatRoomListTile extends HookConsumerWidget {
 | 
				
			|||||||
                    Align(
 | 
					                    Align(
 | 
				
			||||||
                      alignment: Alignment.centerRight,
 | 
					                      alignment: Alignment.centerRight,
 | 
				
			||||||
                      child: Text(
 | 
					                      child: Text(
 | 
				
			||||||
                      RelativeTime(context).format(data.lastMessage.createdAt),
 | 
					                        RelativeTime(
 | 
				
			||||||
 | 
					                          context,
 | 
				
			||||||
 | 
					                        ).format(data.lastMessage!.createdAt),
 | 
				
			||||||
                        style: Theme.of(context).textTheme.bodySmall,
 | 
					                        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>> {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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,43 +15,65 @@ 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<List<CustomApp>> customApps(
 | 
				
			||||||
 | 
					  Ref ref,
 | 
				
			||||||
 | 
					  String publisherName,
 | 
				
			||||||
 | 
					  String projectId,
 | 
				
			||||||
 | 
					) 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',
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					  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,
 | 
				
			||||||
 | 
					              children: [
 | 
				
			||||||
 | 
					                Text('noCustomApps').tr(),
 | 
				
			||||||
 | 
					                const SizedBox(height: 16),
 | 
				
			||||||
 | 
					                ElevatedButton.icon(
 | 
				
			||||||
                  onPressed: () {
 | 
					                  onPressed: () {
 | 
				
			||||||
                    context.pushNamed(
 | 
					                    context.pushNamed(
 | 
				
			||||||
                      'developerAppNew',
 | 
					                      'developerAppNew',
 | 
				
			||||||
                pathParameters: {'name': publisherName},
 | 
					                      pathParameters: {
 | 
				
			||||||
 | 
					                        'name': publisherName,
 | 
				
			||||||
 | 
					                        'projectId': projectId,
 | 
				
			||||||
 | 
					                      },
 | 
				
			||||||
                    );
 | 
					                    );
 | 
				
			||||||
                  },
 | 
					                  },
 | 
				
			||||||
 | 
					                  icon: const Icon(Symbols.add),
 | 
				
			||||||
 | 
					                  label: Text('createCustomApp').tr(),
 | 
				
			||||||
                ),
 | 
					                ),
 | 
				
			||||||
              ],
 | 
					              ],
 | 
				
			||||||
            ),
 | 
					            ),
 | 
				
			||||||
      body: apps.when(
 | 
					          );
 | 
				
			||||||
        data: (data) {
 | 
					 | 
				
			||||||
          if (data.isEmpty) {
 | 
					 | 
				
			||||||
            return Center(child: Text('noCustomApps').tr());
 | 
					 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        return RefreshIndicator(
 | 
					        return RefreshIndicator(
 | 
				
			||||||
          onRefresh:
 | 
					          onRefresh:
 | 
				
			||||||
                () => ref.refresh(customAppsProvider(publisherName).future),
 | 
					              () => ref.refresh(
 | 
				
			||||||
 | 
					                customAppsProvider(publisherName, projectId).future,
 | 
				
			||||||
 | 
					              ),
 | 
				
			||||||
          child: ListView.builder(
 | 
					          child: ListView.builder(
 | 
				
			||||||
            padding: EdgeInsets.only(top: 4),
 | 
					            padding: EdgeInsets.only(top: 4),
 | 
				
			||||||
            itemCount: data.length,
 | 
					            itemCount: data.length,
 | 
				
			||||||
@@ -128,6 +149,7 @@ class CustomAppsScreen extends HookConsumerWidget {
 | 
				
			|||||||
                              'developerAppEdit',
 | 
					                              'developerAppEdit',
 | 
				
			||||||
                              pathParameters: {
 | 
					                              pathParameters: {
 | 
				
			||||||
                                'name': publisherName,
 | 
					                                'name': publisherName,
 | 
				
			||||||
 | 
					                                'projectId': projectId,
 | 
				
			||||||
                                'id': app.id,
 | 
					                                'id': app.id,
 | 
				
			||||||
                              },
 | 
					                              },
 | 
				
			||||||
                            );
 | 
					                            );
 | 
				
			||||||
@@ -139,10 +161,10 @@ 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),
 | 
				
			||||||
                                );
 | 
					                                );
 | 
				
			||||||
                              }
 | 
					                              }
 | 
				
			||||||
                            });
 | 
					                            });
 | 
				
			||||||
@@ -161,7 +183,9 @@ class CustomAppsScreen extends HookConsumerWidget {
 | 
				
			|||||||
      error:
 | 
					      error:
 | 
				
			||||||
          (err, stack) => ResponseErrorWidget(
 | 
					          (err, stack) => ResponseErrorWidget(
 | 
				
			||||||
            error: err,
 | 
					            error: err,
 | 
				
			||||||
              onRetry: () => ref.invalidate(customAppsProvider(publisherName)),
 | 
					            onRetry:
 | 
				
			||||||
 | 
					                () => ref.invalidate(
 | 
				
			||||||
 | 
					                  customAppsProvider(publisherName, projectId),
 | 
				
			||||||
                ),
 | 
					                ),
 | 
				
			||||||
          ),
 | 
					          ),
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -6,7 +6,7 @@ part of 'apps.dart';
 | 
				
			|||||||
// RiverpodGenerator
 | 
					// RiverpodGenerator
 | 
				
			||||||
// **************************************************************************
 | 
					// **************************************************************************
 | 
				
			||||||
 | 
					
 | 
				
			||||||
String _$customAppsHash() => r'c6ac78060eb51a2b208a749a81ecbe0a9c608ce1';
 | 
					String _$customAppsHash() => r'450bedaf4220b8963cb44afeb14d4c0e80f01b11';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/// Copied from Dart SDK
 | 
					/// Copied from Dart SDK
 | 
				
			||||||
class _SystemHash {
 | 
					class _SystemHash {
 | 
				
			||||||
@@ -39,15 +39,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 +68,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 +80,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 +91,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 +111,7 @@ class CustomAppsProvider extends AutoDisposeFutureProvider<List<CustomApp>> {
 | 
				
			|||||||
        allTransitiveDependencies: null,
 | 
					        allTransitiveDependencies: null,
 | 
				
			||||||
        debugGetCreateSourceHash: null,
 | 
					        debugGetCreateSourceHash: null,
 | 
				
			||||||
        publisherName: publisherName,
 | 
					        publisherName: publisherName,
 | 
				
			||||||
 | 
					        projectId: projectId,
 | 
				
			||||||
      ),
 | 
					      ),
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
@@ -119,13 +123,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 +143,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 +155,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,
 | 
				
			||||||
      };
 | 
					      };
 | 
				
			||||||
 | 
					      try {
 | 
				
			||||||
 | 
					        showLoadingModal(context);
 | 
				
			||||||
        if (isNew) {
 | 
					        if (isNew) {
 | 
				
			||||||
          await client.post(
 | 
					          await client.post(
 | 
				
			||||||
          '/develop/developers/$publisherName/apps',
 | 
					            '/develop/developers/$publisherName/projects/$projectId/apps',
 | 
				
			||||||
            data: data,
 | 
					            data: data,
 | 
				
			||||||
          );
 | 
					          );
 | 
				
			||||||
        } else {
 | 
					        } else {
 | 
				
			||||||
          await client.patch(
 | 
					          await client.patch(
 | 
				
			||||||
          '/develop/developers/$publisherName/apps/$id',
 | 
					            '/develop/developers/$publisherName/projects/$projectId/apps/$id',
 | 
				
			||||||
            data: data,
 | 
					            data: data,
 | 
				
			||||||
          );
 | 
					          );
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
      ref.invalidate(customAppsProvider(publisherName));
 | 
					      } catch (err) {
 | 
				
			||||||
 | 
					        showErrorAlert(err);
 | 
				
			||||||
 | 
					        return;
 | 
				
			||||||
 | 
					      } finally {
 | 
				
			||||||
 | 
					        if (context.mounted) hideLoadingModal(context);
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      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
 | 
				
			||||||
@@ -116,33 +116,89 @@ class SliverArticlesList extends ConsumerWidget {
 | 
				
			|||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class ArticlesScreen extends ConsumerWidget {
 | 
					@riverpod
 | 
				
			||||||
  final String? feedId;
 | 
					Future<List<SnWebFeed>> subscribedFeeds(Ref ref) async {
 | 
				
			||||||
  final String? publisherId;
 | 
					  final client = ref.watch(apiClientProvider);
 | 
				
			||||||
  final String? title;
 | 
					  final response = await client.get('/sphere/feeds/subscribed');
 | 
				
			||||||
 | 
					  final data = response.data as List<dynamic>;
 | 
				
			||||||
 | 
					  return data.map((json) => SnWebFeed.fromJson(json)).toList();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const ArticlesScreen({super.key, this.feedId, this.publisherId, this.title});
 | 
					class ArticlesScreen extends ConsumerWidget {
 | 
				
			||||||
 | 
					  const ArticlesScreen({super.key});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  @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(
 | 
				
			||||||
 | 
					      data: (feeds) {
 | 
				
			||||||
 | 
					        return DefaultTabController(
 | 
				
			||||||
 | 
					          length: feeds.length + 1,
 | 
				
			||||||
 | 
					          child: AppScaffold(
 | 
				
			||||||
 | 
					            appBar: AppBar(
 | 
				
			||||||
 | 
					              title: const Text('Articles'),
 | 
				
			||||||
 | 
					              bottom: TabBar(
 | 
				
			||||||
 | 
					                isScrollable: true,
 | 
				
			||||||
 | 
					                tabs: [
 | 
				
			||||||
 | 
					                  const Tab(text: 'All'),
 | 
				
			||||||
 | 
					                  ...feeds.map((feed) => Tab(text: feed.title)),
 | 
				
			||||||
 | 
					                ],
 | 
				
			||||||
 | 
					              ),
 | 
				
			||||||
 | 
					            ),
 | 
				
			||||||
 | 
					            body: TabBarView(
 | 
				
			||||||
 | 
					              children: [
 | 
				
			||||||
 | 
					                Center(
 | 
				
			||||||
                  child: ConstrainedBox(
 | 
					                  child: ConstrainedBox(
 | 
				
			||||||
                    constraints: const BoxConstraints(maxWidth: 560),
 | 
					                    constraints: const BoxConstraints(maxWidth: 560),
 | 
				
			||||||
                    child: CustomScrollView(
 | 
					                    child: CustomScrollView(
 | 
				
			||||||
                      slivers: [
 | 
					                      slivers: [
 | 
				
			||||||
                        SliverPadding(
 | 
					                        SliverPadding(
 | 
				
			||||||
                padding: const EdgeInsets.only(top: 8, left: 8, right: 8),
 | 
					                          padding: const EdgeInsets.only(
 | 
				
			||||||
                sliver: SliverArticlesList(
 | 
					                            top: 8,
 | 
				
			||||||
                  feedId: feedId,
 | 
					                            left: 8,
 | 
				
			||||||
                  publisherId: publisherId,
 | 
					                            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),
 | 
				
			||||||
 | 
					                          ),
 | 
				
			||||||
 | 
					                        ],
 | 
				
			||||||
 | 
					                      ),
 | 
				
			||||||
 | 
					                    ),
 | 
				
			||||||
 | 
					                  );
 | 
				
			||||||
 | 
					                }).toList(),
 | 
				
			||||||
 | 
					              ],
 | 
				
			||||||
 | 
					            ),
 | 
				
			||||||
 | 
					          ),
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      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'cd2f5d7d4ea49ad00dc731f8fc2ed65450a3f0e4';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// 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) => [
 | 
				
			||||||
 | 
					                          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');
 | 
					                              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(),
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										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,
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,5 +1,6 @@
 | 
				
			|||||||
import 'package:dio/dio.dart';
 | 
					import 'package:dio/dio.dart';
 | 
				
			||||||
import 'package:easy_localization/easy_localization.dart';
 | 
					import 'package:easy_localization/easy_localization.dart';
 | 
				
			||||||
 | 
					import 'package:flutter/foundation.dart';
 | 
				
			||||||
import 'package:island/screens/chat/chat.dart';
 | 
					import 'package:island/screens/chat/chat.dart';
 | 
				
			||||||
import 'package:flutter/material.dart';
 | 
					import 'package:flutter/material.dart';
 | 
				
			||||||
import 'package:island/models/chat.dart';
 | 
					import 'package:island/models/chat.dart';
 | 
				
			||||||
@@ -520,9 +521,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();
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -541,6 +544,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();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -553,52 +557,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);
 | 
					 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -610,18 +571,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 {
 | 
				
			||||||
@@ -638,9 +591,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);
 | 
				
			||||||
@@ -652,13 +603,18 @@ 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,
 | 
				
			||||||
 | 
					              builder:
 | 
				
			||||||
 | 
					                  (context, _) => Text(
 | 
				
			||||||
 | 
					                    'members'.plural(memberListNotifier.totalCount.value),
 | 
				
			||||||
 | 
					                    key: ValueKey(memberListNotifier),
 | 
				
			||||||
                    style: Theme.of(context).textTheme.headlineSmall?.copyWith(
 | 
					                    style: Theme.of(context).textTheme.headlineSmall?.copyWith(
 | 
				
			||||||
                      fontWeight: FontWeight.w600,
 | 
					                      fontWeight: FontWeight.w600,
 | 
				
			||||||
                      letterSpacing: -0.5,
 | 
					                      letterSpacing: -0.5,
 | 
				
			||||||
                    ),
 | 
					                    ),
 | 
				
			||||||
                  ),
 | 
					                  ),
 | 
				
			||||||
 | 
					            ),
 | 
				
			||||||
            const Spacer(),
 | 
					            const Spacer(),
 | 
				
			||||||
            IconButton(
 | 
					            IconButton(
 | 
				
			||||||
              icon: const Icon(Symbols.person_add),
 | 
					              icon: const Icon(Symbols.person_add),
 | 
				
			||||||
@@ -668,9 +624,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);
 | 
				
			||||||
              },
 | 
					              },
 | 
				
			||||||
            ),
 | 
					            ),
 | 
				
			||||||
@@ -744,9 +698,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);
 | 
				
			||||||
                              }
 | 
					                              }
 | 
				
			||||||
                            });
 | 
					                            });
 | 
				
			||||||
@@ -766,9 +718,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);
 | 
				
			||||||
@@ -801,34 +751,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();
 | 
				
			||||||
 | 
					        if (context.canPop()) {
 | 
				
			||||||
          context.pop();
 | 
					          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(
 | 
				
			||||||
                    const Divider(height: 1),
 | 
					                      children: [
 | 
				
			||||||
                    buildInfoRow(
 | 
					                        Expanded(
 | 
				
			||||||
                      Icons.storage,
 | 
					                          child: Column(
 | 
				
			||||||
                      'Size',
 | 
					                            crossAxisAlignment: CrossAxisAlignment.center,
 | 
				
			||||||
                      formatFileSize(item.size),
 | 
					                            mainAxisSize: MainAxisSize.min,
 | 
				
			||||||
                    ),
 | 
					                            children: [
 | 
				
			||||||
                    const Divider(height: 1),
 | 
					                              Text('mimeType').tr(),
 | 
				
			||||||
                    buildInfoRow(
 | 
					 | 
				
			||||||
                      Icons.category,
 | 
					 | 
				
			||||||
                      'Type',
 | 
					 | 
				
			||||||
                      item.mimeType?.toUpperCase() ?? 'UNKNOWN',
 | 
					 | 
				
			||||||
                    ),
 | 
					 | 
				
			||||||
                    if (exifData.isNotEmpty) ...[
 | 
					 | 
				
			||||||
                      const SizedBox(height: 16),
 | 
					 | 
				
			||||||
                              Text(
 | 
					                              Text(
 | 
				
			||||||
                        'EXIF Data',
 | 
					                                item.mimeType ?? 'unknown'.tr(),
 | 
				
			||||||
 | 
					                                maxLines: 1,
 | 
				
			||||||
 | 
					                                overflow: TextOverflow.ellipsis,
 | 
				
			||||||
                                style: theme.textTheme.titleMedium?.copyWith(
 | 
					                                style: theme.textTheme.titleMedium?.copyWith(
 | 
				
			||||||
                                  fontWeight: FontWeight.bold,
 | 
					                                  fontWeight: FontWeight.bold,
 | 
				
			||||||
                                ),
 | 
					                                ),
 | 
				
			||||||
                      ).padding(horizontal: 24),
 | 
					                              ),
 | 
				
			||||||
                      const SizedBox(height: 8),
 | 
					                            ],
 | 
				
			||||||
 | 
					                          ),
 | 
				
			||||||
 | 
					                        ),
 | 
				
			||||||
 | 
					                        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),
 | 
				
			||||||
 | 
					                    ListTile(
 | 
				
			||||||
 | 
					                      leading: const Icon(Icons.file_present),
 | 
				
			||||||
 | 
					                      title: Text('Name').tr(),
 | 
				
			||||||
 | 
					                      subtitle: Text(
 | 
				
			||||||
 | 
					                        item.name,
 | 
				
			||||||
 | 
					                        maxLines: 1,
 | 
				
			||||||
 | 
					                        overflow: TextOverflow.ellipsis,
 | 
				
			||||||
 | 
					                      ),
 | 
				
			||||||
 | 
					                      contentPadding: EdgeInsets.symmetric(horizontal: 24),
 | 
				
			||||||
 | 
					                      trailing: IconButton(
 | 
				
			||||||
 | 
					                        icon: const Icon(Icons.copy),
 | 
				
			||||||
 | 
					                        onPressed: () {
 | 
				
			||||||
 | 
					                          Clipboard.setData(ClipboardData(text: item.name));
 | 
				
			||||||
 | 
					                          showSnackBar('File name copied to clipboard');
 | 
				
			||||||
 | 
					                        },
 | 
				
			||||||
 | 
					                      ),
 | 
				
			||||||
 | 
					                    ),
 | 
				
			||||||
 | 
					                    if (exifData.isNotEmpty) ...[
 | 
				
			||||||
 | 
					                      const Divider(height: 1),
 | 
				
			||||||
 | 
					                      Theme(
 | 
				
			||||||
 | 
					                        data: theme.copyWith(dividerColor: Colors.transparent),
 | 
				
			||||||
 | 
					                        child: ExpansionTile(
 | 
				
			||||||
 | 
					                          tilePadding: const EdgeInsets.symmetric(
 | 
				
			||||||
 | 
					                            horizontal: 24,
 | 
				
			||||||
 | 
					                          ),
 | 
				
			||||||
 | 
					                          title: Text(
 | 
				
			||||||
 | 
					                            'exifData'.tr(),
 | 
				
			||||||
 | 
					                            style: theme.textTheme.titleMedium?.copyWith(
 | 
				
			||||||
 | 
					                              fontWeight: FontWeight.bold,
 | 
				
			||||||
 | 
					                            ),
 | 
				
			||||||
 | 
					                          ),
 | 
				
			||||||
 | 
					                          children: [
 | 
				
			||||||
                            Column(
 | 
					                            Column(
 | 
				
			||||||
                              crossAxisAlignment: CrossAxisAlignment.start,
 | 
					                              crossAxisAlignment: CrossAxisAlignment.start,
 | 
				
			||||||
                              children: [
 | 
					                              children: [
 | 
				
			||||||
                                ...exifData.entries.map(
 | 
					                                ...exifData.entries.map(
 | 
				
			||||||
                            (entry) => Padding(
 | 
					                                  (entry) => ListTile(
 | 
				
			||||||
                              padding: const EdgeInsets.symmetric(vertical: 4),
 | 
					                                    dense: true,
 | 
				
			||||||
                              child: Row(
 | 
					                                    contentPadding: EdgeInsets.symmetric(
 | 
				
			||||||
                                crossAxisAlignment: CrossAxisAlignment.start,
 | 
					                                      horizontal: 24,
 | 
				
			||||||
                                children: [
 | 
					                                    ),
 | 
				
			||||||
 | 
					                                    title:
 | 
				
			||||||
                                        Text(
 | 
					                                        Text(
 | 
				
			||||||
                                    '• ${entry.key.contains('-') ? entry.key.split('-').last : entry.key}: ',
 | 
					                                          entry.key.contains('-')
 | 
				
			||||||
                                    style: theme.textTheme.bodyMedium?.copyWith(
 | 
					                                              ? entry.key.split('-').last
 | 
				
			||||||
 | 
					                                              : entry.key,
 | 
				
			||||||
 | 
					                                          style: theme.textTheme.bodyMedium
 | 
				
			||||||
 | 
					                                              ?.copyWith(
 | 
				
			||||||
                                                fontWeight: FontWeight.w500,
 | 
					                                                fontWeight: FontWeight.w500,
 | 
				
			||||||
                                              ),
 | 
					                                              ),
 | 
				
			||||||
                                  ),
 | 
					                                        ).bold(),
 | 
				
			||||||
                                  Expanded(
 | 
					                                    subtitle: Text(
 | 
				
			||||||
                                    child: 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: [
 | 
				
			||||||
 | 
					                            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');
 | 
				
			||||||
 | 
					                                    },
 | 
				
			||||||
 | 
					                                  ),
 | 
				
			||||||
                                ),
 | 
					                                ),
 | 
				
			||||||
                              ],
 | 
					                              ],
 | 
				
			||||||
                      ).padding(horizontal: 24),
 | 
					                            ),
 | 
				
			||||||
 | 
					                          ],
 | 
				
			||||||
 | 
					                        ),
 | 
				
			||||||
 | 
					                      ),
 | 
				
			||||||
 | 
					                    ],
 | 
				
			||||||
 | 
					                    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(),
 | 
				
			||||||
 | 
					            ),
 | 
				
			||||||
 | 
					          ),
 | 
				
			||||||
 | 
					        ],
 | 
				
			||||||
 | 
					      ),
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										52
									
								
								pubspec.lock
									
									
									
									
									
								
							
							
						
						
									
										52
									
								
								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:
 | 
				
			||||||
@@ -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:
 | 
				
			||||||
@@ -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:
 | 
				
			||||||
@@ -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:
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										11
									
								
								pubspec.yaml
									
									
									
									
									
								
							
							
						
						
									
										11
									
								
								pubspec.yaml
									
									
									
									
									
								
							@@ -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