Compare commits

...

19 Commits

Author SHA1 Message Date
88396647f3 🚀 Launch 2.4.2+78 Feature Drop 2025-03-09 14:04:18 +08:00
335318ae3f Status system 2025-03-09 14:00:35 +08:00
da25fb9c29 🐛 Fix user cache 2025-03-09 13:03:57 +08:00
c1aef89b84 ♻️ Refactor account badge showing 2025-03-09 12:57:53 +08:00
0241c5f804 Check in streak 2025-03-09 12:41:34 +08:00
f6939d7c23 💄 Adjust icon size 2025-03-09 01:31:31 +08:00
d654c162e3 Shuffle post 2025-03-09 00:49:13 +08:00
25550ba197 💄 Changes to the showing of realm post 2025-03-09 00:11:01 +08:00
3defd3a593 💄 Modify explore appbar 2025-03-08 23:51:22 +08:00
d62ed4c375 Adjust explore categorized mode 2025-03-08 22:40:17 +08:00
857f3cc832 Post drafts 2025-03-08 22:32:38 +08:00
e16bc80eea 🐛 Fix attachments 2025-03-08 19:19:06 +08:00
a4f6e8af56 ♻️ New post explore realm design 2025-03-08 18:43:58 +08:00
060a97f5ec ♻️ Refactored explore screen 2025-03-08 18:19:57 +08:00
92f7e92018 🐛 Bug fixes due to post editor changes 2025-03-08 16:04:51 +08:00
5c483bd3b8 ♻️ Move the post editor mode into editor itself 2025-03-08 16:00:10 +08:00
1c510d63fe 🐛 Fix share via image errored 2025-03-06 22:46:02 +08:00
115cb4adc1 💄 Redesigned attachment zoom view 2025-03-06 22:35:06 +08:00
54c098c274 🍱 Update assets
 Optimize loading of web version in some regions
2025-03-05 22:23:42 +08:00
45 changed files with 2425 additions and 1114 deletions

BIN
assets/fonts/Nunito-Bold.ttf Executable file

Binary file not shown.

BIN
assets/fonts/Nunito-Italic.ttf Executable file

Binary file not shown.

BIN
assets/fonts/Nunito-Regular.ttf Executable file

Binary file not shown.

View File

@ -153,6 +153,11 @@
"publisherRunBy": "Run by {}", "publisherRunBy": "Run by {}",
"fieldPublisherBelongToRealm": "Belongs to", "fieldPublisherBelongToRealm": "Belongs to",
"fieldPublisherBelongToRealmUnset": "Unset Publisher Belongs to Realm", "fieldPublisherBelongToRealmUnset": "Unset Publisher Belongs to Realm",
"writePost": "Compose",
"postTypeStory": "Story",
"postTypeArticle": "Article",
"postTypeQuestion": "Question",
"postTypeVideo": "Video",
"writePostTypeStory": "Post a story", "writePostTypeStory": "Post a story",
"writePostTypeArticle": "Write an article", "writePostTypeArticle": "Write an article",
"writePostTypeQuestion": "Ask a question", "writePostTypeQuestion": "Ask a question",
@ -763,5 +768,28 @@
"decrypting": "Decrypting……", "decrypting": "Decrypting……",
"decryptingKeyNotFound": "Key not found or exchange failed, the other party may not be online", "decryptingKeyNotFound": "Key not found or exchange failed, the other party may not be online",
"messageUnablePreview": "Unable preview", "messageUnablePreview": "Unable preview",
"messageUnablePreviewEncrypted": "Unable preview encrypted message" "messageUnablePreviewEncrypted": "Unable preview encrypted message",
"postViewInGlobalDescription": "Do not view the post in the specific realm.",
"postDraftSaved": "The draft has been saved.",
"postDraftBox": "Draft Box",
"postShuffle": "Read Randomly",
"checkInStreak": {
"zero": "No streak",
"one": "{} day streak",
"other": "{} days streak"
},
"accountChangeStatus": "Change Status",
"accountStatusSilent": "Do not Disturb",
"accountStatusSilentDesc": "The notification will stop popping up",
"accountStatusInvisible": "Invisible",
"accountStatusInvisibleDesc": "Will show as offline, but all features still remain normal",
"accountCustomStatus": "Custom Status",
"accountCustomStatusDescription": "Customize your status.",
"accountClearStatus": "Clear Status",
"accountClearStatusDescription": "Clear your status, and let server decide which status you are for you.",
"fieldAccountStatusLabel": "Status Text",
"fieldAccountStatusClearAt": "Clear At",
"accountStatusNegative": "Negative",
"accountStatusNeutral": "Neutral",
"accountStatusPositive": "Positive"
} }

View File

@ -137,6 +137,11 @@
"publisherRunBy": "由 {} 管理", "publisherRunBy": "由 {} 管理",
"fieldPublisherBelongToRealm": "所属领域", "fieldPublisherBelongToRealm": "所属领域",
"fieldPublisherBelongToRealmUnset": "未设置发布者所属领域", "fieldPublisherBelongToRealmUnset": "未设置发布者所属领域",
"writePost": "撰写",
"postTypeStory": "动态",
"postTypeArticle": "文章",
"postTypeQuestion": "问题",
"postTypeVideo": "视频",
"writePostTypeStory": "发动态", "writePostTypeStory": "发动态",
"writePostTypeArticle": "写文章", "writePostTypeArticle": "写文章",
"writePostTypeQuestion": "提问题", "writePostTypeQuestion": "提问题",
@ -761,5 +766,28 @@
"decrypting": "解密中……", "decrypting": "解密中……",
"decryptingKeyNotFound": "未找到密钥对或交换失败,对方可能不在线", "decryptingKeyNotFound": "未找到密钥对或交换失败,对方可能不在线",
"messageUnablePreview": "无法预览消息", "messageUnablePreview": "无法预览消息",
"messageUnablePreviewEncrypted": "无法预览加密消息" "messageUnablePreviewEncrypted": "无法预览加密消息",
"postViewInGlobalDescription": "不查看特定领域的帖子。",
"postDraftSaved": "已保存为草稿。",
"postDraftBox": "草稿箱",
"postShuffle": "随便看看",
"checkInStreak": {
"zero": "无连击",
"one": "连续签到 {} 天",
"other": "连续签到 {} 天"
},
"accountChangeStatus": "修改状态",
"accountStatusSilent": "请勿打扰",
"accountStatusSilentDesc": "将会暂停所有通知推送",
"accountStatusInvisible": "隐身",
"accountStatusInvisibleDesc": "将会在他人界面显示离线,但不影响功能使用",
"accountCustomStatus": "自定义状态",
"accountCustomStatusDescription": "客制化你的状态。",
"accountClearStatus": "清除状态",
"accountClearStatusDescription": "清除你的状态,并让服务器决定你的状态。",
"fieldAccountStatusLabel": "状态文字",
"fieldAccountStatusClearAt": "清除时间",
"accountStatusNegative": "负面",
"accountStatusNeutral": "中性",
"accountStatusPositive": "正面"
} }

View File

@ -137,6 +137,11 @@
"publisherRunBy": "由 {} 管理", "publisherRunBy": "由 {} 管理",
"fieldPublisherBelongToRealm": "所屬領域", "fieldPublisherBelongToRealm": "所屬領域",
"fieldPublisherBelongToRealmUnset": "未設置發佈者所屬領域", "fieldPublisherBelongToRealmUnset": "未設置發佈者所屬領域",
"writePost": "撰寫",
"postTypeStory": "動態",
"postTypeArticle": "文章",
"postTypeQuestion": "問題",
"postTypeVideo": "視頻",
"writePostTypeStory": "發動態", "writePostTypeStory": "發動態",
"writePostTypeArticle": "寫文章", "writePostTypeArticle": "寫文章",
"writePostTypeQuestion": "提問題", "writePostTypeQuestion": "提問題",
@ -761,5 +766,28 @@
"decrypting": "解密中……", "decrypting": "解密中……",
"decryptingKeyNotFound": "未找到密鑰對或交換失敗,對方可能不在線", "decryptingKeyNotFound": "未找到密鑰對或交換失敗,對方可能不在線",
"messageUnablePreview": "無法預覽消息", "messageUnablePreview": "無法預覽消息",
"messageUnablePreviewEncrypted": "無法預覽加密消息" "messageUnablePreviewEncrypted": "無法預覽加密消息",
"postViewInGlobalDescription": "不查看特定領域的帖子。",
"postDraftSaved": "已保存為草稿。",
"postDraftBox": "草稿箱",
"postShuffle": "隨便看看",
"checkInStreak": {
"zero": "無連擊",
"one": "連續簽到 {} 天",
"other": "連續簽到 {} 天"
},
"accountChangeStatus": "修改狀態",
"accountStatusSilent": "請勿打擾",
"accountStatusSilentDesc": "將會暫停所有通知推送",
"accountStatusInvisible": "隱身",
"accountStatusInvisibleDesc": "將會在他人界面顯示離線,但不影響功能使用",
"accountCustomStatus": "自定義狀態",
"accountCustomStatusDescription": "客製化你的狀態。",
"accountClearStatus": "清除狀態",
"accountClearStatusDescription": "清除你的狀態,並讓服務器決定你的狀態。",
"fieldAccountStatusLabel": "狀態文字",
"fieldAccountStatusClearAt": "清除時間",
"accountStatusNegative": "負面",
"accountStatusNeutral": "中性",
"accountStatusPositive": "正面"
} }

View File

@ -137,6 +137,11 @@
"publisherRunBy": "由 {} 管理", "publisherRunBy": "由 {} 管理",
"fieldPublisherBelongToRealm": "所屬領域", "fieldPublisherBelongToRealm": "所屬領域",
"fieldPublisherBelongToRealmUnset": "未設置發佈者所屬領域", "fieldPublisherBelongToRealmUnset": "未設置發佈者所屬領域",
"writePost": "撰寫",
"postTypeStory": "動態",
"postTypeArticle": "文章",
"postTypeQuestion": "問題",
"postTypeVideo": "視頻",
"writePostTypeStory": "發動態", "writePostTypeStory": "發動態",
"writePostTypeArticle": "寫文章", "writePostTypeArticle": "寫文章",
"writePostTypeQuestion": "提問題", "writePostTypeQuestion": "提問題",
@ -761,5 +766,28 @@
"decrypting": "解密中……", "decrypting": "解密中……",
"decryptingKeyNotFound": "未找到密鑰對或交換失敗,對方可能不在線", "decryptingKeyNotFound": "未找到密鑰對或交換失敗,對方可能不在線",
"messageUnablePreview": "無法預覽消息", "messageUnablePreview": "無法預覽消息",
"messageUnablePreviewEncrypted": "無法預覽加密消息" "messageUnablePreviewEncrypted": "無法預覽加密消息",
"postViewInGlobalDescription": "不查看特定領域的帖子。",
"postDraftSaved": "已保存為草稿。",
"postDraftBox": "草稿箱",
"postShuffle": "隨便看看",
"checkInStreak": {
"zero": "無連擊",
"one": "連續簽到 {} 天",
"other": "連續簽到 {} 天"
},
"accountChangeStatus": "修改狀態",
"accountStatusSilent": "請勿打擾",
"accountStatusSilentDesc": "將會暫停所有通知推送",
"accountStatusInvisible": "隱身",
"accountStatusInvisibleDesc": "將會在他人界面顯示離線,但不影響功能使用",
"accountCustomStatus": "自定義狀態",
"accountCustomStatusDescription": "客製化你的狀態。",
"accountClearStatus": "清除狀態",
"accountClearStatusDescription": "清除你的狀態,並讓服務器決定你的狀態。",
"fieldAccountStatusLabel": "狀態文字",
"fieldAccountStatusClearAt": "清除時間",
"accountStatusNegative": "負面",
"accountStatusNeutral": "中性",
"accountStatusPositive": "正面"
} }

View File

@ -71,7 +71,8 @@ class PostWriteMedia {
} }
} }
PostWriteMedia.fromBytes(this.raw, this.name, this.type, {this.attachment, this.file}); PostWriteMedia.fromBytes(this.raw, this.name, this.type,
{this.attachment, this.file});
bool get isEmpty => attachment == null && file == null && raw == null; bool get isEmpty => attachment == null && file == null && raw == null;
@ -105,7 +106,8 @@ class PostWriteMedia {
}) { }) {
if (attachment != null) { if (attachment != null) {
final sn = context.read<SnNetworkProvider>(); final sn = context.read<SnNetworkProvider>();
final ImageProvider provider = UniversalImage.provider(sn.getAttachmentUrl(attachment!.rid)); final ImageProvider provider =
UniversalImage.provider(sn.getAttachmentUrl(attachment!.rid));
if (width != null && height != null && !kIsWeb) { if (width != null && height != null && !kIsWeb) {
return ResizeImage( return ResizeImage(
provider, provider,
@ -116,7 +118,8 @@ class PostWriteMedia {
} }
return provider; return provider;
} else if (file != null) { } else if (file != null) {
final ImageProvider provider = kIsWeb ? NetworkImage(file!.path) : FileImage(File(file!.path)); final ImageProvider provider =
kIsWeb ? NetworkImage(file!.path) : FileImage(File(file!.path));
if (width != null && height != null) { if (width != null && height != null) {
return ResizeImage( return ResizeImage(
provider, provider,
@ -159,11 +162,14 @@ class PostWriteController extends ChangeNotifier {
final TextEditingController aliasController = TextEditingController(); final TextEditingController aliasController = TextEditingController();
final TextEditingController rewardController = TextEditingController(); final TextEditingController rewardController = TextEditingController();
ContentInsertionConfiguration get contentInsertionConfiguration => ContentInsertionConfiguration( ContentInsertionConfiguration get contentInsertionConfiguration =>
ContentInsertionConfiguration(
onContentInserted: (KeyboardInsertedContent content) { onContentInserted: (KeyboardInsertedContent content) {
if (content.hasData) { if (content.hasData) {
addAttachments( addAttachments([
[PostWriteMedia.fromBytes(content.data!, 'attachmentInsertedImage'.tr(), SnMediaType.image)]); PostWriteMedia.fromBytes(content.data!,
'attachmentInsertedImage'.tr(), SnMediaType.image)
]);
} }
}, },
); );
@ -193,7 +199,8 @@ class PostWriteController extends ChangeNotifier {
String get description => descriptionController.text; String get description => descriptionController.text;
bool get isRelatedNull => ![editingPost, repostingPost, replyingPost].any((ele) => ele != null); bool get isRelatedNull =>
![editingPost, repostingPost, replyingPost].any((ele) => ele != null);
bool isLoading = false, isBusy = false; bool isLoading = false, isBusy = false;
double? progress; double? progress;
@ -201,6 +208,7 @@ class PostWriteController extends ChangeNotifier {
SnRealm? realm; SnRealm? realm;
SnPublisher? publisher; SnPublisher? publisher;
SnPost? editingPost, repostingPost, replyingPost; SnPost? editingPost, repostingPost, replyingPost;
bool editingDraft = false;
int visibility = 0; int visibility = 0;
List<int> visibleUsers = List.empty(); List<int> visibleUsers = List.empty();
@ -237,14 +245,20 @@ class PostWriteController extends ChangeNotifier {
publishedAt = post.publishedAt; publishedAt = post.publishedAt;
publishedUntil = post.publishedUntil; publishedUntil = post.publishedUntil;
visibleUsers = List.from(post.visibleUsersList ?? [], growable: true); visibleUsers = List.from(post.visibleUsersList ?? [], growable: true);
invisibleUsers = List.from(post.invisibleUsersList ?? [], growable: true); invisibleUsers =
List.from(post.invisibleUsersList ?? [], growable: true);
visibility = post.visibility; visibility = post.visibility;
tags = List.from(post.tags.map((ele) => ele.alias), growable: true); tags = List.from(post.tags.map((ele) => ele.alias), growable: true);
categories = List.from(post.categories.map((ele) => ele.alias), growable: true); categories =
attachments.addAll(post.preload?.attachments?.map((ele) => PostWriteMedia(ele)) ?? []); List.from(post.categories.map((ele) => ele.alias), growable: true);
attachments.addAll(
post.preload?.attachments?.map((ele) => PostWriteMedia(ele)) ?? []);
poll = post.preload?.poll; poll = post.preload?.poll;
if (post.preload?.thumbnail != null && (post.preload?.thumbnail?.rid.isNotEmpty ?? false)) { editingDraft = post.isDraft;
if (post.preload?.thumbnail != null &&
(post.preload?.thumbnail?.rid.isNotEmpty ?? false)) {
thumbnail = PostWriteMedia(post.preload!.thumbnail); thumbnail = PostWriteMedia(post.preload!.thumbnail);
} }
if (post.preload?.realm != null) { if (post.preload?.realm != null) {
@ -272,7 +286,8 @@ class PostWriteController extends ChangeNotifier {
} }
} }
Future<SnAttachment> _uploadAttachment(BuildContext context, PostWriteMedia media, Future<SnAttachment> _uploadAttachment(
BuildContext context, PostWriteMedia media,
{bool isCompressed = false}) async { {bool isCompressed = false}) async {
final attach = context.read<SnAttachmentProvider>(); final attach = context.read<SnAttachmentProvider>();
@ -281,7 +296,9 @@ class PostWriteController extends ChangeNotifier {
media.name, media.name,
'interactive', 'interactive',
null, null,
mimetype: media.raw != null && media.type == SnMediaType.image ? 'image/png' : null, mimetype: media.raw != null && media.type == SnMediaType.image
? 'image/png'
: null,
); );
var item = await attach.chunkedUploadParts( var item = await attach.chunkedUploadParts(
@ -297,9 +314,11 @@ class PostWriteController extends ChangeNotifier {
if (media.type == SnMediaType.video && !isCompressed && context.mounted) { if (media.type == SnMediaType.video && !isCompressed && context.mounted) {
try { try {
final compressedAttachment = await _tryCompressVideoCopy(context, media); final compressedAttachment =
await _tryCompressVideoCopy(context, media);
if (compressedAttachment != null) { if (compressedAttachment != null) {
item = await attach.updateOne(item, compressedId: compressedAttachment.id); item = await attach.updateOne(item,
compressedId: compressedAttachment.id);
} }
} catch (err) { } catch (err) {
if (context.mounted) context.showErrorDialog(err); if (context.mounted) context.showErrorDialog(err);
@ -309,8 +328,10 @@ class PostWriteController extends ChangeNotifier {
return item; return item;
} }
Future<SnAttachment?> _tryCompressVideoCopy(BuildContext context, PostWriteMedia media) async { Future<SnAttachment?> _tryCompressVideoCopy(
if (kIsWeb || !(Platform.isAndroid || Platform.isIOS || Platform.isMacOS)) return null; BuildContext context, PostWriteMedia media) async {
if (kIsWeb || !(Platform.isAndroid || Platform.isIOS || Platform.isMacOS))
return null;
if (media.type != SnMediaType.video) return null; if (media.type != SnMediaType.video) return null;
if (media.file == null) return null; if (media.file == null) return null;
if (VideoCompress.isCompressing) return null; if (VideoCompress.isCompressing) return null;
@ -334,7 +355,8 @@ class PostWriteController extends ChangeNotifier {
if (!context.mounted) return null; if (!context.mounted) return null;
final compressedMedia = PostWriteMedia.fromFile(XFile(mediaInfo.path!)); final compressedMedia = PostWriteMedia.fromFile(XFile(mediaInfo.path!));
final compressedAttachment = await _uploadAttachment(context, compressedMedia, isCompressed: true); final compressedAttachment =
await _uploadAttachment(context, compressedMedia, isCompressed: true);
return compressedAttachment; return compressedAttachment;
} }
@ -370,18 +392,25 @@ class PostWriteController extends ChangeNotifier {
'content': contentController.text, 'content': contentController.text,
if (aliasController.text.isNotEmpty) 'alias': aliasController.text, if (aliasController.text.isNotEmpty) 'alias': aliasController.text,
if (titleController.text.isNotEmpty) 'title': titleController.text, if (titleController.text.isNotEmpty) 'title': titleController.text,
if (descriptionController.text.isNotEmpty) 'description': descriptionController.text, if (descriptionController.text.isNotEmpty)
'description': descriptionController.text,
if (rewardController.text.isNotEmpty) 'reward': rewardController.text, if (rewardController.text.isNotEmpty) 'reward': rewardController.text,
if (thumbnail != null && thumbnail!.attachment != null) 'thumbnail': thumbnail!.attachment!.toJson(), if (thumbnail != null && thumbnail!.attachment != null)
'attachments': 'thumbnail': thumbnail!.attachment!.toJson(),
attachments.where((e) => e.attachment != null).map((e) => e.attachment!.toJson()).toList(growable: true), 'attachments': attachments
.where((e) => e.attachment != null)
.map((e) => e.attachment!.toJson())
.toList(growable: true),
'tags': tags.map((ele) => {'alias': ele}).toList(growable: true), 'tags': tags.map((ele) => {'alias': ele}).toList(growable: true),
'categories': categories.map((ele) => {'alias': ele}).toList(growable: true), 'categories':
categories.map((ele) => {'alias': ele}).toList(growable: true),
'visibility': visibility, 'visibility': visibility,
'visible_users_list': visibleUsers, 'visible_users_list': visibleUsers,
'invisible_users_list': invisibleUsers, 'invisible_users_list': invisibleUsers,
if (publishedAt != null) 'published_at': publishedAt!.toUtc().toIso8601String(), if (publishedAt != null)
if (publishedUntil != null) 'published_until': publishedAt!.toUtc().toIso8601String(), 'published_at': publishedAt!.toUtc().toIso8601String(),
if (publishedUntil != null)
'published_until': publishedAt!.toUtc().toIso8601String(),
if (replyingPost != null) 'reply_to': replyingPost!.toJson(), if (replyingPost != null) 'reply_to': replyingPost!.toJson(),
if (repostingPost != null) 'repost_to': repostingPost!.toJson(), if (repostingPost != null) 'repost_to': repostingPost!.toJson(),
if (poll != null) 'poll': poll!.toJson(), if (poll != null) 'poll': poll!.toJson(),
@ -391,6 +420,12 @@ class PostWriteController extends ChangeNotifier {
}); });
} }
bool get isNotEmpty =>
title.isNotEmpty ||
description.isNotEmpty ||
contentController.text.isNotEmpty ||
attachments.isNotEmpty;
bool temporaryRestored = false; bool temporaryRestored = false;
void _temporaryLoad() { void _temporaryLoad() {
@ -403,18 +438,24 @@ class PostWriteController extends ChangeNotifier {
titleController.text = data['title'] ?? ''; titleController.text = data['title'] ?? '';
descriptionController.text = data['description'] ?? ''; descriptionController.text = data['description'] ?? '';
rewardController.text = data['reward']?.toString() ?? ''; rewardController.text = data['reward']?.toString() ?? '';
if (data['thumbnail'] != null) thumbnail = PostWriteMedia(SnAttachment.fromJson(data['thumbnail'])); if (data['thumbnail'] != null)
attachments thumbnail = PostWriteMedia(SnAttachment.fromJson(data['thumbnail']));
.addAll(data['attachments'].map((ele) => PostWriteMedia(SnAttachment.fromJson(ele))).cast<PostWriteMedia>()); attachments.addAll(data['attachments']
.map((ele) => PostWriteMedia(SnAttachment.fromJson(ele)))
.cast<PostWriteMedia>());
tags = List.from(data['tags'].map((ele) => ele['alias'])); tags = List.from(data['tags'].map((ele) => ele['alias']));
categories = List.from(data['categories'].map((ele) => ele['alias'])); categories = List.from(data['categories'].map((ele) => ele['alias']));
visibility = data['visibility']; visibility = data['visibility'];
visibleUsers = List.from(data['visible_users_list'] ?? []); visibleUsers = List.from(data['visible_users_list'] ?? []);
invisibleUsers = List.from(data['invisible_users_list'] ?? []); invisibleUsers = List.from(data['invisible_users_list'] ?? []);
if (data['published_at'] != null) publishedAt = DateTime.tryParse(data['published_at'])?.toLocal(); if (data['published_at'] != null)
if (data['published_until'] != null) publishedUntil = DateTime.tryParse(data['published_until'])?.toLocal(); publishedAt = DateTime.tryParse(data['published_at'])?.toLocal();
replyingPost = data['reply_to'] != null ? SnPost.fromJson(data['reply_to']) : null; if (data['published_until'] != null)
repostingPost = data['repost_to'] != null ? SnPost.fromJson(data['repost_to']) : null; publishedUntil = DateTime.tryParse(data['published_until'])?.toLocal();
replyingPost =
data['reply_to'] != null ? SnPost.fromJson(data['reply_to']) : null;
repostingPost =
data['repost_to'] != null ? SnPost.fromJson(data['repost_to']) : null;
poll = data['poll'] != null ? SnPoll.fromJson(data['poll']) : null; poll = data['poll'] != null ? SnPoll.fromJson(data['poll']) : null;
realm = data['realm'] != null ? SnRealm.fromJson(data['realm']) : null; realm = data['realm'] != null ? SnRealm.fromJson(data['realm']) : null;
temporaryRestored = true; temporaryRestored = true;
@ -436,7 +477,10 @@ class PostWriteController extends ChangeNotifier {
notifyListeners(); notifyListeners();
} }
Future<void> sendPost(BuildContext context) async { Future<void> sendPost(
BuildContext context, {
bool saveAsDraft = false,
}) async {
if (isBusy || publisher == null) return; if (isBusy || publisher == null) return;
final sn = context.read<SnNetworkProvider>(); final sn = context.read<SnNetworkProvider>();
@ -463,7 +507,9 @@ class PostWriteController extends ChangeNotifier {
media.name, media.name,
'interactive', 'interactive',
null, null,
mimetype: media.raw != null && media.type == SnMediaType.image ? 'image/png' : null, mimetype: media.raw != null && media.type == SnMediaType.image
? 'image/png'
: null,
); );
var item = await attach.chunkedUploadParts( var item = await attach.chunkedUploadParts(
@ -472,16 +518,20 @@ class PostWriteController extends ChangeNotifier {
place.$2, place.$2,
onProgress: (value) { onProgress: (value) {
// Calculate overall progress for attachments // Calculate overall progress for attachments
progress = math.max(((i + value) / attachments.length) * kAttachmentProgressWeight, value); progress = math.max(
((i + value) / attachments.length) * kAttachmentProgressWeight,
value);
notifyListeners(); notifyListeners();
}, },
); );
try { try {
if (context.mounted) { if (context.mounted) {
final compressedAttachment = await _tryCompressVideoCopy(context, media); final compressedAttachment =
await _tryCompressVideoCopy(context, media);
if (compressedAttachment != null) { if (compressedAttachment != null) {
item = await attach.updateOne(item, compressedId: compressedAttachment.id); item = await attach.updateOne(item,
compressedId: compressedAttachment.id);
} }
} }
} catch (err) { } catch (err) {
@ -508,7 +558,7 @@ class PostWriteController extends ChangeNotifier {
// Posting the content // Posting the content
try { try {
final baseProgressVal = progress!; final baseProgressVal = progress!;
await sn.client.request( final resp = await sn.client.request(
[ [
'/cgi/co/$mode', '/cgi/co/$mode',
if (editingPost != null) '${editingPost!.id}', if (editingPost != null) '${editingPost!.id}',
@ -518,36 +568,56 @@ class PostWriteController extends ChangeNotifier {
'content': contentController.text, 'content': contentController.text,
if (aliasController.text.isNotEmpty) 'alias': aliasController.text, if (aliasController.text.isNotEmpty) 'alias': aliasController.text,
if (titleController.text.isNotEmpty) 'title': titleController.text, if (titleController.text.isNotEmpty) 'title': titleController.text,
if (descriptionController.text.isNotEmpty) 'description': descriptionController.text, if (descriptionController.text.isNotEmpty)
if (thumbnail != null && thumbnail!.attachment != null) 'thumbnail': thumbnail!.attachment!.rid, 'description': descriptionController.text,
'attachments': attachments.where((e) => e.attachment != null).map((e) => e.attachment!.rid).toList(), if (thumbnail != null && thumbnail!.attachment != null)
'thumbnail': thumbnail!.attachment!.rid,
'attachments': attachments
.where((e) => e.attachment != null)
.map((e) => e.attachment!.rid)
.toList(),
'tags': tags.map((ele) => {'alias': ele}).toList(), 'tags': tags.map((ele) => {'alias': ele}).toList(),
'categories': categories.map((ele) => {'alias': ele}).toList(), 'categories': categories.map((ele) => {'alias': ele}).toList(),
'visibility': visibility, 'visibility': visibility,
'visible_users_list': visibleUsers, 'visible_users_list': visibleUsers,
'invisible_users_list': invisibleUsers, 'invisible_users_list': invisibleUsers,
if (publishedAt != null) 'published_at': publishedAt!.toUtc().toIso8601String(), if (publishedAt != null)
if (publishedUntil != null) 'published_until': publishedAt!.toUtc().toIso8601String(), 'published_at': publishedAt!.toUtc().toIso8601String(),
if (publishedUntil != null)
'published_until': publishedAt!.toUtc().toIso8601String(),
if (replyingPost != null) 'reply_to': replyingPost!.id, if (replyingPost != null) 'reply_to': replyingPost!.id,
if (repostingPost != null) 'repost_to': repostingPost!.id, if (repostingPost != null) 'repost_to': repostingPost!.id,
if (reward != null) 'reward': reward, if (reward != null) 'reward': reward,
if (videoAttachment != null) 'video': videoAttachment!.rid, if (videoAttachment != null) 'video': videoAttachment!.rid,
if (poll != null) 'poll': poll!.id, if (poll != null) 'poll': poll!.id,
if (realm != null) 'realm': realm!.id, if (realm != null) 'realm': realm!.id,
'is_draft': saveAsDraft,
}, },
onSendProgress: (count, total) { onSendProgress: (count, total) {
progress = baseProgressVal + (count / total) * (kPostingProgressWeight / 2); progress =
baseProgressVal + (count / total) * (kPostingProgressWeight / 2);
notifyListeners(); notifyListeners();
}, },
onReceiveProgress: (count, total) { onReceiveProgress: (count, total) {
progress = baseProgressVal + (kPostingProgressWeight / 2) + (count / total) * (kPostingProgressWeight / 2); progress = baseProgressVal +
(kPostingProgressWeight / 2) +
(count / total) * (kPostingProgressWeight / 2);
notifyListeners(); notifyListeners();
}, },
options: Options( options: Options(
method: editingPost != null ? 'PUT' : 'POST', method: editingPost != null ? 'PUT' : 'POST',
), ),
); );
reset(); if (saveAsDraft) {
if (!context.mounted) return;
editingDraft = true;
final out = SnPost.fromJson(resp.data);
final pt = context.read<SnPostContentProvider>();
editingPost = await pt.completePostData(out);
notifyListeners();
} else {
reset();
}
} catch (err) { } catch (err) {
if (!context.mounted) return; if (!context.mounted) return;
context.showErrorDialog(err); context.showErrorDialog(err);
@ -683,7 +753,8 @@ class PostWriteController extends ChangeNotifier {
repostingPost = null; repostingPost = null;
mode = kTitleMap.keys.first; mode = kTitleMap.keys.first;
temporaryRestored = false; temporaryRestored = false;
SharedPreferences.getInstance().then((prefs) => prefs.remove(kTemporaryStorageKey)); SharedPreferences.getInstance()
.then((prefs) => prefs.remove(kTemporaryStorageKey));
notifyListeners(); notifyListeners();
} }

View File

@ -23,8 +23,6 @@ class $SnLocalChatChannelTable extends SnLocalChatChannel
late final GeneratedColumn<String> alias = GeneratedColumn<String>( late final GeneratedColumn<String> alias = GeneratedColumn<String>(
'alias', aliasedName, false, 'alias', aliasedName, false,
type: DriftSqlType.string, requiredDuringInsert: true); type: DriftSqlType.string, requiredDuringInsert: true);
static const VerificationMeta _contentMeta =
const VerificationMeta('content');
@override @override
late final GeneratedColumnWithTypeConverter<SnChannel, String> content = late final GeneratedColumnWithTypeConverter<SnChannel, String> content =
GeneratedColumn<String>('content', aliasedName, false, GeneratedColumn<String>('content', aliasedName, false,
@ -60,7 +58,6 @@ class $SnLocalChatChannelTable extends SnLocalChatChannel
} else if (isInserting) { } else if (isInserting) {
context.missing(_aliasMeta); context.missing(_aliasMeta);
} }
context.handle(_contentMeta, const VerificationResult.success());
if (data.containsKey('created_at')) { if (data.containsKey('created_at')) {
context.handle(_createdAtMeta, context.handle(_createdAtMeta,
createdAt.isAcceptableOrUnknown(data['created_at']!, _createdAtMeta)); createdAt.isAcceptableOrUnknown(data['created_at']!, _createdAtMeta));
@ -295,8 +292,6 @@ class $SnLocalChatMessageTable extends SnLocalChatMessage
late final GeneratedColumn<int> senderId = GeneratedColumn<int>( late final GeneratedColumn<int> senderId = GeneratedColumn<int>(
'sender_id', aliasedName, true, 'sender_id', aliasedName, true,
type: DriftSqlType.int, requiredDuringInsert: false); type: DriftSqlType.int, requiredDuringInsert: false);
static const VerificationMeta _contentMeta =
const VerificationMeta('content');
@override @override
late final GeneratedColumnWithTypeConverter<SnChatMessage, String> content = late final GeneratedColumnWithTypeConverter<SnChatMessage, String> content =
GeneratedColumn<String>('content', aliasedName, false, GeneratedColumn<String>('content', aliasedName, false,
@ -338,7 +333,6 @@ class $SnLocalChatMessageTable extends SnLocalChatMessage
context.handle(_senderIdMeta, context.handle(_senderIdMeta,
senderId.isAcceptableOrUnknown(data['sender_id']!, _senderIdMeta)); senderId.isAcceptableOrUnknown(data['sender_id']!, _senderIdMeta));
} }
context.handle(_contentMeta, const VerificationResult.success());
if (data.containsKey('created_at')) { if (data.containsKey('created_at')) {
context.handle(_createdAtMeta, context.handle(_createdAtMeta,
createdAt.isAcceptableOrUnknown(data['created_at']!, _createdAtMeta)); createdAt.isAcceptableOrUnknown(data['created_at']!, _createdAtMeta));
@ -604,8 +598,6 @@ class $SnLocalChannelMemberTable extends SnLocalChannelMember
late final GeneratedColumn<int> accountId = GeneratedColumn<int>( late final GeneratedColumn<int> accountId = GeneratedColumn<int>(
'account_id', aliasedName, false, 'account_id', aliasedName, false,
type: DriftSqlType.int, requiredDuringInsert: true); type: DriftSqlType.int, requiredDuringInsert: true);
static const VerificationMeta _contentMeta =
const VerificationMeta('content');
@override @override
late final GeneratedColumnWithTypeConverter<SnChannelMember, String> content = late final GeneratedColumnWithTypeConverter<SnChannelMember, String> content =
GeneratedColumn<String>('content', aliasedName, false, GeneratedColumn<String>('content', aliasedName, false,
@ -655,7 +647,6 @@ class $SnLocalChannelMemberTable extends SnLocalChannelMember
} else if (isInserting) { } else if (isInserting) {
context.missing(_accountIdMeta); context.missing(_accountIdMeta);
} }
context.handle(_contentMeta, const VerificationResult.success());
if (data.containsKey('created_at')) { if (data.containsKey('created_at')) {
context.handle(_createdAtMeta, context.handle(_createdAtMeta,
createdAt.isAcceptableOrUnknown(data['created_at']!, _createdAtMeta)); createdAt.isAcceptableOrUnknown(data['created_at']!, _createdAtMeta));
@ -1265,8 +1256,6 @@ class $SnLocalAccountTable extends SnLocalAccount
late final GeneratedColumn<String> name = GeneratedColumn<String>( late final GeneratedColumn<String> name = GeneratedColumn<String>(
'name', aliasedName, false, 'name', aliasedName, false,
type: DriftSqlType.string, requiredDuringInsert: true); type: DriftSqlType.string, requiredDuringInsert: true);
static const VerificationMeta _contentMeta =
const VerificationMeta('content');
@override @override
late final GeneratedColumnWithTypeConverter<SnAccount, String> content = late final GeneratedColumnWithTypeConverter<SnAccount, String> content =
GeneratedColumn<String>('content', aliasedName, false, GeneratedColumn<String>('content', aliasedName, false,
@ -1308,7 +1297,6 @@ class $SnLocalAccountTable extends SnLocalAccount
} else if (isInserting) { } else if (isInserting) {
context.missing(_nameMeta); context.missing(_nameMeta);
} }
context.handle(_contentMeta, const VerificationResult.success());
if (data.containsKey('created_at')) { if (data.containsKey('created_at')) {
context.handle(_createdAtMeta, context.handle(_createdAtMeta,
createdAt.isAcceptableOrUnknown(data['created_at']!, _createdAtMeta)); createdAt.isAcceptableOrUnknown(data['created_at']!, _createdAtMeta));
@ -1582,8 +1570,6 @@ class $SnLocalAttachmentTable extends SnLocalAttachment
type: DriftSqlType.string, type: DriftSqlType.string,
requiredDuringInsert: true, requiredDuringInsert: true,
defaultConstraints: GeneratedColumn.constraintIsAlways('UNIQUE')); defaultConstraints: GeneratedColumn.constraintIsAlways('UNIQUE'));
static const VerificationMeta _contentMeta =
const VerificationMeta('content');
@override @override
late final GeneratedColumnWithTypeConverter<SnAttachment, String> content = late final GeneratedColumnWithTypeConverter<SnAttachment, String> content =
GeneratedColumn<String>('content', aliasedName, false, GeneratedColumn<String>('content', aliasedName, false,
@ -1639,7 +1625,6 @@ class $SnLocalAttachmentTable extends SnLocalAttachment
} else if (isInserting) { } else if (isInserting) {
context.missing(_uuidMeta); context.missing(_uuidMeta);
} }
context.handle(_contentMeta, const VerificationResult.success());
if (data.containsKey('account_id')) { if (data.containsKey('account_id')) {
context.handle(_accountIdMeta, context.handle(_accountIdMeta,
accountId.isAcceptableOrUnknown(data['account_id']!, _accountIdMeta)); accountId.isAcceptableOrUnknown(data['account_id']!, _accountIdMeta));
@ -1968,8 +1953,6 @@ class $SnLocalStickerTable extends SnLocalSticker
late final GeneratedColumn<String> fullAlias = GeneratedColumn<String>( late final GeneratedColumn<String> fullAlias = GeneratedColumn<String>(
'full_alias', aliasedName, false, 'full_alias', aliasedName, false,
type: DriftSqlType.string, requiredDuringInsert: true); type: DriftSqlType.string, requiredDuringInsert: true);
static const VerificationMeta _contentMeta =
const VerificationMeta('content');
@override @override
late final GeneratedColumnWithTypeConverter<SnSticker, String> content = late final GeneratedColumnWithTypeConverter<SnSticker, String> content =
GeneratedColumn<String>('content', aliasedName, false, GeneratedColumn<String>('content', aliasedName, false,
@ -2011,7 +1994,6 @@ class $SnLocalStickerTable extends SnLocalSticker
} else if (isInserting) { } else if (isInserting) {
context.missing(_fullAliasMeta); context.missing(_fullAliasMeta);
} }
context.handle(_contentMeta, const VerificationResult.success());
if (data.containsKey('created_at')) { if (data.containsKey('created_at')) {
context.handle(_createdAtMeta, context.handle(_createdAtMeta,
createdAt.isAcceptableOrUnknown(data['created_at']!, _createdAtMeta)); createdAt.isAcceptableOrUnknown(data['created_at']!, _createdAtMeta));
@ -2261,8 +2243,6 @@ class $SnLocalStickerPackTable extends SnLocalStickerPack
requiredDuringInsert: false, requiredDuringInsert: false,
defaultConstraints: defaultConstraints:
GeneratedColumn.constraintIsAlways('PRIMARY KEY AUTOINCREMENT')); GeneratedColumn.constraintIsAlways('PRIMARY KEY AUTOINCREMENT'));
static const VerificationMeta _contentMeta =
const VerificationMeta('content');
@override @override
late final GeneratedColumnWithTypeConverter<SnStickerPack, String> content = late final GeneratedColumnWithTypeConverter<SnStickerPack, String> content =
GeneratedColumn<String>('content', aliasedName, false, GeneratedColumn<String>('content', aliasedName, false,
@ -2293,7 +2273,6 @@ class $SnLocalStickerPackTable extends SnLocalStickerPack
if (data.containsKey('id')) { if (data.containsKey('id')) {
context.handle(_idMeta, id.isAcceptableOrUnknown(data['id']!, _idMeta)); context.handle(_idMeta, id.isAcceptableOrUnknown(data['id']!, _idMeta));
} }
context.handle(_contentMeta, const VerificationResult.success());
if (data.containsKey('created_at')) { if (data.containsKey('created_at')) {
context.handle(_createdAtMeta, context.handle(_createdAtMeta,
createdAt.isAcceptableOrUnknown(data['created_at']!, _createdAtMeta)); createdAt.isAcceptableOrUnknown(data['created_at']!, _createdAtMeta));

View File

@ -60,16 +60,24 @@ class SnPostContentProvider {
out[i] = out[i].copyWith( out[i] = out[i].copyWith(
preload: SnPostPreload( preload: SnPostPreload(
thumbnail: attachments.where((ele) => ele?.rid == out[i].body['thumbnail']).firstOrNull, thumbnail: attachments
attachments: attachments.where((ele) => out[i].body['attachments']?.contains(ele?.rid) ?? false).toList(), .where((ele) => ele?.rid == out[i].body['thumbnail'])
video: attachments.where((ele) => ele?.rid == out[i].body['video']).firstOrNull, .firstOrNull,
attachments: attachments
.where((ele) =>
out[i].body['attachments']?.contains(ele?.rid) ?? false)
.toList(),
video: attachments
.where((ele) => ele?.rid == out[i].body['video'])
.firstOrNull,
poll: poll, poll: poll,
realm: realm, realm: realm,
), ),
); );
} }
uids.addAll(attachments.where((ele) => ele != null).map((ele) => ele!.accountId)); uids.addAll(
attachments.where((ele) => ele != null).map((ele) => ele!.accountId));
await _ud.listAccount(uids); await _ud.listAccount(uids);
return out; return out;
@ -107,15 +115,23 @@ class SnPostContentProvider {
out = out.copyWith( out = out.copyWith(
preload: SnPostPreload( preload: SnPostPreload(
thumbnail: attachments.where((ele) => ele?.rid == out.body['thumbnail']).firstOrNull, thumbnail: attachments
attachments: attachments.where((ele) => out.body['attachments']?.contains(ele?.rid) ?? false).toList(), .where((ele) => ele?.rid == out.body['thumbnail'])
video: attachments.where((ele) => ele?.rid == out.body['video']).firstOrNull, .firstOrNull,
attachments: attachments
.where(
(ele) => out.body['attachments']?.contains(ele?.rid) ?? false)
.toList(),
video: attachments
.where((ele) => ele?.rid == out.body['video'])
.firstOrNull,
poll: poll, poll: poll,
realm: realm, realm: realm,
), ),
); );
uids.addAll(attachments.where((ele) => ele != null).map((ele) => ele!.accountId)); uids.addAll(
attachments.where((ele) => ele != null).map((ele) => ele!.accountId));
await _ud.listAccount(uids); await _ud.listAccount(uids);
return out; return out;
@ -138,17 +154,25 @@ class SnPostContentProvider {
Iterable<String>? tags, Iterable<String>? tags,
String? realm, String? realm,
String? channel, String? channel,
bool isDraft = false,
bool isShuffle = false,
}) async { }) async {
final resp = await _sn.client.get('/cgi/co/posts', queryParameters: { final resp = await _sn.client.get(
'take': take, isShuffle
'offset': offset, ? '/cgi/co/recommendations/shuffle'
if (type != null) 'type': type, : '/cgi/co/posts${isDraft ? '/drafts' : ''}',
if (author != null) 'author': author, queryParameters: {
if (tags?.isNotEmpty ?? false) 'tags': tags!.join(','), 'take': take,
if (categories?.isNotEmpty ?? false) 'categories': categories!.join(','), 'offset': offset,
if (realm != null) 'realm': realm, if (type != null) 'type': type,
if (channel != null) 'channel': channel, if (author != null) 'author': author,
}); if (tags?.isNotEmpty ?? false) 'tags': tags!.join(','),
if (categories?.isNotEmpty ?? false)
'categories': categories!.join(','),
if (realm != null) 'realm': realm,
if (channel != null) 'channel': channel,
},
);
final List<SnPost> out = await _preloadRelatedDataInBatch( final List<SnPost> out = await _preloadRelatedDataInBatch(
List.from(resp.data['data']?.map((e) => SnPost.fromJson(e)) ?? []), List.from(resp.data['data']?.map((e) => SnPost.fromJson(e)) ?? []),
); );
@ -161,7 +185,8 @@ class SnPostContentProvider {
int take = 10, int take = 10,
int offset = 0, int offset = 0,
}) async { }) async {
final resp = await _sn.client.get('/cgi/co/posts/$parentId/replies', queryParameters: { final resp = await _sn.client
.get('/cgi/co/posts/$parentId/replies', queryParameters: {
'take': take, 'take': take,
'offset': offset, 'offset': offset,
}); });
@ -200,4 +225,9 @@ class SnPostContentProvider {
); );
return out; return out;
} }
Future<SnPost> completePostData(SnPost post) async {
final out = await _preloadRelatedDataSingle(post);
return out;
}
} }

View File

@ -19,6 +19,7 @@ class UserDirectoryProvider {
final Map<String, int> _idCache = {}; final Map<String, int> _idCache = {};
final Map<int, SnAccount> _cache = {}; final Map<int, SnAccount> _cache = {};
DateTime? _cacheExpiredAt;
Future<int> loadAccountCache({int max = 100}) async { Future<int> loadAccountCache({int max = 100}) async {
final out = await (_dt.db.snLocalAccount.select()..limit(max)).get(); final out = await (_dt.db.snLocalAccount.select()..limit(max)).get();
@ -26,11 +27,18 @@ class UserDirectoryProvider {
_cache[ele.id] = ele.content; _cache[ele.id] = ele.content;
_idCache[ele.name] = ele.id; _idCache[ele.name] = ele.id;
} }
_cacheExpiredAt = DateTime.now().add(const Duration(hours: 1));
return out.length; return out.length;
} }
Future<List<SnAccount?>> listAccount(Iterable<dynamic> id) async { Future<List<SnAccount?>> listAccount(Iterable<dynamic> id) async {
// In-memory cache // In-memory cache
if (_cacheExpiredAt != null && _cacheExpiredAt!.isAfter(DateTime.now())) {
_cache.clear();
_cacheExpiredAt = DateTime.now().add(const Duration(hours: 1));
} else {
_cacheExpiredAt ??= DateTime.now().add(const Duration(hours: 1));
}
final out = List<SnAccount?>.generate(id.length, (e) => null); final out = List<SnAccount?>.generate(id.length, (e) => null);
final plannedQuery = <int>{}; final plannedQuery = <int>{};
for (var idx = 0; idx < out.length; idx++) { for (var idx = 0; idx < out.length; idx++) {
@ -62,6 +70,7 @@ class UserDirectoryProvider {
plannedQuery.remove(dbResp[idx].id); plannedQuery.remove(dbResp[idx].id);
} }
// Remote server // Remote server
_saveToLocal(out.where((ele) => ele != null).cast());
if (plannedQuery.isEmpty) return out; if (plannedQuery.isEmpty) return out;
final resp = await _sn.client final resp = await _sn.client
.get('/cgi/id/users', queryParameters: {'id': plannedQuery.join(',')}); .get('/cgi/id/users', queryParameters: {'id': plannedQuery.join(',')});

View File

@ -28,7 +28,9 @@ import 'package:surface/screens/news/news_detail.dart';
import 'package:surface/screens/news/news_list.dart'; import 'package:surface/screens/news/news_list.dart';
import 'package:surface/screens/notification.dart'; import 'package:surface/screens/notification.dart';
import 'package:surface/screens/post/post_detail.dart'; import 'package:surface/screens/post/post_detail.dart';
import 'package:surface/screens/post/post_draft.dart';
import 'package:surface/screens/post/post_editor.dart'; import 'package:surface/screens/post/post_editor.dart';
import 'package:surface/screens/post/post_shuffle.dart';
import 'package:surface/screens/post/publisher_page.dart'; import 'package:surface/screens/post/publisher_page.dart';
import 'package:surface/screens/post/post_search.dart'; import 'package:surface/screens/post/post_search.dart';
import 'package:surface/screens/realm.dart'; import 'package:surface/screens/realm.dart';
@ -66,10 +68,15 @@ final _appRoutes = [
builder: (context, state) => const ExploreScreen(), builder: (context, state) => const ExploreScreen(),
routes: [ routes: [
GoRoute( GoRoute(
path: '/write/:mode', path: '/draft',
name: 'postDraftBox',
builder: (context, state) => const PostDraftBox(),
),
GoRoute(
path: '/write',
name: 'postEditor', name: 'postEditor',
builder: (context, state) => PostEditorScreen( builder: (context, state) => PostEditorScreen(
mode: state.pathParameters['mode']!, mode: state.uri.queryParameters['mode'],
postEditId: int.tryParse( postEditId: int.tryParse(
state.uri.queryParameters['editing'] ?? '', state.uri.queryParameters['editing'] ?? '',
), ),
@ -82,6 +89,11 @@ final _appRoutes = [
extraProps: state.extra as PostEditorExtra?, extraProps: state.extra as PostEditorExtra?,
), ),
), ),
GoRoute(
path: '/shuffle',
name: 'postShuffle',
builder: (context, state) => const PostShuffleScreen(),
),
GoRoute( GoRoute(
path: '/search', path: '/search',
name: 'postSearch', name: 'postSearch',

View File

@ -11,7 +11,9 @@ import 'package:surface/providers/database.dart';
import 'package:surface/providers/sn_network.dart'; import 'package:surface/providers/sn_network.dart';
import 'package:surface/providers/userinfo.dart'; import 'package:surface/providers/userinfo.dart';
import 'package:surface/providers/websocket.dart'; import 'package:surface/providers/websocket.dart';
import 'package:surface/types/account.dart';
import 'package:surface/widgets/account/account_image.dart'; import 'package:surface/widgets/account/account_image.dart';
import 'package:surface/widgets/account/account_status.dart';
import 'package:surface/widgets/app_bar_leading.dart'; import 'package:surface/widgets/app_bar_leading.dart';
import 'package:surface/widgets/dialog.dart'; import 'package:surface/widgets/dialog.dart';
import 'package:surface/widgets/navigation/app_scaffold.dart'; import 'package:surface/widgets/navigation/app_scaffold.dart';
@ -112,7 +114,14 @@ class _AuthorizedAccountScreen extends StatelessWidget {
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
AccountImage(content: ua.user!.avatar, radius: 28), Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
AccountImage(content: ua.user!.avatar, radius: 28),
_AccountStatusWidget(account: ua.user!),
],
),
const Gap(8), const Gap(8),
Row( Row(
crossAxisAlignment: CrossAxisAlignment.baseline, crossAxisAlignment: CrossAxisAlignment.baseline,
@ -290,3 +299,81 @@ class _UnauthorizedAccountScreen extends StatelessWidget {
); );
} }
} }
class _AccountStatusWidget extends StatefulWidget {
final SnAccount account;
const _AccountStatusWidget({required this.account});
@override
State<_AccountStatusWidget> createState() => _AccountStatusWidgetState();
}
class _AccountStatusWidgetState extends State<_AccountStatusWidget> {
SnAccountStatusInfo? _status;
Future<void> _fetchStatus() async {
try {
final sn = context.read<SnNetworkProvider>();
final resp =
await sn.client.get('/cgi/id/users/${widget.account.name}/status');
setState(() {
_status = SnAccountStatusInfo.fromJson(resp.data);
});
} catch (err) {
if (!mounted) return;
context.showErrorDialog(err);
} finally {
setState(() {});
}
}
@override
void initState() {
super.initState();
_fetchStatus();
}
@override
Widget build(BuildContext context) {
return InkWell(
child: Row(
children: [
Text(
_status != null
? (_status!.status?.label.isNotEmpty ?? false)
? _status!.status!.label
: _status!.isOnline
? 'accountStatusOnline'.tr()
: 'accountStatusOffline'.tr()
: 'loading'.tr(),
),
const Gap(4),
Icon(
(_status?.isDisturbable ?? true)
? Symbols.circle
: Symbols.do_not_disturb_on,
fill: (_status?.isOnline ?? false) ? 1 : 0,
size: 16,
color: (_status?.isOnline ?? false)
? (_status?.isDisturbable ?? true)
? Colors.green
: Colors.red
: Colors.grey,
).padding(all: 4),
],
),
onTap: () {
showModalBottomSheet(
context: context,
builder: (context) => AccountStatusActionPopup(
currentStatus: _status,
),
).then((value) {
if (value == true && mounted) {
_fetchStatus();
}
});
},
);
}
}

View File

@ -18,6 +18,7 @@ import 'package:surface/types/account.dart';
import 'package:surface/types/check_in.dart'; import 'package:surface/types/check_in.dart';
import 'package:surface/types/post.dart'; import 'package:surface/types/post.dart';
import 'package:surface/widgets/account/account_image.dart'; import 'package:surface/widgets/account/account_image.dart';
import 'package:surface/widgets/account/badge.dart';
import 'package:surface/widgets/dialog.dart'; import 'package:surface/widgets/dialog.dart';
import 'package:surface/widgets/universal_image.dart'; import 'package:surface/widgets/universal_image.dart';
import 'package:surface/theme.dart'; import 'package:surface/theme.dart';
@ -450,19 +451,25 @@ class _UserScreenState extends State<UserScreen>
child: Row( child: Row(
children: [ children: [
Icon( Icon(
Symbols.circle, (_status?.isDisturbable ?? true)
fill: 1, ? Symbols.circle
: Symbols.do_not_disturb_on,
fill: (_status?.isOnline ?? false) ? 1 : 0,
size: 16, size: 16,
color: (_status?.isOnline ?? false) color: (_status?.isOnline ?? false)
? Colors.green ? (_status?.isDisturbable ?? true)
? Colors.green
: Colors.red
: Colors.grey, : Colors.grey,
).padding(all: 4), ).padding(all: 4),
const Gap(8), const Gap(8),
Text( Text(
_status != null _status != null
? _status!.isOnline ? (_status!.status?.label.isNotEmpty ?? false)
? 'accountStatusOnline'.tr() ? _status!.status!.label
: 'accountStatusOffline'.tr() : _status!.isOnline
? 'accountStatusOnline'.tr()
: 'accountStatusOffline'.tr()
: 'loading'.tr(), : 'loading'.tr(),
), ),
if (_status != null && if (_status != null &&
@ -484,34 +491,7 @@ class _UserScreenState extends State<UserScreen>
Wrap( Wrap(
children: _account!.badges children: _account!.badges
.map( .map(
(ele) => Tooltip( (ele) => AccountBadge(badge: ele),
richMessage: TextSpan(
children: [
TextSpan(
text: kBadgesMeta[ele.type]?.$1.tr() ??
'unknown'.tr(),
),
if (ele.metadata['title'] != null)
TextSpan(
text: '\n${ele.metadata['title']}',
style: const TextStyle(
fontWeight: FontWeight.bold),
),
TextSpan(text: '\n'),
TextSpan(
text: DateFormat.yMEd().format(ele.createdAt),
),
],
),
child: Icon(
kBadgesMeta[ele.type]?.$2 ??
Symbols.question_mark,
color: ele.metadata['color'] != null
? HexColor.fromHex(ele.metadata['color']!)
: kBadgesMeta[ele.type]?.$3,
fill: 1,
),
),
) )
.toList(), .toList(),
).padding(horizontal: 8), ).padding(horizontal: 8),

View File

@ -204,7 +204,6 @@ class _ChatScreenState extends State<ChatScreen> {
Theme.of(context).floatingActionButtonTheme.foregroundColor, Theme.of(context).floatingActionButtonTheme.foregroundColor,
backgroundColor: backgroundColor:
Theme.of(context).floatingActionButtonTheme.backgroundColor, Theme.of(context).floatingActionButtonTheme.backgroundColor,
shape: const CircleBorder(),
), ),
closeButtonBuilder: DefaultFloatingActionButtonBuilder( closeButtonBuilder: DefaultFloatingActionButtonBuilder(
child: const Icon(Symbols.close, size: 28), child: const Icon(Symbols.close, size: 28),
@ -213,7 +212,6 @@ class _ChatScreenState extends State<ChatScreen> {
Theme.of(context).floatingActionButtonTheme.foregroundColor, Theme.of(context).floatingActionButtonTheme.foregroundColor,
backgroundColor: backgroundColor:
Theme.of(context).floatingActionButtonTheme.backgroundColor, Theme.of(context).floatingActionButtonTheme.backgroundColor,
shape: const CircleBorder(),
), ),
children: [ children: [
Row( Row(

View File

@ -1,4 +1,3 @@
import 'package:dropdown_button2/dropdown_button2.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_expandable_fab/flutter_expandable_fab.dart'; import 'package:flutter_expandable_fab/flutter_expandable_fab.dart';
@ -19,6 +18,9 @@ import 'package:surface/widgets/navigation/app_scaffold.dart';
import 'package:surface/widgets/post/post_item.dart'; import 'package:surface/widgets/post/post_item.dart';
import 'package:very_good_infinite_list/very_good_infinite_list.dart'; import 'package:very_good_infinite_list/very_good_infinite_list.dart';
const kPostChannels = ['Global', 'Friends', 'Following'];
const kPostChannelIcons = [Symbols.globe, Symbols.group, Symbols.subscriptions];
const Map<String, IconData> kCategoryIcons = { const Map<String, IconData> kCategoryIcons = {
'technology': Symbols.tools_wrench, 'technology': Symbols.tools_wrench,
'gaming': Symbols.gamepad, 'gaming': Symbols.gamepad,
@ -39,17 +41,17 @@ class ExploreScreen extends StatefulWidget {
State<ExploreScreen> createState() => _ExploreScreenState(); State<ExploreScreen> createState() => _ExploreScreenState();
} }
// You know what? I'm not going to make this a global variable.
// Cuz the global key make the selected category not update to child widget when the category is changed.
SnPostCategory? _selectedCategory;
class _ExploreScreenState extends State<ExploreScreen> class _ExploreScreenState extends State<ExploreScreen>
with SingleTickerProviderStateMixin { with TickerProviderStateMixin {
late final TabController _tabController = late TabController _tabController = TabController(
TabController(length: 4, vsync: this); length: kPostChannels.length,
vsync: this,
);
final _fabKey = GlobalKey<ExpandableFabState>(); final _fabKey = GlobalKey<ExpandableFabState>();
final _listKeys = List.generate(4, (_) => GlobalKey<_PostListWidgetState>()); final _listKey = GlobalKey<_PostListWidgetState>();
bool _showCategories = false;
final List<SnPostCategory> _categories = List.empty(growable: true); final List<SnPostCategory> _categories = List.empty(growable: true);
@ -69,14 +71,68 @@ class _ExploreScreenState extends State<ExploreScreen>
} }
} }
void _clearFilter() { final List<SnRealm> _realms = List.empty(growable: true);
_selectedCategory = null;
Future<void> _fetchRealms() async {
try {
final rels = context.read<SnRealmProvider>();
final out = await rels.listAvailableRealms();
setState(() {
_realms.addAll(out);
});
} catch (err) {
if (!mounted) return;
context.showErrorDialog(err);
rethrow;
}
}
void _toggleShowCategories() {
_showCategories = !_showCategories;
if (_showCategories) {
_tabController = TabController(length: _categories.length, vsync: this);
_listKey.currentState?.setCategory(_categories[_tabController.index]);
_listKey.currentState?.refreshPosts();
} else {
_tabController = TabController(length: kPostChannels.length, vsync: this);
_listKey.currentState?.setCategory(null);
_listKey.currentState?.refreshPosts();
}
_tabListen();
setState(() {});
}
void _tabListen() {
_tabController.addListener(() {
if (_tabController.indexIsChanging) {
if (_showCategories) {
_listKey.currentState?.setCategory(_categories[_tabController.index]);
_listKey.currentState?.refreshPosts();
return;
}
switch (_tabController.index) {
case 0:
case 3:
_listKey.currentState?.setChannel(null);
break;
case 1:
_listKey.currentState?.setChannel('friends');
break;
case 2:
_listKey.currentState?.setChannel('following');
break;
}
_listKey.currentState?.refreshPosts();
}
});
} }
@override @override
void initState() { void initState() {
_fetchCategories();
super.initState(); super.initState();
_tabListen();
_fetchCategories();
_fetchRealms();
} }
@override @override
@ -86,7 +142,7 @@ class _ExploreScreenState extends State<ExploreScreen>
} }
Future<void> refreshPosts() async { Future<void> refreshPosts() async {
await _listKeys[_tabController.index].currentState?.refreshPosts(); await _listKey.currentState?.refreshPosts();
} }
@override @override
@ -111,7 +167,6 @@ class _ExploreScreenState extends State<ExploreScreen>
Theme.of(context).floatingActionButtonTheme.foregroundColor, Theme.of(context).floatingActionButtonTheme.foregroundColor,
backgroundColor: backgroundColor:
Theme.of(context).floatingActionButtonTheme.backgroundColor, Theme.of(context).floatingActionButtonTheme.backgroundColor,
shape: const CircleBorder(),
), ),
closeButtonBuilder: DefaultFloatingActionButtonBuilder( closeButtonBuilder: DefaultFloatingActionButtonBuilder(
child: const Icon(Symbols.close, size: 28), child: const Icon(Symbols.close, size: 28),
@ -120,90 +175,39 @@ class _ExploreScreenState extends State<ExploreScreen>
Theme.of(context).floatingActionButtonTheme.foregroundColor, Theme.of(context).floatingActionButtonTheme.foregroundColor,
backgroundColor: backgroundColor:
Theme.of(context).floatingActionButtonTheme.backgroundColor, Theme.of(context).floatingActionButtonTheme.backgroundColor,
shape: const CircleBorder(),
), ),
children: [ children: [
Row( Row(
children: [ children: [
Text('writePostTypeStory').tr(), Text('writePost').tr(),
const Gap(20), const Gap(20),
FloatingActionButton( FloatingActionButton(
heroTag: null, heroTag: null,
tooltip: 'writePostTypeStory'.tr(), tooltip: 'writePost'.tr(),
onPressed: () { onPressed: () {
GoRouter.of(context).pushNamed('postEditor', pathParameters: { GoRouter.of(context).pushNamed('postEditor').then((value) {
'mode': 'stories',
}).then((value) {
if (value == true) { if (value == true) {
refreshPosts(); refreshPosts();
} }
}); });
_fabKey.currentState!.toggle(); _fabKey.currentState!.toggle();
}, },
child: const Icon(Symbols.post_rounded), child: const Icon(Symbols.edit),
), ),
], ],
), ),
Row( Row(
children: [ children: [
Text('writePostTypeArticle').tr(), Text('postDraftBox').tr(),
const Gap(20), const Gap(20),
FloatingActionButton( FloatingActionButton(
heroTag: null, heroTag: null,
tooltip: 'writePostTypeArticle'.tr(), tooltip: 'postDraftBox'.tr(),
onPressed: () { onPressed: () {
GoRouter.of(context).pushNamed('postEditor', pathParameters: { GoRouter.of(context).pushNamed('postDraftBox');
'mode': 'articles',
}).then((value) {
if (value == true) {
refreshPosts();
}
});
_fabKey.currentState!.toggle(); _fabKey.currentState!.toggle();
}, },
child: const Icon(Symbols.news), child: const Icon(Symbols.box_edit),
),
],
),
Row(
children: [
Text('writePostTypeQuestion').tr(),
const Gap(20),
FloatingActionButton(
heroTag: null,
tooltip: 'writePostTypeQuestion'.tr(),
onPressed: () {
GoRouter.of(context).pushNamed('postEditor', pathParameters: {
'mode': 'questions',
}).then((value) {
if (value == true) {
refreshPosts();
}
});
_fabKey.currentState!.toggle();
},
child: const Icon(Symbols.question_answer),
),
],
),
Row(
children: [
Text('writePostTypeVideo').tr(),
const Gap(20),
FloatingActionButton(
heroTag: null,
tooltip: 'writePostTypeVideo'.tr(),
onPressed: () {
GoRouter.of(context).pushNamed('postEditor', pathParameters: {
'mode': 'videos',
}).then((value) {
if (value == true) {
refreshPosts();
}
});
_fabKey.currentState!.toggle();
},
child: const Icon(Symbols.video_call),
), ),
], ],
), ),
@ -216,25 +220,74 @@ class _ExploreScreenState extends State<ExploreScreen>
handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context), handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context),
sliver: SliverAppBar( sliver: SliverAppBar(
leading: AutoAppBarLeading(), leading: AutoAppBarLeading(),
title: Text('screenExplore').tr(), titleSpacing: 0,
title: Row(
children: [
IconButton(
icon: const Icon(Symbols.shuffle),
onPressed: () {
GoRouter.of(context).pushNamed('postShuffle');
},
),
Expanded(
child: Center(
child: IconButton(
padding: EdgeInsets.zero,
constraints: const BoxConstraints(),
visualDensity: VisualDensity.compact,
icon: _listKey.currentState?.realm != null
? AccountImage(
content: _listKey.currentState!.realm!.avatar,
radius: 14,
)
: Image.asset(
'assets/icon/icon-dark.png',
width: 32,
height: 32,
color: Theme.of(context)
.appBarTheme
.foregroundColor,
),
onPressed: () {
showModalBottomSheet(
context: context,
builder: (context) => _PostListRealmPopup(
realms: _realms,
onUpdate: (realm) {
_listKey.currentState?.setRealm(realm);
_listKey.currentState?.refreshPosts();
Future.delayed(
const Duration(milliseconds: 100), () {
if (mounted) {
setState(() {});
}
});
},
),
);
},
),
),
),
],
),
floating: true, floating: true,
snap: true, snap: true,
actions: [ actions: [
IconButton( IconButton(
icon: const Icon(Symbols.category), icon: const Icon(Symbols.category),
style: _showCategories
? ButtonStyle(
foregroundColor: WidgetStateProperty.all(
Theme.of(context).colorScheme.primary,
),
backgroundColor: MaterialStateProperty.all(
Theme.of(context).colorScheme.secondaryContainer,
),
)
: null,
onPressed: () { onPressed: () {
showModalBottomSheet( _toggleShowCategories();
context: context,
builder: (context) => _PostCategoryPickerPopup(
categories: _categories,
selected: _selectedCategory,
),
).then((value) {
if (value != null && context.mounted) {
_selectedCategory = value == false ? null : value;
refreshPosts();
}
});
}, },
), ),
IconButton( IconButton(
@ -246,122 +299,79 @@ class _ExploreScreenState extends State<ExploreScreen>
const Gap(8), const Gap(8),
], ],
bottom: TabBar( bottom: TabBar(
isScrollable: _showCategories,
controller: _tabController, controller: _tabController,
tabs: [ tabs: _showCategories
Tab( ? [
child: Row( for (final category in _categories)
mainAxisSize: MainAxisSize.min, Tab(
crossAxisAlignment: CrossAxisAlignment.center, child: Row(
children: [ mainAxisSize: MainAxisSize.min,
Icon(Symbols.globe, crossAxisAlignment: CrossAxisAlignment.center,
size: 20, children: [
color: Theme.of(context) Icon(
.appBarTheme kCategoryIcons[category.alias] ??
.foregroundColor), Symbols.question_mark,
const Gap(8), color: Theme.of(context)
Flexible( .appBarTheme
child: Text( .foregroundColor!,
'postChannelGlobal', ),
maxLines: 1, const Gap(8),
).tr().textColor( Flexible(
Theme.of(context).appBarTheme.foregroundColor), child: Text(
), 'postCategory${category.alias.capitalize()}'
.trExists()
? 'postCategory${category.alias.capitalize()}'
.tr()
: category.name,
maxLines: 1,
).textColor(
Theme.of(context)
.appBarTheme
.foregroundColor!,
),
),
],
),
),
]
: [
for (final channel in kPostChannels)
Tab(
child: Row(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Icon(
kPostChannelIcons[
kPostChannels.indexOf(channel)],
size: 20,
color: Theme.of(context)
.appBarTheme
.foregroundColor,
),
const Gap(8),
Flexible(
child: Text(
'postChannel$channel',
maxLines: 1,
).tr().textColor(
Theme.of(context)
.appBarTheme
.foregroundColor,
),
),
],
),
),
], ],
),
),
Tab(
child: Row(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Icon(Symbols.group,
size: 20,
color: Theme.of(context)
.appBarTheme
.foregroundColor),
const Gap(8),
Flexible(
child: Text(
'postChannelFriends',
maxLines: 1,
textAlign: TextAlign.center,
).tr().textColor(
Theme.of(context).appBarTheme.foregroundColor),
),
],
),
),
Tab(
child: Row(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Icon(Symbols.subscriptions,
size: 20,
color: Theme.of(context)
.appBarTheme
.foregroundColor),
const Gap(8),
Flexible(
child: Text(
'postChannelFollowing',
maxLines: 1,
).tr().textColor(
Theme.of(context).appBarTheme.foregroundColor),
),
],
),
),
Tab(
child: Row(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Icon(Symbols.workspaces,
size: 20,
color: Theme.of(context)
.appBarTheme
.foregroundColor),
const Gap(8),
Flexible(
child: Text(
'postChannelRealm',
maxLines: 1,
).tr().textColor(
Theme.of(context).appBarTheme.foregroundColor),
),
],
),
),
],
), ),
), ),
), ),
]; ];
}, },
body: TabBarView( body: _PostListWidget(
controller: _tabController, key: _listKey,
children: [
_PostListWidget(
key: _listKeys[0],
onClearFilter: _clearFilter,
),
_PostListWidget(
key: _listKeys[1],
channel: 'friends',
onClearFilter: _clearFilter,
),
_PostListWidget(
key: _listKeys[2],
channel: 'following',
onClearFilter: _clearFilter,
),
_PostListWidget(
key: _listKeys[3],
withRealm: true,
onClearFilter: _clearFilter,
),
],
), ),
), ),
); );
@ -369,15 +379,7 @@ class _ExploreScreenState extends State<ExploreScreen>
} }
class _PostListWidget extends StatefulWidget { class _PostListWidget extends StatefulWidget {
final String? channel; const _PostListWidget({super.key});
final bool withRealm;
final Function onClearFilter;
const _PostListWidget(
{super.key,
this.channel,
this.withRealm = false,
required this.onClearFilter});
@override @override
State<_PostListWidget> createState() => _PostListWidgetState(); State<_PostListWidget> createState() => _PostListWidgetState();
@ -386,25 +388,13 @@ class _PostListWidget extends StatefulWidget {
class _PostListWidgetState extends State<_PostListWidget> { class _PostListWidgetState extends State<_PostListWidget> {
bool _isBusy = false; bool _isBusy = false;
final List<SnPost> _posts = List.empty(growable: true); SnRealm? get realm => _selectedRealm;
final List<SnRealm> _realms = List.empty(growable: true);
SnRealm? _selectedRealm;
int? _postCount;
Future<void> _fetchRealms() async { final List<SnPost> _posts = List.empty(growable: true);
try { SnRealm? _selectedRealm;
final rels = context.read<SnRealmProvider>(); String? _selectedChannel;
final out = await rels.listAvailableRealms(); SnPostCategory? _selectedCategory;
setState(() { int? _postCount;
_realms.addAll(out);
_selectedRealm = out.firstOrNull;
});
} catch (err) {
if (!mounted) return;
context.showErrorDialog(err);
rethrow;
}
}
Future<void> _fetchPosts() async { Future<void> _fetchPosts() async {
if (_postCount != null && _posts.length >= _postCount!) return; if (_postCount != null && _posts.length >= _postCount!) return;
@ -416,7 +406,7 @@ class _PostListWidgetState extends State<_PostListWidget> {
take: 10, take: 10,
offset: _posts.length, offset: _posts.length,
categories: _selectedCategory != null ? [_selectedCategory!.alias] : null, categories: _selectedCategory != null ? [_selectedCategory!.alias] : null,
channel: widget.channel, channel: _selectedChannel,
realm: _selectedRealm?.alias, realm: _selectedRealm?.alias,
); );
final out = result.$1; final out = result.$1;
@ -429,6 +419,21 @@ class _PostListWidgetState extends State<_PostListWidget> {
if (mounted) setState(() => _isBusy = false); if (mounted) setState(() => _isBusy = false);
} }
void setChannel(String? channel) {
_selectedChannel = channel;
setState(() {});
}
void setRealm(SnRealm? realm) {
_selectedRealm = realm;
setState(() {});
}
void setCategory(SnPostCategory? category) {
_selectedCategory = category;
setState(() {});
}
Future<void> refreshPosts() { Future<void> refreshPosts() {
_postCount = null; _postCount = null;
_posts.clear(); _posts.clear();
@ -438,13 +443,7 @@ class _PostListWidgetState extends State<_PostListWidget> {
@override @override
void initState() { void initState() {
super.initState(); super.initState();
if (widget.withRealm) { _fetchPosts();
_fetchRealms().then((_) {
_fetchPosts();
});
} else {
_fetchPosts();
}
} }
@override @override
@ -467,52 +466,13 @@ class _PostListWidgetState extends State<_PostListWidget> {
IconButton( IconButton(
icon: const Icon(Symbols.clear), icon: const Icon(Symbols.clear),
onPressed: () { onPressed: () {
widget.onClearFilter.call(); setState(() => _selectedCategory = null);
refreshPosts(); refreshPosts();
}, },
), ),
], ],
padding: const EdgeInsets.only(left: 20, right: 4), padding: const EdgeInsets.only(left: 20, right: 4),
), ),
if (widget.withRealm)
DropdownButtonHideUnderline(
child: DropdownButton2<SnRealm>(
isExpanded: true,
items: _realms
.map(
(ele) => DropdownMenuItem<SnRealm>(
value: ele,
child: Row(
children: [
AccountImage(
content: ele.avatar,
fallbackWidget: const Icon(Symbols.group, size: 16),
radius: 14,
),
const Gap(8),
Text(
ele.name,
style: Theme.of(context).textTheme.bodyMedium,
),
],
),
),
)
.toList(),
value: _selectedRealm,
onChanged: (SnRealm? value) {
setState(() => _selectedRealm = value);
refreshPosts();
},
buttonStyleData: const ButtonStyleData(
padding: EdgeInsets.only(left: 4, right: 12),
),
menuItemStyleData: const MenuItemStyleData(
height: 48,
),
),
),
if (widget.withRealm) const Divider(height: 1),
Expanded( Expanded(
child: MediaQuery.removePadding( child: MediaQuery.removePadding(
context: context, context: context,
@ -521,6 +481,7 @@ class _PostListWidgetState extends State<_PostListWidget> {
displacement: 40 + MediaQuery.of(context).padding.top, displacement: 40 + MediaQuery.of(context).padding.top,
onRefresh: () => refreshPosts(), onRefresh: () => refreshPosts(),
child: InfiniteList( child: InfiniteList(
padding: EdgeInsets.only(top: 8),
itemCount: _posts.length, itemCount: _posts.length,
isLoading: _isBusy, isLoading: _isBusy,
centerLoading: true, centerLoading: true,
@ -542,18 +503,21 @@ class _PostListWidgetState extends State<_PostListWidget> {
separatorBuilder: (_, __) => const Gap(8), separatorBuilder: (_, __) => const Gap(8),
), ),
), ),
).padding(top: 8), ),
), ),
], ],
); );
} }
} }
class _PostCategoryPickerPopup extends StatelessWidget { class _PostListRealmPopup extends StatelessWidget {
final List<SnPostCategory> categories; final List<SnRealm>? realms;
final SnPostCategory? selected; final Function(SnRealm?) onUpdate;
const _PostCategoryPickerPopup({required this.categories, this.selected}); const _PostListRealmPopup({
required this.realms,
required this.onUpdate,
});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -563,62 +527,38 @@ class _PostCategoryPickerPopup extends StatelessWidget {
Row( Row(
crossAxisAlignment: CrossAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center,
children: [ children: [
const Icon(Symbols.category, size: 24), const Icon(Symbols.face, size: 24),
const Gap(16), const Gap(16),
Text('postCategory') Text('accountRealms', style: Theme.of(context).textTheme.titleLarge)
.tr() .tr(),
.textStyle(Theme.of(context).textTheme.titleLarge!),
], ],
).padding(horizontal: 20, top: 16, bottom: 12), ).padding(horizontal: 20, top: 16, bottom: 12),
ListTile( ListTile(
leading: const Icon(Symbols.clear), leading: const Icon(Symbols.close),
title: Text('postFilterReset').tr(), title: Text('postInGlobal').tr(),
subtitle: Text('postFilterResetDescription').tr(), subtitle: Text('postViewInGlobalDescription').tr(),
contentPadding: const EdgeInsets.symmetric(horizontal: 20), contentPadding: const EdgeInsets.symmetric(horizontal: 24),
onTap: () { onTap: () {
Navigator.pop(context, false); onUpdate.call(null);
Navigator.pop(context);
}, },
), ),
const Divider(height: 1), const Divider(height: 1),
Expanded( Expanded(
child: GridView.count( child: ListView.builder(
crossAxisCount: 4, itemCount: realms?.length ?? 0,
shrinkWrap: true, itemBuilder: (context, idx) {
physics: const NeverScrollableScrollPhysics(), final realm = realms![idx];
childAspectRatio: 1, return ListTile(
children: categories title: Text(realm.name),
.map( subtitle: Text('@${realm.alias}'),
(ele) => InkWell( leading: AccountImage(content: realm.avatar, radius: 18),
onTap: () { onTap: () {
_selectedCategory = ele; onUpdate.call(realm);
Navigator.pop(context, ele); Navigator.pop(context);
}, },
child: Column( );
crossAxisAlignment: CrossAxisAlignment.center, },
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: [
Icon(
kCategoryIcons[ele.alias] ?? Symbols.question_mark,
color: selected == ele
? Theme.of(context).colorScheme.primary
: null,
),
const Gap(4),
Text(
'postCategory${ele.alias.capitalize()}'.trExists()
? 'postCategory${ele.alias.capitalize()}'.tr()
: ele.name,
)
.textStyle(Theme.of(context).textTheme.titleMedium!)
.textColor(selected == ele
? Theme.of(context).colorScheme.primary
: null),
],
),
),
)
.toList(),
), ),
), ),
], ],

View File

@ -546,11 +546,26 @@ class _HomeDashCheckInWidgetState extends State<_HomeDashCheckInWidget> {
'+${_todayRecord!.resultExperience} EXP', '+${_todayRecord!.resultExperience} EXP',
style: Theme.of(context).textTheme.bodyLarge, style: Theme.of(context).textTheme.bodyLarge,
), ),
if (_todayRecord!.resultCoin >= 0) if (_todayRecord!.resultCoin > 0)
Text( Text(
'+${_todayRecord!.resultCoin} ${'walletCurrencyShort'.tr()}', '+${_todayRecord!.resultCoin} ${'walletCurrencyShort'.tr()}',
style: Theme.of(context).textTheme.bodyLarge, style: Theme.of(context).textTheme.bodyLarge,
) ),
if (_todayRecord!.currentStreak > 0)
Row(
children: [
const Icon(
Symbols.local_fire_department,
size: 14,
).padding(bottom: 2),
const Gap(4),
Text(
'checkInStreak'
.plural(_todayRecord!.currentStreak),
style: Theme.of(context).textTheme.bodySmall,
),
],
).padding(top: 4),
], ],
), ),
), ),

View File

@ -0,0 +1,88 @@
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:gap/gap.dart';
import 'package:provider/provider.dart';
import 'package:surface/providers/post.dart';
import 'package:surface/types/post.dart';
import 'package:surface/widgets/dialog.dart';
import 'package:surface/widgets/loading_indicator.dart';
import 'package:surface/widgets/navigation/app_scaffold.dart';
import 'package:surface/widgets/post/post_item.dart';
import 'package:very_good_infinite_list/very_good_infinite_list.dart';
class PostDraftBox extends StatefulWidget {
const PostDraftBox({super.key});
@override
State<PostDraftBox> createState() => _PostDraftBoxState();
}
class _PostDraftBoxState extends State<PostDraftBox> {
bool _isBusy = false;
final List<SnPost> _posts = List.empty(growable: true);
int? _totalCount;
Future<void> _fetchPosts() async {
setState(() => _isBusy = true);
try {
final pt = context.read<SnPostContentProvider>();
final resp = await pt.listPosts(
take: 10,
offset: _posts.length,
isDraft: true,
);
final out = resp.$1;
_totalCount = resp.$2;
if (!mounted) return;
_posts.addAll(out);
} catch (err) {
if (!mounted) return;
context.showErrorDialog(err);
} finally {
setState(() => _isBusy = false);
}
}
@override
Widget build(BuildContext context) {
return AppScaffold(
appBar: AppBar(
title: Text('postDraftBox').tr(),
),
body: Column(
children: [
LoadingIndicator(isActive: _isBusy),
Expanded(
child: RefreshIndicator(
onRefresh: () {
_posts.clear();
return _fetchPosts();
},
child: InfiniteList(
padding: EdgeInsets.only(top: 8),
hasReachedMax:
_totalCount != null && _posts.length >= _totalCount!,
itemCount: _posts.length,
onFetchData: () => _fetchPosts(),
itemBuilder: (context, idx) {
final ele = _posts[idx];
return OpenablePostItem(
data: ele,
onChanged: (data) {
_posts[idx] = data;
},
onDeleted: () {
_posts.clear();
_fetchPosts();
},
);
},
separatorBuilder: (_, __) => const Gap(8),
),
),
),
],
),
);
}
}

View File

@ -18,6 +18,7 @@ import 'package:surface/controllers/post_write_controller.dart';
import 'package:surface/providers/config.dart'; import 'package:surface/providers/config.dart';
import 'package:surface/providers/sn_attachment.dart'; import 'package:surface/providers/sn_attachment.dart';
import 'package:surface/providers/sn_network.dart'; import 'package:surface/providers/sn_network.dart';
import 'package:surface/providers/sn_realm.dart';
import 'package:surface/types/attachment.dart'; import 'package:surface/types/attachment.dart';
import 'package:surface/types/post.dart'; import 'package:surface/types/post.dart';
import 'package:surface/types/realm.dart'; import 'package:surface/types/realm.dart';
@ -36,7 +37,8 @@ import 'package:provider/provider.dart';
import 'package:surface/widgets/post/post_poll_editor.dart'; import 'package:surface/widgets/post/post_poll_editor.dart';
import 'package:uuid/uuid.dart'; import 'package:uuid/uuid.dart';
import '../../providers/sn_realm.dart'; const kPostTypes = ['Story', 'Article', 'Question', 'Video'];
const kPostTypeAliases = ['stories', 'articles', 'questions', 'videos'];
class PostEditorExtra { class PostEditorExtra {
final String? text; final String? text;
@ -53,7 +55,7 @@ class PostEditorExtra {
} }
class PostEditorScreen extends StatefulWidget { class PostEditorScreen extends StatefulWidget {
final String mode; final String? mode;
final int? postEditId; final int? postEditId;
final int? postReplyId; final int? postReplyId;
final int? postRepostId; final int? postRepostId;
@ -72,7 +74,10 @@ class PostEditorScreen extends StatefulWidget {
State<PostEditorScreen> createState() => _PostEditorScreenState(); State<PostEditorScreen> createState() => _PostEditorScreenState();
} }
class _PostEditorScreenState extends State<PostEditorScreen> { class _PostEditorScreenState extends State<PostEditorScreen>
with SingleTickerProviderStateMixin {
late final TabController _tabController =
TabController(length: 4, vsync: this);
late final PostWriteController _writeController = PostWriteController( late final PostWriteController _writeController = PostWriteController(
doLoadFromTemporary: widget.postEditId == null, doLoadFromTemporary: widget.postEditId == null,
); );
@ -133,6 +138,15 @@ class _PostEditorScreenState extends State<PostEditorScreen> {
], ],
scope: HotKeyScope.inapp, scope: HotKeyScope.inapp,
); );
final HotKey _saveDraftHotKey = HotKey(
key: PhysicalKeyboardKey.keyS,
modifiers: [
(!kIsWeb && Platform.isMacOS)
? HotKeyModifier.meta
: HotKeyModifier.control
],
scope: HotKeyScope.inapp,
);
void _registerHotKey() { void _registerHotKey() {
if (kIsWeb || Platform.isAndroid || Platform.isIOS) return; if (kIsWeb || Platform.isAndroid || Platform.isIOS) return;
@ -148,6 +162,11 @@ class _PostEditorScreenState extends State<PostEditorScreen> {
]); ]);
setState(() {}); setState(() {});
}); });
hotKeyManager.register(_saveDraftHotKey, keyDownHandler: (_) async {
if (mounted) {
_writeController.sendPost(context);
}
});
} }
void _showPublisherPopup() { void _showPublisherPopup() {
@ -209,9 +228,11 @@ class _PostEditorScreenState extends State<PostEditorScreen> {
@override @override
void dispose() { void dispose() {
_tabController.dispose();
_writeController.dispose(); _writeController.dispose();
if (!kIsWeb && !(Platform.isAndroid || Platform.isIOS)) { if (!kIsWeb && !(Platform.isAndroid || Platform.isIOS)) {
hotKeyManager.unregister(_pasteHotKey); hotKeyManager.unregister(_pasteHotKey);
hotKeyManager.unregister(_saveDraftHotKey);
} }
super.dispose(); super.dispose();
} }
@ -220,14 +241,16 @@ class _PostEditorScreenState extends State<PostEditorScreen> {
void initState() { void initState() {
super.initState(); super.initState();
_registerHotKey(); _registerHotKey();
if (!PostWriteController.kTitleMap.keys.contains(widget.mode)) {
context.showErrorDialog('Unknown post type');
Navigator.pop(context);
} else {
_writeController.setMode(widget.mode);
}
_fetchRealms(); _fetchRealms();
_fetchPublishers(); _fetchPublishers();
if (widget.mode != null) {
_writeController.setMode(widget.mode!);
}
_tabController.addListener(() {
if (_tabController.indexIsChanging) {
_writeController.setMode(kPostTypeAliases[_tabController.index]);
}
});
_writeController.fetchRelatedPost( _writeController.fetchRelatedPost(
context, context,
editing: widget.postEditId, editing: widget.postEditId,
@ -255,38 +278,55 @@ class _PostEditorScreenState extends State<PostEditorScreen> {
Navigator.pop(context); Navigator.pop(context);
}, },
), ),
title: RichText( title: Text(
textAlign: TextAlign.center, _writeController.title.isNotEmpty
text: TextSpan(children: [ ? _writeController.title
TextSpan( : 'untitled'.tr(),
text: _writeController.title.isNotEmpty
? _writeController.title
: 'untitled'.tr(),
style: Theme.of(context).textTheme.titleLarge!.copyWith(
color: Theme.of(context).appBarTheme.foregroundColor!,
),
),
const TextSpan(text: '\n'),
TextSpan(
text: PostWriteController.kTitleMap[widget.mode]!.tr(),
style: Theme.of(context).textTheme.bodySmall!.copyWith(
color: Theme.of(context).appBarTheme.foregroundColor!,
),
),
]),
maxLines: 2,
), ),
actions: [ actions: [
IconButton(
icon: _writeController.editingDraft
? const Icon(Icons.save)
: const Icon(Symbols.save_as),
onPressed: () {
_writeController.sendPost(context, saveAsDraft: true).then(
(_) {
if (!context.mounted) return;
context.showSnackbar('postDraftSaved'.tr());
HapticFeedback.mediumImpact();
},
);
},
),
IconButton( IconButton(
icon: const Icon(Symbols.tune), icon: const Icon(Symbols.tune),
onPressed: _writeController.isBusy ? null : _updateMeta, onPressed: _writeController.isBusy ? null : _updateMeta,
), ),
const Gap(8), const Gap(8),
], ],
bottom: _writeController.isNotEmpty || widget.mode != null
? null
: TabBar(
controller: _tabController,
tabs: [
for (final type in kPostTypes)
Tab(
child: Text(
'postType$type'.tr(),
style: TextStyle(
color: Theme.of(context)
.appBarTheme
.foregroundColor!,
),
),
),
],
),
), ),
body: Column( body: Column(
children: [ children: [
if (_writeController.editingPost != null) if (_writeController.editingPost != null &&
!_writeController.editingDraft)
Container( Container(
padding: const EdgeInsets.only( padding: const EdgeInsets.only(
top: 4, bottom: 4, left: 20, right: 20), top: 4, bottom: 4, left: 20, right: 20),
@ -374,7 +414,7 @@ class _PostEditorScreenState extends State<PostEditorScreen> {
children: [ children: [
SingleChildScrollView( SingleChildScrollView(
padding: EdgeInsets.only(bottom: 160), padding: EdgeInsets.only(bottom: 160),
child: StyledWidget(switch (_writeController.mode) { child: switch (_writeController.mode) {
'stories' => _PostStoryEditor( 'stories' => _PostStoryEditor(
controller: _writeController, controller: _writeController,
onTapPublisher: _showPublisherPopup, onTapPublisher: _showPublisherPopup,
@ -396,8 +436,7 @@ class _PostEditorScreenState extends State<PostEditorScreen> {
onTapRealm: _showRealmPopup, onTapRealm: _showRealmPopup,
), ),
_ => const Placeholder(), _ => const Placeholder(),
}) },
.padding(top: 8),
), ),
if (_writeController.attachments.isNotEmpty || if (_writeController.attachments.isNotEmpty ||
_writeController.thumbnail != null) _writeController.thumbnail != null)
@ -720,7 +759,7 @@ class _PostStoryEditor extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Container( return Container(
padding: const EdgeInsets.symmetric(horizontal: 12), padding: const EdgeInsets.only(left: 12, right: 12, top: 8),
constraints: const BoxConstraints(maxWidth: 640), constraints: const BoxConstraints(maxWidth: 640),
child: Row( child: Row(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
@ -969,7 +1008,7 @@ class _PostQuestionEditor extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Container( return Container(
padding: const EdgeInsets.symmetric(horizontal: 12), padding: const EdgeInsets.only(left: 12, right: 12, top: 8),
constraints: const BoxConstraints(maxWidth: 640), constraints: const BoxConstraints(maxWidth: 640),
child: Row( child: Row(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
@ -1053,7 +1092,7 @@ class _PostQuestionEditor extends StatelessWidget {
), ),
), ),
], ],
).padding(top: 8), ),
); );
} }
} }
@ -1154,7 +1193,7 @@ class _PostVideoEditor extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Container( return Container(
padding: const EdgeInsets.symmetric(horizontal: 12), padding: const EdgeInsets.only(left: 12, right: 12, top: 8),
constraints: const BoxConstraints(maxWidth: 640), constraints: const BoxConstraints(maxWidth: 640),
child: Row( child: Row(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,

View File

@ -0,0 +1,132 @@
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:flutter_card_swiper/flutter_card_swiper.dart';
import 'package:material_symbols_icons/symbols.dart';
import 'package:provider/provider.dart';
import 'package:styled_widget/styled_widget.dart';
import 'package:surface/providers/post.dart';
import 'package:surface/types/post.dart';
import 'package:surface/widgets/dialog.dart';
import 'package:surface/widgets/navigation/app_scaffold.dart';
import 'package:surface/widgets/post/post_item.dart';
class PostShuffleScreen extends StatefulWidget {
const PostShuffleScreen({super.key});
@override
State<PostShuffleScreen> createState() => _PostShuffleScreenState();
}
class _PostShuffleScreenState extends State<PostShuffleScreen> {
late final CardSwiperController _cardController = CardSwiperController();
bool _isBusy = false;
final List<SnPost> _posts = List.empty(growable: true);
Future<void> _fetchPosts() async {
_posts.clear();
setState(() => _isBusy = true);
try {
final pt = context.read<SnPostContentProvider>();
final result = await pt.listPosts(
take: 10,
offset: _posts.length,
isShuffle: true,
);
_posts.addAll(result.$1);
} catch (err) {
if (!mounted) return;
context.showErrorDialog(err);
} finally {
setState(() => _isBusy = false);
}
}
@override
void initState() {
super.initState();
_fetchPosts();
}
@override
void dispose() {
super.dispose();
_cardController.dispose();
}
@override
Widget build(BuildContext context) {
return AppScaffold(
appBar: AppBar(
title: Text('postShuffle').tr(),
),
body: Stack(
children: [
Column(
children: [
if (_isBusy || _posts.isEmpty)
const Expanded(
child: Center(
child: CircularProgressIndicator(),
),
)
else
Expanded(
child: CardSwiper(
controller: _cardController,
isLoop: false,
padding: EdgeInsets.zero,
cardsCount: _posts.length,
cardBuilder: (context, idx, _, __) {
final ele = _posts[idx];
return SingleChildScrollView(
child: Center(
child: OpenablePostItem(
key: ValueKey(ele),
data: ele,
maxWidth: 640,
onChanged: (ele) {
_posts[idx] = ele;
setState(() {});
},
onDeleted: () {
_fetchPosts();
},
).padding(
all: 24,
bottom:
MediaQuery.of(context).padding.bottom + 16 + 50,
),
),
);
},
onEnd: () {
_fetchPosts();
},
),
),
],
),
if (!_isBusy && _posts.isNotEmpty)
Positioned(
bottom: MediaQuery.of(context).padding.bottom + 16,
left: 16,
right: 16,
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
IconButton.filled(
icon: const Icon(Symbols.next_plan),
color: Theme.of(context).colorScheme.onPrimary,
onPressed: () {
_cardController.swipe(CardSwiperDirection.right);
},
),
],
),
),
],
),
);
}
}

View File

@ -61,7 +61,7 @@ class _AppSharingListenerState extends State<AppSharingListener> {
onTap: () { onTap: () {
GoRouter.of(context).pushNamed( GoRouter.of(context).pushNamed(
'postEditor', 'postEditor',
pathParameters: { queryParameters: {
'mode': 'stories', 'mode': 'stories',
}, },
extra: PostEditorExtra( extra: PostEditorExtra(

View File

@ -50,16 +50,17 @@ Future<ThemeData> createAppTheme(
useMaterial3 ?? (prefs.getBool(kMaterialYouToggleStoreKey) ?? true); useMaterial3 ?? (prefs.getBool(kMaterialYouToggleStoreKey) ?? true);
final inUseFonts = (customFonts ?? prefs.getString(kAppCustomFonts)) final inUseFonts = (customFonts ?? prefs.getString(kAppCustomFonts))
?.split(',') ?.split(',')
.map((ele) => ele.trim()) .map((ele) => ele.trim())
.toList(); .toList() ??
['Nunito'];
return ThemeData( return ThemeData(
useMaterial3: useM3, useMaterial3: useM3,
colorScheme: colorScheme, colorScheme: colorScheme,
brightness: brightness, brightness: brightness,
fontFamily: inUseFonts?.firstOrNull, fontFamily: inUseFonts.firstOrNull,
fontFamilyFallback: inUseFonts?.sublist(1), fontFamilyFallback: inUseFonts.sublist(1),
iconTheme: IconThemeData( iconTheme: IconThemeData(
fill: 0, fill: 0,
weight: 400, weight: 400,

View File

@ -119,13 +119,33 @@ abstract class SnAccountStatusInfo with _$SnAccountStatusInfo {
required bool isDisturbable, required bool isDisturbable,
required bool isOnline, required bool isOnline,
required DateTime? lastSeenAt, required DateTime? lastSeenAt,
required dynamic status, required SnAccountStatus? status,
}) = _SnAccountStatusInfo; }) = _SnAccountStatusInfo;
factory SnAccountStatusInfo.fromJson(Map<String, Object?> json) => factory SnAccountStatusInfo.fromJson(Map<String, Object?> json) =>
_$SnAccountStatusInfoFromJson(json); _$SnAccountStatusInfoFromJson(json);
} }
@freezed
abstract class SnAccountStatus with _$SnAccountStatus {
const factory SnAccountStatus({
required int id,
required DateTime createdAt,
required DateTime updatedAt,
required DateTime? deletedAt,
required String type,
required String label,
required int attitude,
required bool isNoDisturb,
required bool isInvisible,
required DateTime? clearAt,
required int accountId,
}) = _SnAccountStatus;
factory SnAccountStatus.fromJson(Map<String, Object?> json) =>
_$SnAccountStatusFromJson(json);
}
@freezed @freezed
abstract class SnAbuseReport with _$SnAbuseReport { abstract class SnAbuseReport with _$SnAbuseReport {
const factory SnAbuseReport({ const factory SnAbuseReport({

View File

@ -2139,7 +2139,7 @@ mixin _$SnAccountStatusInfo {
bool get isDisturbable; bool get isDisturbable;
bool get isOnline; bool get isOnline;
DateTime? get lastSeenAt; DateTime? get lastSeenAt;
dynamic get status; SnAccountStatus? get status;
/// Create a copy of SnAccountStatusInfo /// Create a copy of SnAccountStatusInfo
/// with the given fields replaced by the non-null parameter values. /// with the given fields replaced by the non-null parameter values.
@ -2163,13 +2163,13 @@ mixin _$SnAccountStatusInfo {
other.isOnline == isOnline) && other.isOnline == isOnline) &&
(identical(other.lastSeenAt, lastSeenAt) || (identical(other.lastSeenAt, lastSeenAt) ||
other.lastSeenAt == lastSeenAt) && other.lastSeenAt == lastSeenAt) &&
const DeepCollectionEquality().equals(other.status, status)); (identical(other.status, status) || other.status == status));
} }
@JsonKey(includeFromJson: false, includeToJson: false) @JsonKey(includeFromJson: false, includeToJson: false)
@override @override
int get hashCode => Object.hash(runtimeType, isDisturbable, isOnline, int get hashCode =>
lastSeenAt, const DeepCollectionEquality().hash(status)); Object.hash(runtimeType, isDisturbable, isOnline, lastSeenAt, status);
@override @override
String toString() { String toString() {
@ -2187,7 +2187,9 @@ abstract mixin class $SnAccountStatusInfoCopyWith<$Res> {
{bool isDisturbable, {bool isDisturbable,
bool isOnline, bool isOnline,
DateTime? lastSeenAt, DateTime? lastSeenAt,
dynamic status}); SnAccountStatus? status});
$SnAccountStatusCopyWith<$Res>? get status;
} }
/// @nodoc /// @nodoc
@ -2224,9 +2226,23 @@ class _$SnAccountStatusInfoCopyWithImpl<$Res>
status: freezed == status status: freezed == status
? _self.status ? _self.status
: status // ignore: cast_nullable_to_non_nullable : status // ignore: cast_nullable_to_non_nullable
as dynamic, as SnAccountStatus?,
)); ));
} }
/// Create a copy of SnAccountStatusInfo
/// with the given fields replaced by the non-null parameter values.
@override
@pragma('vm:prefer-inline')
$SnAccountStatusCopyWith<$Res>? get status {
if (_self.status == null) {
return null;
}
return $SnAccountStatusCopyWith<$Res>(_self.status!, (value) {
return _then(_self.copyWith(status: value));
});
}
} }
/// @nodoc /// @nodoc
@ -2247,7 +2263,7 @@ class _SnAccountStatusInfo implements SnAccountStatusInfo {
@override @override
final DateTime? lastSeenAt; final DateTime? lastSeenAt;
@override @override
final dynamic status; final SnAccountStatus? status;
/// Create a copy of SnAccountStatusInfo /// Create a copy of SnAccountStatusInfo
/// with the given fields replaced by the non-null parameter values. /// with the given fields replaced by the non-null parameter values.
@ -2276,13 +2292,13 @@ class _SnAccountStatusInfo implements SnAccountStatusInfo {
other.isOnline == isOnline) && other.isOnline == isOnline) &&
(identical(other.lastSeenAt, lastSeenAt) || (identical(other.lastSeenAt, lastSeenAt) ||
other.lastSeenAt == lastSeenAt) && other.lastSeenAt == lastSeenAt) &&
const DeepCollectionEquality().equals(other.status, status)); (identical(other.status, status) || other.status == status));
} }
@JsonKey(includeFromJson: false, includeToJson: false) @JsonKey(includeFromJson: false, includeToJson: false)
@override @override
int get hashCode => Object.hash(runtimeType, isDisturbable, isOnline, int get hashCode =>
lastSeenAt, const DeepCollectionEquality().hash(status)); Object.hash(runtimeType, isDisturbable, isOnline, lastSeenAt, status);
@override @override
String toString() { String toString() {
@ -2302,7 +2318,10 @@ abstract mixin class _$SnAccountStatusInfoCopyWith<$Res>
{bool isDisturbable, {bool isDisturbable,
bool isOnline, bool isOnline,
DateTime? lastSeenAt, DateTime? lastSeenAt,
dynamic status}); SnAccountStatus? status});
@override
$SnAccountStatusCopyWith<$Res>? get status;
} }
/// @nodoc /// @nodoc
@ -2339,7 +2358,386 @@ class __$SnAccountStatusInfoCopyWithImpl<$Res>
status: freezed == status status: freezed == status
? _self.status ? _self.status
: status // ignore: cast_nullable_to_non_nullable : status // ignore: cast_nullable_to_non_nullable
as dynamic, as SnAccountStatus?,
));
}
/// Create a copy of SnAccountStatusInfo
/// with the given fields replaced by the non-null parameter values.
@override
@pragma('vm:prefer-inline')
$SnAccountStatusCopyWith<$Res>? get status {
if (_self.status == null) {
return null;
}
return $SnAccountStatusCopyWith<$Res>(_self.status!, (value) {
return _then(_self.copyWith(status: value));
});
}
}
/// @nodoc
mixin _$SnAccountStatus {
int get id;
DateTime get createdAt;
DateTime get updatedAt;
DateTime? get deletedAt;
String get type;
String get label;
int get attitude;
bool get isNoDisturb;
bool get isInvisible;
DateTime? get clearAt;
int get accountId;
/// Create a copy of SnAccountStatus
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
$SnAccountStatusCopyWith<SnAccountStatus> get copyWith =>
_$SnAccountStatusCopyWithImpl<SnAccountStatus>(
this as SnAccountStatus, _$identity);
/// Serializes this SnAccountStatus to a JSON map.
Map<String, dynamic> toJson();
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is SnAccountStatus &&
(identical(other.id, id) || other.id == id) &&
(identical(other.createdAt, createdAt) ||
other.createdAt == createdAt) &&
(identical(other.updatedAt, updatedAt) ||
other.updatedAt == updatedAt) &&
(identical(other.deletedAt, deletedAt) ||
other.deletedAt == deletedAt) &&
(identical(other.type, type) || other.type == type) &&
(identical(other.label, label) || other.label == label) &&
(identical(other.attitude, attitude) ||
other.attitude == attitude) &&
(identical(other.isNoDisturb, isNoDisturb) ||
other.isNoDisturb == isNoDisturb) &&
(identical(other.isInvisible, isInvisible) ||
other.isInvisible == isInvisible) &&
(identical(other.clearAt, clearAt) || other.clearAt == clearAt) &&
(identical(other.accountId, accountId) ||
other.accountId == accountId));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(
runtimeType,
id,
createdAt,
updatedAt,
deletedAt,
type,
label,
attitude,
isNoDisturb,
isInvisible,
clearAt,
accountId);
@override
String toString() {
return 'SnAccountStatus(id: $id, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt, type: $type, label: $label, attitude: $attitude, isNoDisturb: $isNoDisturb, isInvisible: $isInvisible, clearAt: $clearAt, accountId: $accountId)';
}
}
/// @nodoc
abstract mixin class $SnAccountStatusCopyWith<$Res> {
factory $SnAccountStatusCopyWith(
SnAccountStatus value, $Res Function(SnAccountStatus) _then) =
_$SnAccountStatusCopyWithImpl;
@useResult
$Res call(
{int id,
DateTime createdAt,
DateTime updatedAt,
DateTime? deletedAt,
String type,
String label,
int attitude,
bool isNoDisturb,
bool isInvisible,
DateTime? clearAt,
int accountId});
}
/// @nodoc
class _$SnAccountStatusCopyWithImpl<$Res>
implements $SnAccountStatusCopyWith<$Res> {
_$SnAccountStatusCopyWithImpl(this._self, this._then);
final SnAccountStatus _self;
final $Res Function(SnAccountStatus) _then;
/// Create a copy of SnAccountStatus
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
Object? id = null,
Object? createdAt = null,
Object? updatedAt = null,
Object? deletedAt = freezed,
Object? type = null,
Object? label = null,
Object? attitude = null,
Object? isNoDisturb = null,
Object? isInvisible = null,
Object? clearAt = freezed,
Object? accountId = null,
}) {
return _then(_self.copyWith(
id: null == id
? _self.id
: id // ignore: cast_nullable_to_non_nullable
as int,
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?,
type: null == type
? _self.type
: type // ignore: cast_nullable_to_non_nullable
as String,
label: null == label
? _self.label
: label // ignore: cast_nullable_to_non_nullable
as String,
attitude: null == attitude
? _self.attitude
: attitude // ignore: cast_nullable_to_non_nullable
as int,
isNoDisturb: null == isNoDisturb
? _self.isNoDisturb
: isNoDisturb // ignore: cast_nullable_to_non_nullable
as bool,
isInvisible: null == isInvisible
? _self.isInvisible
: isInvisible // ignore: cast_nullable_to_non_nullable
as bool,
clearAt: freezed == clearAt
? _self.clearAt
: clearAt // ignore: cast_nullable_to_non_nullable
as DateTime?,
accountId: null == accountId
? _self.accountId
: accountId // ignore: cast_nullable_to_non_nullable
as int,
));
}
}
/// @nodoc
@JsonSerializable()
class _SnAccountStatus implements SnAccountStatus {
const _SnAccountStatus(
{required this.id,
required this.createdAt,
required this.updatedAt,
required this.deletedAt,
required this.type,
required this.label,
required this.attitude,
required this.isNoDisturb,
required this.isInvisible,
required this.clearAt,
required this.accountId});
factory _SnAccountStatus.fromJson(Map<String, dynamic> json) =>
_$SnAccountStatusFromJson(json);
@override
final int id;
@override
final DateTime createdAt;
@override
final DateTime updatedAt;
@override
final DateTime? deletedAt;
@override
final String type;
@override
final String label;
@override
final int attitude;
@override
final bool isNoDisturb;
@override
final bool isInvisible;
@override
final DateTime? clearAt;
@override
final int accountId;
/// Create a copy of SnAccountStatus
/// with the given fields replaced by the non-null parameter values.
@override
@JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
_$SnAccountStatusCopyWith<_SnAccountStatus> get copyWith =>
__$SnAccountStatusCopyWithImpl<_SnAccountStatus>(this, _$identity);
@override
Map<String, dynamic> toJson() {
return _$SnAccountStatusToJson(
this,
);
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _SnAccountStatus &&
(identical(other.id, id) || other.id == id) &&
(identical(other.createdAt, createdAt) ||
other.createdAt == createdAt) &&
(identical(other.updatedAt, updatedAt) ||
other.updatedAt == updatedAt) &&
(identical(other.deletedAt, deletedAt) ||
other.deletedAt == deletedAt) &&
(identical(other.type, type) || other.type == type) &&
(identical(other.label, label) || other.label == label) &&
(identical(other.attitude, attitude) ||
other.attitude == attitude) &&
(identical(other.isNoDisturb, isNoDisturb) ||
other.isNoDisturb == isNoDisturb) &&
(identical(other.isInvisible, isInvisible) ||
other.isInvisible == isInvisible) &&
(identical(other.clearAt, clearAt) || other.clearAt == clearAt) &&
(identical(other.accountId, accountId) ||
other.accountId == accountId));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(
runtimeType,
id,
createdAt,
updatedAt,
deletedAt,
type,
label,
attitude,
isNoDisturb,
isInvisible,
clearAt,
accountId);
@override
String toString() {
return 'SnAccountStatus(id: $id, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt, type: $type, label: $label, attitude: $attitude, isNoDisturb: $isNoDisturb, isInvisible: $isInvisible, clearAt: $clearAt, accountId: $accountId)';
}
}
/// @nodoc
abstract mixin class _$SnAccountStatusCopyWith<$Res>
implements $SnAccountStatusCopyWith<$Res> {
factory _$SnAccountStatusCopyWith(
_SnAccountStatus value, $Res Function(_SnAccountStatus) _then) =
__$SnAccountStatusCopyWithImpl;
@override
@useResult
$Res call(
{int id,
DateTime createdAt,
DateTime updatedAt,
DateTime? deletedAt,
String type,
String label,
int attitude,
bool isNoDisturb,
bool isInvisible,
DateTime? clearAt,
int accountId});
}
/// @nodoc
class __$SnAccountStatusCopyWithImpl<$Res>
implements _$SnAccountStatusCopyWith<$Res> {
__$SnAccountStatusCopyWithImpl(this._self, this._then);
final _SnAccountStatus _self;
final $Res Function(_SnAccountStatus) _then;
/// Create a copy of SnAccountStatus
/// with the given fields replaced by the non-null parameter values.
@override
@pragma('vm:prefer-inline')
$Res call({
Object? id = null,
Object? createdAt = null,
Object? updatedAt = null,
Object? deletedAt = freezed,
Object? type = null,
Object? label = null,
Object? attitude = null,
Object? isNoDisturb = null,
Object? isInvisible = null,
Object? clearAt = freezed,
Object? accountId = null,
}) {
return _then(_SnAccountStatus(
id: null == id
? _self.id
: id // ignore: cast_nullable_to_non_nullable
as int,
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?,
type: null == type
? _self.type
: type // ignore: cast_nullable_to_non_nullable
as String,
label: null == label
? _self.label
: label // ignore: cast_nullable_to_non_nullable
as String,
attitude: null == attitude
? _self.attitude
: attitude // ignore: cast_nullable_to_non_nullable
as int,
isNoDisturb: null == isNoDisturb
? _self.isNoDisturb
: isNoDisturb // ignore: cast_nullable_to_non_nullable
as bool,
isInvisible: null == isInvisible
? _self.isInvisible
: isInvisible // ignore: cast_nullable_to_non_nullable
as bool,
clearAt: freezed == clearAt
? _self.clearAt
: clearAt // ignore: cast_nullable_to_non_nullable
as DateTime?,
accountId: null == accountId
? _self.accountId
: accountId // ignore: cast_nullable_to_non_nullable
as int,
)); ));
} }
} }

View File

@ -210,7 +210,9 @@ _SnAccountStatusInfo _$SnAccountStatusInfoFromJson(Map<String, dynamic> json) =>
lastSeenAt: json['last_seen_at'] == null lastSeenAt: json['last_seen_at'] == null
? null ? null
: DateTime.parse(json['last_seen_at'] as String), : DateTime.parse(json['last_seen_at'] as String),
status: json['status'], status: json['status'] == null
? null
: SnAccountStatus.fromJson(json['status'] as Map<String, dynamic>),
); );
Map<String, dynamic> _$SnAccountStatusInfoToJson( Map<String, dynamic> _$SnAccountStatusInfoToJson(
@ -219,7 +221,41 @@ Map<String, dynamic> _$SnAccountStatusInfoToJson(
'is_disturbable': instance.isDisturbable, 'is_disturbable': instance.isDisturbable,
'is_online': instance.isOnline, 'is_online': instance.isOnline,
'last_seen_at': instance.lastSeenAt?.toIso8601String(), 'last_seen_at': instance.lastSeenAt?.toIso8601String(),
'status': instance.status, 'status': instance.status?.toJson(),
};
_SnAccountStatus _$SnAccountStatusFromJson(Map<String, dynamic> json) =>
_SnAccountStatus(
id: (json['id'] as num).toInt(),
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),
type: json['type'] as String,
label: json['label'] as String,
attitude: (json['attitude'] as num).toInt(),
isNoDisturb: json['is_no_disturb'] as bool,
isInvisible: json['is_invisible'] as bool,
clearAt: json['clear_at'] == null
? null
: DateTime.parse(json['clear_at'] as String),
accountId: (json['account_id'] as num).toInt(),
);
Map<String, dynamic> _$SnAccountStatusToJson(_SnAccountStatus instance) =>
<String, dynamic>{
'id': instance.id,
'created_at': instance.createdAt.toIso8601String(),
'updated_at': instance.updatedAt.toIso8601String(),
'deleted_at': instance.deletedAt?.toIso8601String(),
'type': instance.type,
'label': instance.label,
'attitude': instance.attitude,
'is_no_disturb': instance.isNoDisturb,
'is_invisible': instance.isInvisible,
'clear_at': instance.clearAt?.toIso8601String(),
'account_id': instance.accountId,
}; };
_SnAbuseReport _$SnAbuseReportFromJson(Map<String, dynamic> json) => _SnAbuseReport _$SnAbuseReportFromJson(Map<String, dynamic> json) =>

View File

@ -25,11 +25,13 @@ abstract class SnCheckInRecord with _$SnCheckInRecord {
required int resultTier, required int resultTier,
required int resultExperience, required int resultExperience,
required double resultCoin, required double resultCoin,
@Default(0) int currentStreak,
required List<int> resultModifiers, required List<int> resultModifiers,
required int accountId, required int accountId,
}) = _SnCheckInRecord; }) = _SnCheckInRecord;
factory SnCheckInRecord.fromJson(Map<String, dynamic> json) => _$SnCheckInRecordFromJson(json); factory SnCheckInRecord.fromJson(Map<String, dynamic> json) =>
_$SnCheckInRecordFromJson(json);
String get symbol => kCheckInResultTierSymbols[resultTier]; String get symbol => kCheckInResultTierSymbols[resultTier];
} }

View File

@ -22,6 +22,7 @@ mixin _$SnCheckInRecord {
int get resultTier; int get resultTier;
int get resultExperience; int get resultExperience;
double get resultCoin; double get resultCoin;
int get currentStreak;
List<int> get resultModifiers; List<int> get resultModifiers;
int get accountId; int get accountId;
@ -54,6 +55,8 @@ mixin _$SnCheckInRecord {
other.resultExperience == resultExperience) && other.resultExperience == resultExperience) &&
(identical(other.resultCoin, resultCoin) || (identical(other.resultCoin, resultCoin) ||
other.resultCoin == resultCoin) && other.resultCoin == resultCoin) &&
(identical(other.currentStreak, currentStreak) ||
other.currentStreak == currentStreak) &&
const DeepCollectionEquality() const DeepCollectionEquality()
.equals(other.resultModifiers, resultModifiers) && .equals(other.resultModifiers, resultModifiers) &&
(identical(other.accountId, accountId) || (identical(other.accountId, accountId) ||
@ -71,12 +74,13 @@ mixin _$SnCheckInRecord {
resultTier, resultTier,
resultExperience, resultExperience,
resultCoin, resultCoin,
currentStreak,
const DeepCollectionEquality().hash(resultModifiers), const DeepCollectionEquality().hash(resultModifiers),
accountId); accountId);
@override @override
String toString() { String toString() {
return 'SnCheckInRecord(id: $id, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt, resultTier: $resultTier, resultExperience: $resultExperience, resultCoin: $resultCoin, resultModifiers: $resultModifiers, accountId: $accountId)'; return 'SnCheckInRecord(id: $id, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt, resultTier: $resultTier, resultExperience: $resultExperience, resultCoin: $resultCoin, currentStreak: $currentStreak, resultModifiers: $resultModifiers, accountId: $accountId)';
} }
} }
@ -94,6 +98,7 @@ abstract mixin class $SnCheckInRecordCopyWith<$Res> {
int resultTier, int resultTier,
int resultExperience, int resultExperience,
double resultCoin, double resultCoin,
int currentStreak,
List<int> resultModifiers, List<int> resultModifiers,
int accountId}); int accountId});
} }
@ -118,6 +123,7 @@ class _$SnCheckInRecordCopyWithImpl<$Res>
Object? resultTier = null, Object? resultTier = null,
Object? resultExperience = null, Object? resultExperience = null,
Object? resultCoin = null, Object? resultCoin = null,
Object? currentStreak = null,
Object? resultModifiers = null, Object? resultModifiers = null,
Object? accountId = null, Object? accountId = null,
}) { }) {
@ -150,6 +156,10 @@ class _$SnCheckInRecordCopyWithImpl<$Res>
? _self.resultCoin ? _self.resultCoin
: resultCoin // ignore: cast_nullable_to_non_nullable : resultCoin // ignore: cast_nullable_to_non_nullable
as double, as double,
currentStreak: null == currentStreak
? _self.currentStreak
: currentStreak // ignore: cast_nullable_to_non_nullable
as int,
resultModifiers: null == resultModifiers resultModifiers: null == resultModifiers
? _self.resultModifiers ? _self.resultModifiers
: resultModifiers // ignore: cast_nullable_to_non_nullable : resultModifiers // ignore: cast_nullable_to_non_nullable
@ -173,6 +183,7 @@ class _SnCheckInRecord extends SnCheckInRecord {
required this.resultTier, required this.resultTier,
required this.resultExperience, required this.resultExperience,
required this.resultCoin, required this.resultCoin,
this.currentStreak = 0,
required final List<int> resultModifiers, required final List<int> resultModifiers,
required this.accountId}) required this.accountId})
: _resultModifiers = resultModifiers, : _resultModifiers = resultModifiers,
@ -194,6 +205,9 @@ class _SnCheckInRecord extends SnCheckInRecord {
final int resultExperience; final int resultExperience;
@override @override
final double resultCoin; final double resultCoin;
@override
@JsonKey()
final int currentStreak;
final List<int> _resultModifiers; final List<int> _resultModifiers;
@override @override
List<int> get resultModifiers { List<int> get resultModifiers {
@ -238,6 +252,8 @@ class _SnCheckInRecord extends SnCheckInRecord {
other.resultExperience == resultExperience) && other.resultExperience == resultExperience) &&
(identical(other.resultCoin, resultCoin) || (identical(other.resultCoin, resultCoin) ||
other.resultCoin == resultCoin) && other.resultCoin == resultCoin) &&
(identical(other.currentStreak, currentStreak) ||
other.currentStreak == currentStreak) &&
const DeepCollectionEquality() const DeepCollectionEquality()
.equals(other._resultModifiers, _resultModifiers) && .equals(other._resultModifiers, _resultModifiers) &&
(identical(other.accountId, accountId) || (identical(other.accountId, accountId) ||
@ -255,12 +271,13 @@ class _SnCheckInRecord extends SnCheckInRecord {
resultTier, resultTier,
resultExperience, resultExperience,
resultCoin, resultCoin,
currentStreak,
const DeepCollectionEquality().hash(_resultModifiers), const DeepCollectionEquality().hash(_resultModifiers),
accountId); accountId);
@override @override
String toString() { String toString() {
return 'SnCheckInRecord(id: $id, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt, resultTier: $resultTier, resultExperience: $resultExperience, resultCoin: $resultCoin, resultModifiers: $resultModifiers, accountId: $accountId)'; return 'SnCheckInRecord(id: $id, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt, resultTier: $resultTier, resultExperience: $resultExperience, resultCoin: $resultCoin, currentStreak: $currentStreak, resultModifiers: $resultModifiers, accountId: $accountId)';
} }
} }
@ -280,6 +297,7 @@ abstract mixin class _$SnCheckInRecordCopyWith<$Res>
int resultTier, int resultTier,
int resultExperience, int resultExperience,
double resultCoin, double resultCoin,
int currentStreak,
List<int> resultModifiers, List<int> resultModifiers,
int accountId}); int accountId});
} }
@ -304,6 +322,7 @@ class __$SnCheckInRecordCopyWithImpl<$Res>
Object? resultTier = null, Object? resultTier = null,
Object? resultExperience = null, Object? resultExperience = null,
Object? resultCoin = null, Object? resultCoin = null,
Object? currentStreak = null,
Object? resultModifiers = null, Object? resultModifiers = null,
Object? accountId = null, Object? accountId = null,
}) { }) {
@ -336,6 +355,10 @@ class __$SnCheckInRecordCopyWithImpl<$Res>
? _self.resultCoin ? _self.resultCoin
: resultCoin // ignore: cast_nullable_to_non_nullable : resultCoin // ignore: cast_nullable_to_non_nullable
as double, as double,
currentStreak: null == currentStreak
? _self.currentStreak
: currentStreak // ignore: cast_nullable_to_non_nullable
as int,
resultModifiers: null == resultModifiers resultModifiers: null == resultModifiers
? _self._resultModifiers ? _self._resultModifiers
: resultModifiers // ignore: cast_nullable_to_non_nullable : resultModifiers // ignore: cast_nullable_to_non_nullable

View File

@ -17,6 +17,7 @@ _SnCheckInRecord _$SnCheckInRecordFromJson(Map<String, dynamic> json) =>
resultTier: (json['result_tier'] as num).toInt(), resultTier: (json['result_tier'] as num).toInt(),
resultExperience: (json['result_experience'] as num).toInt(), resultExperience: (json['result_experience'] as num).toInt(),
resultCoin: (json['result_coin'] as num).toDouble(), resultCoin: (json['result_coin'] as num).toDouble(),
currentStreak: (json['current_streak'] as num?)?.toInt() ?? 0,
resultModifiers: (json['result_modifiers'] as List<dynamic>) resultModifiers: (json['result_modifiers'] as List<dynamic>)
.map((e) => (e as num).toInt()) .map((e) => (e as num).toInt())
.toList(), .toList(),
@ -32,6 +33,7 @@ Map<String, dynamic> _$SnCheckInRecordToJson(_SnCheckInRecord instance) =>
'result_tier': instance.resultTier, 'result_tier': instance.resultTier,
'result_experience': instance.resultExperience, 'result_experience': instance.resultExperience,
'result_coin': instance.resultCoin, 'result_coin': instance.resultCoin,
'current_streak': instance.currentStreak,
'result_modifiers': instance.resultModifiers, 'result_modifiers': instance.resultModifiers,
'account_id': instance.accountId, 'account_id': instance.accountId,
}; };

View File

@ -13,6 +13,7 @@ class AccountImage extends StatelessWidget {
final double? borderRadius; final double? borderRadius;
final Widget? fallbackWidget; final Widget? fallbackWidget;
final Widget? badge; final Widget? badge;
final Offset? badgeOffset;
const AccountImage({ const AccountImage({
super.key, super.key,
@ -23,6 +24,7 @@ class AccountImage extends StatelessWidget {
this.borderRadius, this.borderRadius,
this.fallbackWidget, this.fallbackWidget,
this.badge, this.badge,
this.badgeOffset,
}); });
@override @override
@ -40,7 +42,8 @@ class AccountImage extends StatelessWidget {
borderRadius: BorderRadius.circular(borderRadius ?? radius ?? 20), borderRadius: BorderRadius.circular(borderRadius ?? radius ?? 20),
child: (content?.isEmpty ?? true) child: (content?.isEmpty ?? true)
? Container( ? Container(
color: backgroundColor ?? Theme.of(context).colorScheme.primaryContainer, color: backgroundColor ??
Theme.of(context).colorScheme.primaryContainer,
child: (fallbackWidget ?? child: (fallbackWidget ??
Icon( Icon(
Symbols.account_circle, Symbols.account_circle,
@ -58,8 +61,8 @@ class AccountImage extends StatelessWidget {
), ),
if (badge != null) if (badge != null)
Positioned( Positioned(
right: -4, right: badgeOffset?.dx ?? -4,
bottom: -2, bottom: badgeOffset?.dy ?? -2,
child: badge!, child: badge!,
), ),
], ],

View File

@ -8,9 +8,9 @@ import 'package:relative_time/relative_time.dart';
import 'package:styled_widget/styled_widget.dart'; import 'package:styled_widget/styled_widget.dart';
import 'package:surface/providers/experience.dart'; import 'package:surface/providers/experience.dart';
import 'package:surface/providers/sn_network.dart'; import 'package:surface/providers/sn_network.dart';
import 'package:surface/screens/account/profile_page.dart';
import 'package:surface/types/account.dart'; import 'package:surface/types/account.dart';
import 'package:surface/widgets/account/account_image.dart'; import 'package:surface/widgets/account/account_image.dart';
import 'package:surface/widgets/account/badge.dart';
import 'package:surface/widgets/universal_image.dart'; import 'package:surface/widgets/universal_image.dart';
class AccountPopoverCard extends StatelessWidget { class AccountPopoverCard extends StatelessWidget {
@ -72,37 +72,21 @@ class AccountPopoverCard extends StatelessWidget {
const Gap(8) const Gap(8)
], ],
).padding(horizontal: 16), ).padding(horizontal: 16),
if (data.badges.isNotEmpty) const Gap(12),
if (data.badges.isNotEmpty) if (data.badges.isNotEmpty)
Wrap( Wrap(
spacing: 4, spacing: 4,
children: data.badges children: data.badges
.map( .map(
(ele) => Tooltip( (ele) => AccountBadge(badge: ele),
richMessage: TextSpan(
children: [
TextSpan(text: kBadgesMeta[ele.type]?.$1.tr() ?? 'unknown'.tr()),
if (ele.metadata['title'] != null)
TextSpan(
text: '\n${ele.metadata['title']}',
style: const TextStyle(fontWeight: FontWeight.bold),
),
TextSpan(text: '\n'),
TextSpan(
text: DateFormat.yMEd().format(ele.createdAt),
),
],
),
child: Icon(
kBadgesMeta[ele.type]?.$2 ?? Symbols.question_mark,
color: kBadgesMeta[ele.type]?.$3,
fill: 1,
),
),
) )
.toList(), .toList(),
).padding(horizontal: 24), ).padding(horizontal: 24, bottom: 12, top: 12),
const Gap(8), if (data.profile?.description.isNotEmpty ?? false)
Text(
data.profile?.description ?? '',
maxLines: 2,
overflow: TextOverflow.ellipsis,
).padding(horizontal: 26, bottom: 8),
Row( Row(
crossAxisAlignment: CrossAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center,
children: [ children: [
@ -110,7 +94,9 @@ class AccountPopoverCard extends StatelessWidget {
const Gap(8), const Gap(8),
Text('Lv${getLevelFromExp(data.profile?.experience ?? 0)}'), Text('Lv${getLevelFromExp(data.profile?.experience ?? 0)}'),
const Gap(8), const Gap(8),
Text(calcLevelUpProgressLevel(data.profile?.experience ?? 0)).fontSize(11).opacity(0.5), Text(calcLevelUpProgressLevel(data.profile?.experience ?? 0))
.fontSize(11)
.opacity(0.5),
const Gap(8), const Gap(8),
Container( Container(
width: double.infinity, width: double.infinity,
@ -126,25 +112,36 @@ class AccountPopoverCard extends StatelessWidget {
FutureBuilder( FutureBuilder(
future: sn.client.get('/cgi/id/users/${data.name}/status'), future: sn.client.get('/cgi/id/users/${data.name}/status'),
builder: (context, snapshot) { builder: (context, snapshot) {
final SnAccountStatusInfo? status = final SnAccountStatusInfo? status = snapshot.hasData
snapshot.hasData ? SnAccountStatusInfo.fromJson(snapshot.data!.data) : null; ? SnAccountStatusInfo.fromJson(snapshot.data!.data)
: null;
return Row( return Row(
children: [ children: [
Icon( Icon(
Symbols.circle, (status?.isDisturbable ?? true)
fill: 1, ? Symbols.circle
: Symbols.do_not_disturb_on,
fill: (status?.isOnline ?? false) ? 1 : 0,
size: 16, size: 16,
color: (status?.isOnline ?? false) ? Colors.green : Colors.grey, color: (status?.isOnline ?? false)
? (status?.isDisturbable ?? true)
? Colors.green
: Colors.red
: Colors.grey,
).padding(all: 4), ).padding(all: 4),
const Gap(8), const Gap(8),
Text( Text(
status != null status != null
? status.isOnline ? (status.status?.label.isNotEmpty ?? false)
? 'accountStatusOnline'.tr() ? status.status!.label
: 'accountStatusOffline'.tr() : status.isOnline
? 'accountStatusOnline'.tr()
: 'accountStatusOffline'.tr()
: 'loading'.tr(), : 'loading'.tr(),
), ),
if (status != null && !status.isOnline && status.lastSeenAt != null) if (status != null &&
!status.isOnline &&
status.lastSeenAt != null)
Text( Text(
'accountStatusLastSeen'.tr(args: [ 'accountStatusLastSeen'.tr(args: [
status.lastSeenAt != null status.lastSeenAt != null

View File

@ -0,0 +1,391 @@
import 'package:dio/dio.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:gap/gap.dart';
import 'package:material_symbols_icons/symbols.dart';
import 'package:provider/provider.dart';
import 'package:styled_widget/styled_widget.dart';
import 'package:surface/providers/sn_network.dart';
import 'package:surface/types/account.dart';
import 'package:surface/widgets/dialog.dart';
import 'package:surface/widgets/loading_indicator.dart';
final Map<String, (Widget, String, String?)> kPresetStatus = {
'online': (
const Icon(Symbols.circle, color: Colors.green, fill: 1),
'accountStatusOnline'.tr(),
null,
),
'silent': (
const Icon(Symbols.do_not_disturb_on, color: Colors.red),
'accountStatusSilent'.tr(),
'accountStatusSilentDesc'.tr(),
),
'invisible': (
const Icon(Symbols.circle, color: Colors.grey),
'accountStatusInvisible'.tr(),
'accountStatusInvisibleDesc'.tr(),
),
};
class AccountStatusActionPopup extends StatefulWidget {
final SnAccountStatusInfo? currentStatus;
const AccountStatusActionPopup({super.key, this.currentStatus});
@override
State<AccountStatusActionPopup> createState() =>
_AccountStatusActionPopupState();
}
class _AccountStatusActionPopupState extends State<AccountStatusActionPopup> {
bool _isBusy = false;
Future<void> setStatus(
String type,
String? label,
int attitude, {
bool isUpdate = false,
bool isSilent = false,
bool isInvisible = false,
DateTime? clearAt,
}) async {
setState(() => _isBusy = true);
final sn = context.read<SnNetworkProvider>();
final payload = {
'type': type,
'label': label,
'attitude': attitude,
'is_no_disturb': isSilent,
'is_invisible': isInvisible,
'clear_at': clearAt?.toUtc().toIso8601String()
};
try {
await sn.client.request(
'/cgi/id/users/me/status',
data: payload,
options: Options(method: isUpdate ? 'PUT' : 'POST'),
);
if (!mounted) return;
Navigator.pop(context, true);
} catch (err) {
if (!mounted) return;
context.showErrorDialog(err);
} finally {
setState(() => _isBusy = false);
}
}
Future<void> _clearStatus() async {
if (_isBusy) return;
setState(() => _isBusy = true);
try {
final sn = context.read<SnNetworkProvider>();
await sn.client.delete('/cgi/id/users/me/status');
if (!mounted) return;
Navigator.of(context).pop(true);
} catch (err) {
if (!mounted) return;
context.showErrorDialog(err);
} finally {
setState(() => _isBusy = false);
}
}
@override
Widget build(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
const Icon(Symbols.mood, size: 24),
const Gap(16),
Text('accountChangeStatus',
style: Theme.of(context).textTheme.titleLarge)
.tr(),
],
).padding(horizontal: 20, top: 16, bottom: 12),
LoadingIndicator(isActive: _isBusy),
SizedBox(
height: 48,
child: ListView(
padding: EdgeInsets.symmetric(horizontal: 18),
scrollDirection: Axis.horizontal,
children: kPresetStatus.entries
.map(
(x) => StyledWidget(ActionChip(
avatar: x.value.$1,
label: Text(x.value.$2),
tooltip: x.value.$3,
onPressed: _isBusy
? null
: () {
setStatus(
x.key,
x.value.$2,
0,
isInvisible: x.key == 'invisible',
isSilent: x.key == 'silent',
);
},
)).padding(right: 6),
)
.toList(),
),
),
const Gap(16),
const Divider(thickness: 0.3, height: 0.3),
ListTile(
contentPadding: const EdgeInsets.symmetric(horizontal: 24),
leading: widget.currentStatus != null
? const Icon(Icons.edit)
: const Icon(Icons.add),
title: Text('accountCustomStatus').tr(),
subtitle: Text('accountCustomStatusDescription').tr(),
onTap: _isBusy
? null
: () async {
final val = await showDialog(
context: context,
builder: (context) => _AccountStatusEditorDialog(
currentStatus: widget.currentStatus,
),
);
if (val == true && context.mounted) {
Navigator.of(context).pop(true);
}
},
),
if (widget.currentStatus != null)
ListTile(
contentPadding: const EdgeInsets.symmetric(horizontal: 24),
leading: const Icon(Icons.clear),
title: Text('accountClearStatus').tr(),
subtitle: Text('accountClearStatusDescription').tr(),
onTap: _isBusy
? null
: () {
_clearStatus();
},
),
],
);
}
}
class _AccountStatusEditorDialog extends StatefulWidget {
final SnAccountStatusInfo? currentStatus;
const _AccountStatusEditorDialog({this.currentStatus});
@override
State<_AccountStatusEditorDialog> createState() =>
_AccountStatusEditorDialogState();
}
class _AccountStatusEditorDialogState
extends State<_AccountStatusEditorDialog> {
bool _isBusy = false;
final TextEditingController _labelController = TextEditingController();
final TextEditingController _clearAtController = TextEditingController();
int _attitude = 0;
bool _isSilent = false;
bool _isInvisible = false;
DateTime? _clearAt;
Future<void> _selectClearAt() async {
final DateTime? pickedDate = await showDatePicker(
context: context,
initialDate: _clearAt?.toLocal() ?? DateTime.now(),
firstDate: DateTime.now(),
lastDate: DateTime.now().add(const Duration(days: 365)),
);
if (pickedDate == null) return;
if (!mounted) return;
final TimeOfDay? pickedTime = await showTimePicker(
context: context,
initialTime: TimeOfDay.now(),
);
if (pickedTime == null) return;
if (!mounted) return;
final picked = pickedDate.copyWith(
hour: pickedTime.hour,
minute: pickedTime.minute,
);
setState(() {
_clearAt = picked;
_clearAtController.text = DateFormat('y/M/d HH:mm').format(_clearAt!);
});
}
Future<void> _applyStatus() async {
if (_isBusy) return;
setState(() => _isBusy = true);
try {
final sn = context.read<SnNetworkProvider>();
await sn.client.request(
'/cgi/id/users/me/status',
data: {
'type': 'custom',
'label': _labelController.text,
'attitude': _attitude,
'is_no_disturb': _isSilent,
'is_invisible': _isInvisible,
'clear_at': _clearAt?.toUtc().toIso8601String(),
},
options: Options(
method: widget.currentStatus?.status != null ? 'PUT' : 'POST',
),
);
if (!mounted) return;
Navigator.of(context).pop(true);
} catch (err) {
if (!mounted) return;
context.showErrorDialog(err);
} finally {
setState(() => _isBusy = false);
}
}
void _syncWidget() {
if (widget.currentStatus?.status != null) {
_clearAt = widget.currentStatus!.status!.clearAt;
if (_clearAt != null) {
_clearAtController.text = DateFormat('y/M/d HH:mm').format(_clearAt!);
}
_labelController.text = widget.currentStatus!.status!.label;
_attitude = widget.currentStatus!.status!.attitude;
_isInvisible = widget.currentStatus!.status!.isInvisible;
_isSilent = widget.currentStatus!.status!.isNoDisturb;
}
}
@override
void initState() {
_syncWidget();
super.initState();
}
@override
Widget build(BuildContext context) {
return AlertDialog(
title: Text('accountCustomStatus').tr(),
content: Column(
mainAxisSize: MainAxisSize.min,
children: [
LoadingIndicator(isActive: _isBusy),
TextField(
controller: _labelController,
decoration: InputDecoration(
isDense: true,
prefixIcon: const Icon(Icons.label),
border: const OutlineInputBorder(),
labelText: 'fieldAccountStatusLabel'.tr(),
),
onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(),
),
const Gap(8),
TextField(
controller: _clearAtController,
readOnly: true,
decoration: InputDecoration(
isDense: true,
prefixIcon: const Icon(Icons.event_busy),
border: const OutlineInputBorder(),
labelText: 'fieldAccountStatusClearAt'.tr(),
),
onTap: () => _selectClearAt(),
),
const Gap(8),
SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: Wrap(
spacing: 6,
runSpacing: 0,
children: [
ChoiceChip(
avatar: Icon(
Symbols.radio_button_unchecked,
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
selected: _attitude == 2,
label: Text('accountStatusNegative'.tr()),
onSelected: (val) {
if (val) setState(() => _attitude = 2);
},
),
ChoiceChip(
avatar: Icon(
Symbols.contrast,
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
selected: _attitude == 0,
label: Text('accountStatusNeutral'.tr()),
onSelected: (val) {
if (val) setState(() => _attitude = 0);
},
),
ChoiceChip(
avatar: Icon(
Symbols.circle,
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
selected: _attitude == 1,
label: Text('accountStatusPositive'.tr()),
onSelected: (val) {
if (val) setState(() => _attitude = 1);
},
),
],
),
),
const Gap(4),
SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: Wrap(
spacing: 6,
runSpacing: 0,
children: [
ChoiceChip(
selected: _isSilent,
label: Text('accountStatusSilent').tr(),
onSelected: (val) {
setState(() => _isSilent = val);
},
),
ChoiceChip(
selected: _isInvisible,
label: Text('accountStatusInvisible').tr(),
onSelected: (val) {
setState(() => _isInvisible = val);
},
),
],
),
),
],
),
actions: <Widget>[
TextButton(
style: TextButton.styleFrom(
foregroundColor:
Theme.of(context).colorScheme.onSurface.withOpacity(0.8),
),
onPressed: _isBusy ? null : () => Navigator.pop(context),
child: Text('dialogCancel').tr(),
),
TextButton(
onPressed: _isBusy ? null : () => _applyStatus(),
child: Text('dialogConfirm').tr(),
),
],
);
}
}

View File

@ -0,0 +1,50 @@
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:material_symbols_icons/symbols.dart';
import 'package:surface/screens/account/profile_page.dart' show kBadgesMeta;
import 'package:surface/types/account.dart';
class AccountBadge extends StatelessWidget {
final SnAccountBadge badge;
final double radius;
final EdgeInsets? padding;
const AccountBadge({
super.key,
required this.badge,
this.radius = 20,
this.padding,
});
@override
Widget build(BuildContext context) {
return Tooltip(
richMessage: TextSpan(
children: [
TextSpan(text: kBadgesMeta[badge.type]?.$1.tr() ?? 'unknown'.tr()),
if (badge.metadata['title'] != null)
TextSpan(
text: '\n${badge.metadata['title']}',
style: const TextStyle(fontWeight: FontWeight.bold),
),
TextSpan(text: '\n'),
TextSpan(
text: DateFormat.yMEd().format(badge.createdAt),
),
],
),
child: Container(
padding: padding ?? EdgeInsets.all(3),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(radius),
color: kBadgesMeta[badge.type]?.$3,
),
child: Icon(
kBadgesMeta[badge.type]?.$2 ?? Symbols.question_mark,
color: Colors.white,
fill: 1,
size: radius - 4,
),
),
);
}
}

View File

@ -22,12 +22,14 @@ class AttachmentItem extends StatelessWidget {
final SnAttachment? data; final SnAttachment? data;
final String? heroTag; final String? heroTag;
final BoxFit fit; final BoxFit fit;
final FilterQuality? filterQuality;
const AttachmentItem({ const AttachmentItem({
super.key, super.key,
this.fit = BoxFit.cover, this.fit = BoxFit.cover,
required this.data, required this.data,
required this.heroTag, required this.heroTag,
this.filterQuality,
}); });
Widget _buildContent(BuildContext context) { Widget _buildContent(BuildContext context) {
@ -47,6 +49,7 @@ class AttachmentItem extends StatelessWidget {
sn.getAttachmentUrl(data!.rid), sn.getAttachmentUrl(data!.rid),
key: Key('attachment-${data!.rid}-$tag'), key: Key('attachment-${data!.rid}-$tag'),
fit: fit, fit: fit,
filterQuality: filterQuality,
), ),
); );
case 'video': case 'video':
@ -83,13 +86,16 @@ class _AttachmentItemSensitiveBlur extends StatefulWidget {
final Widget child; final Widget child;
final bool isCompact; final bool isCompact;
const _AttachmentItemSensitiveBlur({required this.child, this.isCompact = false}); const _AttachmentItemSensitiveBlur(
{required this.child, this.isCompact = false});
@override @override
State<_AttachmentItemSensitiveBlur> createState() => _AttachmentItemSensitiveBlurState(); State<_AttachmentItemSensitiveBlur> createState() =>
_AttachmentItemSensitiveBlurState();
} }
class _AttachmentItemSensitiveBlurState extends State<_AttachmentItemSensitiveBlur> { class _AttachmentItemSensitiveBlurState
extends State<_AttachmentItemSensitiveBlur> {
bool _doesShow = false; bool _doesShow = false;
@override @override
@ -124,10 +130,15 @@ class _AttachmentItemSensitiveBlurState extends State<_AttachmentItemSensitiveBl
Text( Text(
'sensitiveContentDescription', 'sensitiveContentDescription',
textAlign: TextAlign.center, textAlign: TextAlign.center,
).tr().fontSize(14).textColor(Colors.white.withOpacity(0.8)), )
.tr()
.fontSize(14)
.textColor(Colors.white.withOpacity(0.8)),
if (!widget.isCompact) const Gap(16), if (!widget.isCompact) const Gap(16),
InkWell( InkWell(
child: Text('sensitiveContentReveal').tr().textColor(Colors.white), child: Text('sensitiveContentReveal')
.tr()
.textColor(Colors.white),
onTap: () { onTap: () {
setState(() => _doesShow = !_doesShow); setState(() => _doesShow = !_doesShow);
}, },
@ -137,7 +148,9 @@ class _AttachmentItemSensitiveBlurState extends State<_AttachmentItemSensitiveBl
).center(), ).center(),
), ),
), ),
).opacity(_doesShow ? 0 : 1, animate: true).animate(const Duration(milliseconds: 300), Curves.easeInOut), )
.opacity(_doesShow ? 0 : 1, animate: true)
.animate(const Duration(milliseconds: 300), Curves.easeInOut),
if (_doesShow) if (_doesShow)
Positioned( Positioned(
top: 0, top: 0,
@ -174,10 +187,12 @@ class _AttachmentItemContentVideo extends StatefulWidget {
}); });
@override @override
State<_AttachmentItemContentVideo> createState() => _AttachmentItemContentVideoState(); State<_AttachmentItemContentVideo> createState() =>
_AttachmentItemContentVideoState();
} }
class _AttachmentItemContentVideoState extends State<_AttachmentItemContentVideo> { class _AttachmentItemContentVideoState
extends State<_AttachmentItemContentVideo> {
bool _showContent = false; bool _showContent = false;
bool _showOriginal = false; bool _showOriginal = false;
@ -188,7 +203,9 @@ class _AttachmentItemContentVideoState extends State<_AttachmentItemContentVideo
setState(() => _showContent = true); setState(() => _showContent = true);
MediaKit.ensureInitialized(); MediaKit.ensureInitialized();
final sn = context.read<SnNetworkProvider>(); final sn = context.read<SnNetworkProvider>();
final url = _showOriginal ? sn.getAttachmentUrl(widget.data.rid) : sn.getAttachmentUrl(widget.data.compressed!.rid); final url = _showOriginal
? sn.getAttachmentUrl(widget.data.rid)
: sn.getAttachmentUrl(widget.data.compressed!.rid);
_videoPlayer = Player(); _videoPlayer = Player();
_videoController = VideoController(_videoPlayer!); _videoController = VideoController(_videoPlayer!);
_videoPlayer!.open(Media(url), play: !widget.isAutoload); _videoPlayer!.open(Media(url), play: !widget.isAutoload);
@ -201,7 +218,9 @@ class _AttachmentItemContentVideoState extends State<_AttachmentItemContentVideo
final sn = context.read<SnNetworkProvider>(); final sn = context.read<SnNetworkProvider>();
_videoPlayer?.open( _videoPlayer?.open(
Media( Media(
_showOriginal ? sn.getAttachmentUrl(widget.data.rid) : sn.getAttachmentUrl(widget.data.compressed!.rid), _showOriginal
? sn.getAttachmentUrl(widget.data.rid)
: sn.getAttachmentUrl(widget.data.compressed!.rid),
), ),
play: true, play: true,
); );
@ -283,7 +302,9 @@ class _AttachmentItemContentVideoState extends State<_AttachmentItemContentVideo
), ),
Text( Text(
Duration( Duration(
milliseconds: (widget.data.data['duration'] ?? 0).toInt() * 1000, milliseconds:
(widget.data.data['duration'] ?? 0).toInt() *
1000,
).toString(), ).toString(),
style: GoogleFonts.robotoMono( style: GoogleFonts.robotoMono(
fontSize: 12, fontSize: 12,
@ -346,7 +367,9 @@ class _AttachmentItemContentVideoState extends State<_AttachmentItemContentVideo
MaterialDesktopCustomButton( MaterialDesktopCustomButton(
iconSize: 24, iconSize: 24,
onPressed: _toggleOriginal, onPressed: _toggleOriginal,
icon: _showOriginal ? const Icon(Symbols.high_quality, size: 24) : const Icon(Symbols.sd, size: 24), icon: _showOriginal
? const Icon(Symbols.high_quality, size: 24)
: const Icon(Symbols.sd, size: 24),
), ),
], ],
), ),
@ -354,8 +377,9 @@ class _AttachmentItemContentVideoState extends State<_AttachmentItemContentVideo
child: Video( child: Video(
controller: _videoController!, controller: _videoController!,
aspectRatio: ratio, aspectRatio: ratio,
controls: controls: !kIsWeb && (Platform.isAndroid || Platform.isIOS)
!kIsWeb && (Platform.isAndroid || Platform.isIOS) ? MaterialVideoControls : MaterialDesktopVideoControls, ? MaterialVideoControls
: MaterialDesktopVideoControls,
), ),
), ),
); );
@ -378,10 +402,12 @@ class _AttachmentItemContentAudio extends StatefulWidget {
}); });
@override @override
State<_AttachmentItemContentAudio> createState() => _AttachmentItemContentAudioState(); State<_AttachmentItemContentAudio> createState() =>
_AttachmentItemContentAudioState();
} }
class _AttachmentItemContentAudioState extends State<_AttachmentItemContentAudio> { class _AttachmentItemContentAudioState
extends State<_AttachmentItemContentAudio> {
bool _showContent = false; bool _showContent = false;
double? _draggingValue; double? _draggingValue;
@ -552,8 +578,12 @@ class _AttachmentItemContentAudioState extends State<_AttachmentItemContentAudio
overlayShape: SliderComponentShape.noOverlay, overlayShape: SliderComponentShape.noOverlay,
), ),
child: Slider( child: Slider(
secondaryTrackValue: _bufferedPosition.inMilliseconds.abs().toDouble(), secondaryTrackValue: _bufferedPosition
value: _draggingValue?.abs() ?? _position.inMilliseconds.toDouble().abs(), .inMilliseconds
.abs()
.toDouble(),
value: _draggingValue?.abs() ??
_position.inMilliseconds.toDouble().abs(),
min: 0, min: 0,
max: math max: math
.max( .max(
@ -593,7 +623,9 @@ class _AttachmentItemContentAudioState extends State<_AttachmentItemContentAudio
), ),
const Gap(16), const Gap(16),
IconButton.filled( IconButton.filled(
icon: _isPlaying ? const Icon(Symbols.pause) : const Icon(Symbols.play_arrow), icon: _isPlaying
? const Icon(Symbols.pause)
: const Icon(Symbols.play_arrow),
onPressed: () { onPressed: () {
_audioPlayer!.playOrPause(); _audioPlayer!.playOrPause();
}, },

View File

@ -21,6 +21,7 @@ class AttachmentList extends StatefulWidget {
final double? minWidth; final double? minWidth;
final double? maxWidth; final double? maxWidth;
final EdgeInsets? padding; final EdgeInsets? padding;
final FilterQuality? filterQuality;
const AttachmentList({ const AttachmentList({
super.key, super.key,
@ -33,23 +34,27 @@ class AttachmentList extends StatefulWidget {
this.minWidth, this.minWidth,
this.maxWidth, this.maxWidth,
this.padding, this.padding,
this.filterQuality,
}); });
static const BorderRadius kDefaultRadius = BorderRadius.all(Radius.circular(8)); static const BorderRadius kDefaultRadius =
BorderRadius.all(Radius.circular(8));
@override @override
State<AttachmentList> createState() => _AttachmentListState(); State<AttachmentList> createState() => _AttachmentListState();
} }
class _AttachmentListState extends State<AttachmentList> { class _AttachmentListState extends State<AttachmentList> {
late final List<String> heroTags = List.generate(widget.data.length, (_) => const Uuid().v4()); late final List<String> heroTags =
List.generate(widget.data.length, (_) => const Uuid().v4());
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return LayoutBuilder( return LayoutBuilder(
builder: (context, layoutConstraints) { builder: (context, layoutConstraints) {
final borderSide = final borderSide = widget.bordered
widget.bordered ? BorderSide(width: 1, color: Theme.of(context).dividerColor) : BorderSide.none; ? BorderSide(width: 1, color: Theme.of(context).dividerColor)
: BorderSide.none;
final backgroundColor = Theme.of(context).colorScheme.surfaceContainer; final backgroundColor = Theme.of(context).colorScheme.surfaceContainer;
final constraints = BoxConstraints( final constraints = BoxConstraints(
minWidth: widget.minWidth ?? 80, minWidth: widget.minWidth ?? 80,
@ -58,13 +63,13 @@ class _AttachmentListState extends State<AttachmentList> {
if (widget.data.isEmpty) return const SizedBox.shrink(); if (widget.data.isEmpty) return const SizedBox.shrink();
if (widget.data.length == 1) { if (widget.data.length == 1) {
final singleAspectRatio = final singleAspectRatio = widget.data[0]?.data['ratio']?.toDouble() ??
widget.data[0]?.data['ratio']?.toDouble() ??
switch (widget.data[0]?.mimetype.split('/').firstOrNull) { switch (widget.data[0]?.mimetype.split('/').firstOrNull) {
'audio' => 16 / 9, 'audio' => 16 / 9,
'video' => 16 / 9, 'video' => 16 / 9,
_ => 1, _ => 1,
}.toDouble(); }
.toDouble();
return Container( return Container(
padding: widget.padding ?? EdgeInsets.zero, padding: widget.padding ?? EdgeInsets.zero,
@ -80,12 +85,18 @@ class _AttachmentListState extends State<AttachmentList> {
), ),
child: ClipRRect( child: ClipRRect(
borderRadius: AttachmentList.kDefaultRadius, borderRadius: AttachmentList.kDefaultRadius,
child: AttachmentItem(data: widget.data[0], heroTag: heroTags[0], fit: widget.fit), child: AttachmentItem(
data: widget.data[0],
heroTag: heroTags[0],
fit: widget.fit,
filterQuality: widget.filterQuality,
),
), ),
), ),
), ),
onTap: () { onTap: () {
if (widget.data.firstOrNull?.mediaType != SnMediaType.image) return; if (widget.data.firstOrNull?.mediaType != SnMediaType.image)
return;
context.pushTransparentRoute( context.pushTransparentRoute(
AttachmentZoomView( AttachmentZoomView(
data: widget.data.where((ele) => ele != null).cast(), data: widget.data.where((ele) => ele != null).cast(),
@ -100,8 +111,10 @@ class _AttachmentListState extends State<AttachmentList> {
); );
} }
final fullOfImage = final fullOfImage = widget.data
widget.data.where((ele) => ele?.mediaType == SnMediaType.image).length == widget.data.length; .where((ele) => ele?.mediaType == SnMediaType.image)
.length ==
widget.data.length;
if (widget.gridded && fullOfImage) { if (widget.gridded && fullOfImage) {
return Container( return Container(
@ -117,29 +130,38 @@ class _AttachmentListState extends State<AttachmentList> {
crossAxisCount: math.min(widget.data.length, 2), crossAxisCount: math.min(widget.data.length, 2),
crossAxisSpacing: 4, crossAxisSpacing: 4,
mainAxisSpacing: 4, mainAxisSpacing: 4,
children: children: widget.data
widget.data .mapIndexed(
.mapIndexed( (idx, ele) => GestureDetector(
(idx, ele) => GestureDetector( child: Container(
child: Container( constraints: constraints,
constraints: constraints, child: AttachmentItem(
child: AttachmentItem(data: ele, heroTag: heroTags[idx], fit: BoxFit.cover), data: ele,
), heroTag: heroTags[idx],
onTap: () { fit: BoxFit.cover,
if (widget.data[idx]!.mediaType != SnMediaType.image) return; filterQuality: widget.filterQuality,
context.pushTransparentRoute(
AttachmentZoomView(
data: widget.data.where((ele) => ele != null).cast(),
initialIndex: idx,
heroTags: heroTags,
),
backgroundColor: Colors.black.withOpacity(0.7),
rootNavigator: true,
);
},
), ),
) ),
.toList(), onTap: () {
if (widget.data[idx]!.mediaType !=
SnMediaType.image) {
return;
}
context.pushTransparentRoute(
AttachmentZoomView(
data: widget.data
.where((ele) => ele != null)
.cast(),
initialIndex: idx,
heroTags: heroTags,
),
backgroundColor: Colors.black.withOpacity(0.7),
rootNavigator: true,
);
},
),
)
.toList(),
), ),
), ),
); );
@ -156,22 +178,26 @@ class _AttachmentListState extends State<AttachmentList> {
child: ClipRRect( child: ClipRRect(
borderRadius: AttachmentList.kDefaultRadius, borderRadius: AttachmentList.kDefaultRadius,
child: Column( child: Column(
children: children: widget.data
widget.data .mapIndexed(
.mapIndexed( (idx, ele) => GestureDetector(
(idx, ele) => GestureDetector( child: AspectRatio(
child: AspectRatio( aspectRatio: ele?.data['ratio']?.toDouble() ?? 1,
aspectRatio: ele?.data['ratio']?.toDouble() ?? 1, child: Container(
child: Container( constraints: constraints,
constraints: constraints, child: AttachmentItem(
child: AttachmentItem(data: ele, heroTag: heroTags[idx], fit: BoxFit.cover), data: ele,
), heroTag: heroTags[idx],
fit: BoxFit.cover,
filterQuality: widget.filterQuality,
), ),
), ),
) ),
.expand((ele) => [ele, const Divider(height: 1)]) ),
.toList() )
..removeLast(), .expand((ele) => [ele, const Divider(height: 1)])
.toList()
..removeLast(),
), ),
), ),
); );
@ -179,6 +205,7 @@ class _AttachmentListState extends State<AttachmentList> {
return Container( return Container(
constraints: BoxConstraints(maxHeight: constraints.maxHeight), constraints: BoxConstraints(maxHeight: constraints.maxHeight),
width: double.infinity,
child: AspectRatio( child: AspectRatio(
aspectRatio: widget.data[0]?.data['ratio']?.toDouble() ?? 1, aspectRatio: widget.data[0]?.data['ratio']?.toDouble() ?? 1,
child: ScrollConfiguration( child: ScrollConfiguration(
@ -189,16 +216,22 @@ class _AttachmentListState extends State<AttachmentList> {
itemCount: widget.data.length, itemCount: widget.data.length,
itemBuilder: (context, idx) { itemBuilder: (context, idx) {
return Container( return Container(
constraints: constraints.copyWith(maxWidth: widget.maxWidth), constraints:
constraints.copyWith(maxWidth: widget.maxWidth),
child: AspectRatio( child: AspectRatio(
aspectRatio: (widget.data[idx]?.data['ratio'] ?? 1).toDouble(), aspectRatio:
(widget.data[idx]?.data['ratio'] ?? 1).toDouble(),
child: GestureDetector( child: GestureDetector(
onTap: () { onTap: () {
if (widget.data[idx]?.mediaType != SnMediaType.image) return; if (widget.data[idx]?.mediaType != SnMediaType.image)
return;
context.pushTransparentRoute( context.pushTransparentRoute(
AttachmentZoomView( AttachmentZoomView(
data: data: widget.data
widget.data.where((ele) => ele != null && ele.mediaType == SnMediaType.image).cast(), .where((ele) =>
ele != null &&
ele.mediaType == SnMediaType.image)
.cast(),
initialIndex: idx, initialIndex: idx,
heroTags: heroTags, heroTags: heroTags,
), ),
@ -212,18 +245,25 @@ class _AttachmentListState extends State<AttachmentList> {
Container( Container(
decoration: BoxDecoration( decoration: BoxDecoration(
color: backgroundColor, color: backgroundColor,
border: Border(top: borderSide, bottom: borderSide), border:
Border(top: borderSide, bottom: borderSide),
borderRadius: AttachmentList.kDefaultRadius, borderRadius: AttachmentList.kDefaultRadius,
), ),
child: ClipRRect( child: ClipRRect(
borderRadius: AttachmentList.kDefaultRadius, borderRadius: AttachmentList.kDefaultRadius,
child: AttachmentItem(data: widget.data[idx], heroTag: heroTags[idx]), child: AttachmentItem(
data: widget.data[idx],
heroTag: heroTags[idx],
filterQuality: widget.filterQuality,
),
), ),
), ),
Positioned( Positioned(
right: 8, right: 8,
bottom: 8, bottom: 8,
child: Chip(label: Text('${idx + 1}/${widget.data.length}')), child: Chip(
label:
Text('${idx + 1}/${widget.data.length}')),
), ),
], ],
), ),
@ -245,5 +285,6 @@ class _AttachmentListState extends State<AttachmentList> {
class _AttachmentListScrollBehavior extends MaterialScrollBehavior { class _AttachmentListScrollBehavior extends MaterialScrollBehavior {
@override @override
Set<PointerDeviceKind> get dragDevices => {PointerDeviceKind.touch, PointerDeviceKind.mouse}; Set<PointerDeviceKind> get dragDevices =>
{PointerDeviceKind.touch, PointerDeviceKind.mouse};
} }

View File

@ -1,5 +1,4 @@
import 'dart:io'; import 'dart:io';
import 'dart:math' show max;
import 'package:dio/dio.dart'; import 'package:dio/dio.dart';
import 'package:dismissible_page/dismissible_page.dart'; import 'package:dismissible_page/dismissible_page.dart';
@ -48,11 +47,14 @@ class _AttachmentZoomViewState extends State<AttachmentZoomView> {
bool _showOverlay = true; bool _showOverlay = true;
bool _dismissable = true; bool _dismissable = true;
int _page = 0;
void _updatePage() { void _updatePage() {
setState(() { setState(() {
if (_isCompletedDownload) { if (_isCompletedDownload) {
setState(() => _isCompletedDownload = false); setState(() => _isCompletedDownload = false);
} }
_page = _pageController.page?.round() ?? 0;
}); });
} }
@ -155,7 +157,7 @@ class _AttachmentZoomViewState extends State<AttachmentZoomView> {
Navigator.of(context).pop(); Navigator.of(context).pop();
}, },
direction: _dismissable direction: _dismissable
? DismissiblePageDismissDirection.multi ? DismissiblePageDismissDirection.down
: DismissiblePageDismissDirection.none, : DismissiblePageDismissDirection.none,
backgroundColor: Colors.transparent, backgroundColor: Colors.transparent,
isFullScreen: true, isFullScreen: true,
@ -222,31 +224,11 @@ class _AttachmentZoomViewState extends State<AttachmentZoomView> {
BoxDecoration(color: Colors.transparent), BoxDecoration(color: Colors.transparent),
); );
}), }),
Positioned(
top: max(MediaQuery.of(context).padding.top, 8),
left: 14,
child: IgnorePointer(
ignoring: !_showOverlay,
child: IconButton(
constraints: const BoxConstraints(),
icon: const Icon(Icons.close),
style: ButtonStyle(
backgroundColor: MaterialStateProperty.all(
Theme.of(context).colorScheme.surface.withOpacity(0.5),
),
),
onPressed: () {
Navigator.of(context).pop();
},
).opacity(_showOverlay ? 1 : 0, animate: true).animate(
const Duration(milliseconds: 300), Curves.easeInOut),
),
),
Align( Align(
alignment: Alignment.bottomCenter, alignment: Alignment.bottomCenter,
child: IgnorePointer( child: IgnorePointer(
child: Container( child: Container(
height: 300, height: 200,
decoration: BoxDecoration( decoration: BoxDecoration(
gradient: LinearGradient( gradient: LinearGradient(
begin: Alignment.bottomCenter, begin: Alignment.bottomCenter,
@ -269,153 +251,130 @@ class _AttachmentZoomViewState extends State<AttachmentZoomView> {
child: Material( child: Material(
color: Colors.transparent, color: Colors.transparent,
child: Builder(builder: (context) { child: Builder(builder: (context) {
final ud = context.read<UserDirectoryProvider>(); return Row(
final item = widget.data.elementAt(
widget.data.length > 1
? _pageController.page?.round() ?? 0
: 0,
);
final account = ud.getFromCache(item.accountId);
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
if (item.accountId > 0) IconButton(
Row( iconSize: 18,
children: [ constraints: const BoxConstraints(),
IgnorePointer( icon: const Icon(Icons.close),
child: AccountImage( style: ButtonStyle(
content: account?.avatar, backgroundColor: MaterialStateProperty.all(
radius: 19, Theme.of(context)
), .colorScheme
), .surface
const Gap(8), .withOpacity(0.5),
Expanded(
child: IgnorePointer(
child: Column(
crossAxisAlignment:
CrossAxisAlignment.start,
children: [
Text(
'attachmentUploadBy'.tr(),
style: Theme.of(context)
.textTheme
.bodySmall,
),
Text(
account?.nick ?? 'unknown'.tr(),
style: Theme.of(context)
.textTheme
.bodyMedium,
),
],
),
),
),
if (widget.data.length > 1)
IgnorePointer(
child: Text(
'${(_pageController.page?.round() ?? 0) + 1}/${widget.data.length}',
style: GoogleFonts.robotoMono(fontSize: 13),
).padding(right: 8),
),
InkWell(
borderRadius:
const BorderRadius.all(Radius.circular(16)),
onTap: _isDownloading
? null
: () => _saveToAlbum(widget.data.length > 1
? _pageController.page?.round() ?? 0
: 0),
child: Container(
padding: const EdgeInsets.all(6),
child: !_isDownloading
? !_isCompletedDownload
? const Icon(Symbols.save_alt)
: const Icon(Symbols.download_done)
: SizedBox(
width: 24,
height: 24,
child: CircularProgressIndicator(
value: _progressOfDownload,
strokeWidth: 3,
),
),
),
),
],
),
const Gap(4),
IgnorePointer(
child: Text(
item.alt,
maxLines: 2,
overflow: TextOverflow.ellipsis,
style: const TextStyle(
fontSize: 15,
fontWeight: FontWeight.w500,
), ),
), ),
onPressed: () {
Navigator.of(context).pop();
},
), ),
const Gap(2), IconButton(
IgnorePointer( iconSize: 20,
child: Wrap( constraints: const BoxConstraints(),
spacing: 6, padding: EdgeInsets.zero,
children: [ visualDensity: VisualDensity.compact,
if (item.metadata['exif'] == null) icon: const Icon(Symbols.hide).padding(all: 6),
Text( onPressed: () {
'#${item.rid}', setState(() => _showOverlay = false);
style: metaTextStyle, }),
), Expanded(
if (item.metadata['exif']?['Model'] != null) child: IgnorePointer(
Text( child: Builder(builder: (context) {
'attachmentShotOn'.tr(args: [ final item = widget.data.elementAt(_page);
item.metadata['exif']?['Model'], final doShowCameraInfo =
]), item.metadata['exif']?['Model'] != null;
style: metaTextStyle, final exif = item.metadata['exif'];
).padding(right: 2), return Column(
if (item.metadata['exif']?['Megapixels'] != children: [
null && if (widget.data.length > 1)
item.metadata['exif']?['Model'] != null) Text(
Text( '${_page + 1}/${widget.data.length}',
'${item.metadata['exif']?['Megapixels']}MP', style:
style: metaTextStyle, GoogleFonts.robotoMono(fontSize: 13),
) ).padding(right: 8),
else if (doShowCameraInfo)
Text( Text(
item.size.formatBytes(), 'attachmentShotOn'
style: metaTextStyle, .tr(args: [exif?['Model']]),
), style: metaTextStyle,
if (item.metadata['width'] != null && textAlign: TextAlign.center,
item.metadata['height'] != null) ),
Text( if (doShowCameraInfo)
'${item.metadata['width']}x${item.metadata['height']}', Row(
style: metaTextStyle, spacing: 4,
), mainAxisSize: MainAxisSize.min,
], children: [
if (exif?['Megapixels'] != null)
Text(
'${exif?['Megapixels']}MP',
style: metaTextStyle,
),
if (exif?['ISO'] != null)
Text(
'ISO${exif['ISO']}',
style: metaTextStyle,
),
if (exif?['FNumber'] != null)
Text(
'f/${exif['FNumber']}',
style: metaTextStyle,
),
],
)
],
);
}),
), ),
), ),
const Gap(4), IconButton(
InkWell( constraints: const BoxConstraints(),
onTap: () { padding: EdgeInsets.zero,
visualDensity: VisualDensity.compact,
icon: Container(
padding: const EdgeInsets.all(6),
child: !_isDownloading
? !_isCompletedDownload
? const Icon(Symbols.save_alt)
: const Icon(Symbols.download_done)
: SizedBox(
width: 20,
height: 20,
child: CircularProgressIndicator(
backgroundColor: Theme.of(context)
.colorScheme
.surfaceContainerHighest,
value: _progressOfDownload,
strokeWidth: 3,
),
),
),
onPressed:
_isDownloading ? null : () => _saveToAlbum(_page),
),
IconButton(
iconSize: 18,
constraints: const BoxConstraints(),
icon: const Icon(Icons.info_outline),
style: ButtonStyle(
backgroundColor: MaterialStateProperty.all(
Theme.of(context)
.colorScheme
.surface
.withOpacity(0.5),
),
),
onPressed: () {
_showDetail = true; _showDetail = true;
showModalBottomSheet( showModalBottomSheet(
context: context, context: context,
builder: (context) => _AttachmentZoomDetailPopup( builder: (context) => _AttachmentZoomDetailPopup(
data: widget.data.elementAt( data: widget.data.elementAt(_page),
widget.data.length > 1
? _pageController.page?.round() ?? 0
: 0),
), ),
).then((_) { ).then((_) {
_showDetail = false; _showDetail = false;
}); });
}, },
child: Text(
'viewDetailedAttachment'.tr(),
style: metaTextStyle.copyWith(
decoration: TextDecoration.underline),
),
), ),
], ],
); );
@ -427,18 +386,20 @@ class _AttachmentZoomViewState extends State<AttachmentZoomView> {
), ),
), ),
onTap: () { onTap: () {
if (_showOverlay) {
Navigator.pop(context);
return;
}
setState(() => _showOverlay = !_showOverlay); setState(() => _showOverlay = !_showOverlay);
}, },
onVerticalDragUpdate: (details) { onVerticalDragUpdate: (details) {
if (_showDetail) return; if (_showDetail || !_dismissable) return;
if (details.delta.dy <= -20) { if (details.delta.dy <= -20) {
_showDetail = true; _showDetail = true;
showModalBottomSheet( showModalBottomSheet(
context: context, context: context,
builder: (context) => _AttachmentZoomDetailPopup( builder: (context) => _AttachmentZoomDetailPopup(
data: widget.data.elementAt(widget.data.length > 1 data: widget.data.elementAt(_page),
? _pageController.page?.round() ?? 0
: 0),
), ),
).then((_) { ).then((_) {
_showDetail = false; _showDetail = false;

View File

@ -12,10 +12,10 @@ import 'package:surface/providers/config.dart';
import 'package:surface/providers/keypair.dart'; import 'package:surface/providers/keypair.dart';
import 'package:surface/providers/user_directory.dart'; import 'package:surface/providers/user_directory.dart';
import 'package:surface/providers/userinfo.dart'; import 'package:surface/providers/userinfo.dart';
import 'package:surface/screens/account/profile_page.dart';
import 'package:surface/types/chat.dart'; import 'package:surface/types/chat.dart';
import 'package:surface/widgets/account/account_image.dart'; import 'package:surface/widgets/account/account_image.dart';
import 'package:surface/widgets/account/account_popover.dart'; import 'package:surface/widgets/account/account_popover.dart';
import 'package:surface/widgets/account/badge.dart';
import 'package:surface/widgets/attachment/attachment_list.dart'; import 'package:surface/widgets/attachment/attachment_list.dart';
import 'package:surface/widgets/context_menu.dart'; import 'package:surface/widgets/context_menu.dart';
import 'package:surface/widgets/link_preview.dart'; import 'package:surface/widgets/link_preview.dart';
@ -109,18 +109,10 @@ class ChatMessage extends StatelessWidget {
child: AccountImage( child: AccountImage(
content: user?.avatar, content: user?.avatar,
badge: (user?.badges.isNotEmpty ?? false) badge: (user?.badges.isNotEmpty ?? false)
? Icon( ? AccountBadge(
kBadgesMeta[user!.badges.first.type]?.$2 ?? badge: user!.badges.first,
Symbols.question_mark, radius: 16,
color: kBadgesMeta[user.badges.first.type]?.$3, padding: EdgeInsets.all(2),
fill: 1,
size: 18,
shadows: [
Shadow(
offset: Offset(1, 1),
blurRadius: 5.0,
color: Color.fromARGB(200, 0, 0, 0)),
],
) )
: null, : null,
), ),
@ -214,7 +206,8 @@ class ChatMessage extends StatelessWidget {
data.type == 'messages.new' && data.type == 'messages.new' &&
(data.body['text']?.isNotEmpty ?? false) && (data.body['text']?.isNotEmpty ?? false) &&
(cfg.prefs.getBool(kAppExpandChatLink) ?? true)) (cfg.prefs.getBool(kAppExpandChatLink) ?? true))
LinkPreviewWidget(text: data.body['text']!).padding(left: 48), LinkPreviewWidget(text: data.body['text']!)
.padding(left: isCompact ? 0 : 48),
if (data.preload?.attachments?.isNotEmpty ?? false) if (data.preload?.attachments?.isNotEmpty ?? false)
AttachmentList( AttachmentList(
data: data.preload!.attachments!, data: data.preload!.attachments!,

View File

@ -29,6 +29,7 @@ import 'package:surface/types/attachment.dart';
import 'package:surface/types/post.dart'; import 'package:surface/types/post.dart';
import 'package:surface/types/reaction.dart'; import 'package:surface/types/reaction.dart';
import 'package:surface/widgets/account/account_image.dart'; import 'package:surface/widgets/account/account_image.dart';
import 'package:surface/widgets/account/badge.dart';
import 'package:surface/widgets/attachment/attachment_item.dart'; import 'package:surface/widgets/attachment/attachment_item.dart';
import 'package:surface/widgets/attachment/attachment_list.dart'; import 'package:surface/widgets/attachment/attachment_list.dart';
import 'package:surface/widgets/dialog.dart'; import 'package:surface/widgets/dialog.dart';
@ -43,8 +44,6 @@ import 'package:surface/widgets/post/publisher_popover.dart';
import 'package:surface/widgets/universal_image.dart'; import 'package:surface/widgets/universal_image.dart';
import 'package:xml/xml.dart'; import 'package:xml/xml.dart';
import '../../screens/account/profile_page.dart' show kBadgesMeta;
class OpenablePostItem extends StatelessWidget { class OpenablePostItem extends StatelessWidget {
final SnPost data; final SnPost data;
final bool showReactions; final bool showReactions;
@ -149,7 +148,6 @@ class PostItem extends StatelessWidget {
void _doShareViaPicture(BuildContext context) async { void _doShareViaPicture(BuildContext context) async {
final box = context.findRenderObject() as RenderBox?; final box = context.findRenderObject() as RenderBox?;
context.showSnackbar('postSharingViaPicture'.tr());
final controller = ScreenshotController(); final controller = ScreenshotController();
final capturedImage = await controller.captureFromLongWidget( final capturedImage = await controller.captureFromLongWidget(
@ -160,9 +158,9 @@ class PostItem extends StatelessWidget {
child: Material( child: Material(
child: MultiProvider( child: MultiProvider(
providers: [ providers: [
// Create a copy of environments
Provider<SnNetworkProvider>(create: (_) => context.read()), Provider<SnNetworkProvider>(create: (_) => context.read()),
ChangeNotifierProvider<ConfigProvider>( Provider<UserDirectoryProvider>(create: (_) => context.read()),
create: (_) => context.read()),
], ],
child: ResponsiveBreakpoints.builder( child: ResponsiveBreakpoints.builder(
breakpoints: ResponsiveBreakpoints.of(context).breakpoints, breakpoints: ResponsiveBreakpoints.of(context).breakpoints,
@ -224,7 +222,7 @@ class PostItem extends StatelessWidget {
onShareImage: () => _doShareViaPicture(context), onShareImage: () => _doShareViaPicture(context),
onSelectAnswer: onSelectAnswer, onSelectAnswer: onSelectAnswer,
onDeleted: () { onDeleted: () {
if (onDeleted != null) {} onDeleted?.call();
}, },
).padding(bottom: 8), ).padding(bottom: 8),
if (data.preload?.video != null) if (data.preload?.video != null)
@ -273,7 +271,7 @@ class PostItem extends StatelessWidget {
onShareImage: () => _doShareViaPicture(context), onShareImage: () => _doShareViaPicture(context),
onSelectAnswer: onSelectAnswer, onSelectAnswer: onSelectAnswer,
onDeleted: () { onDeleted: () {
if (onDeleted != null) {} onDeleted?.call();
}, },
).padding(horizontal: 12, top: 8, bottom: 8), ).padding(horizontal: 12, top: 8, bottom: 8),
if (data.preload?.video != null) if (data.preload?.video != null)
@ -364,7 +362,7 @@ class PostItem extends StatelessWidget {
onShareImage: () => _doShareViaPicture(context), onShareImage: () => _doShareViaPicture(context),
onSelectAnswer: onSelectAnswer, onSelectAnswer: onSelectAnswer,
onDeleted: () { onDeleted: () {
if (onDeleted != null) onDeleted!(); onDeleted?.call();
}, },
).padding(horizontal: 12, vertical: 8), ).padding(horizontal: 12, vertical: 8),
if (data.preload?.video != null) if (data.preload?.video != null)
@ -507,6 +505,8 @@ class PostShareImageWidget extends StatelessWidget {
StyledWidget(AttachmentList( StyledWidget(AttachmentList(
data: data.preload!.attachments!, data: data.preload!.attachments!,
columned: true, columned: true,
fit: BoxFit.contain,
filterQuality: FilterQuality.high,
)).padding(horizontal: 16, bottom: 8), )).padding(horizontal: 16, bottom: 8),
Column( Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
@ -886,7 +886,7 @@ class _PostContentHeader extends StatelessWidget {
'publisherId': data.publisherId, 'publisherId': data.publisherId,
}); });
if (!context.mounted) return; if (!context.mounted) return;
context.showSnackbar('postDeleted'.tr(args: ['#${data.id}'])); onDeleted.call();
} catch (err) { } catch (err) {
if (!context.mounted) return; if (!context.mounted) return;
context.showErrorDialog(err); context.showErrorDialog(err);
@ -922,27 +922,39 @@ class _PostContentHeader extends StatelessWidget {
return Row( return Row(
children: [ children: [
GestureDetector( GestureDetector(
child: AccountImage( child: data.preload?.realm == null
content: data.publisher.avatar, ? AccountImage(
radius: isCompact ? 12 : 20, content: data.publisher.avatar,
borderRadius: data.publisher.type == 1 ? (isCompact ? 4 : 8) : 20, radius: isCompact ? 12 : 20,
badge: (user?.badges.isNotEmpty ?? false) borderRadius:
? Icon( data.publisher.type == 1 ? (isCompact ? 4 : 8) : 20,
kBadgesMeta[user!.badges.first.type]?.$2 ?? badge: (user?.badges.isNotEmpty ?? false)
Symbols.question_mark, ? AccountBadge(
color: kBadgesMeta[user.badges.first.type]?.$3, badge: user!.badges.first,
fill: 1, radius: 16,
size: 18, padding: EdgeInsets.all(2),
shadows: [ )
Shadow( : null,
offset: Offset(1, 1), )
blurRadius: 5.0, : AccountImage(
color: Color.fromARGB(200, 0, 0, 0), content: data.preload!.realm!.avatar,
radius: isCompact ? 12 : 20,
borderRadius: isCompact ? 4 : 8,
badgeOffset: Offset(-6, -4),
badge: Container(
decoration: BoxDecoration(
border: Border.all(
color: Theme.of(context).colorScheme.surface,
width: 2,
), ),
], borderRadius: BorderRadius.circular(10),
) ),
: null, child: AccountImage(
), content: data.publisher.avatar,
radius: 10,
),
),
),
onTap: () { onTap: () {
showPopover( showPopover(
backgroundColor: Theme.of(context).colorScheme.surface, backgroundColor: Theme.of(context).colorScheme.surface,
@ -987,7 +999,17 @@ class _PostContentHeader extends StatelessWidget {
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text(data.publisher.nick).bold(), Row(
children: [
Text(data.publisher.nick).bold(),
if (data.preload?.realm != null)
const Icon(Symbols.arrow_right, size: 16)
.padding(horizontal: 2)
.opacity(0.5),
if (data.preload?.realm != null)
Text(data.preload!.realm!.name),
],
),
Row( Row(
children: [ children: [
Text('@${data.publisher.name}').fontSize(13), Text('@${data.publisher.name}').fontSize(13),
@ -1037,8 +1059,10 @@ class _PostContentHeader extends StatelessWidget {
onTap: () { onTap: () {
GoRouter.of(context).pushNamed( GoRouter.of(context).pushNamed(
'postEditor', 'postEditor',
pathParameters: {'mode': data.typePlural}, queryParameters: {
queryParameters: {'editing': data.id.toString()}, 'editing': data.id.toString(),
'mode': data.typePlural,
},
); );
}, },
), ),
@ -1065,8 +1089,10 @@ class _PostContentHeader extends StatelessWidget {
onTap: () { onTap: () {
GoRouter.of(context).pushNamed( GoRouter.of(context).pushNamed(
'postEditor', 'postEditor',
pathParameters: {'mode': 'stories'}, queryParameters: {
queryParameters: {'replying': data.id.toString()}, 'replying': data.id.toString(),
'mode': data.typePlural,
},
); );
}, },
), ),
@ -1081,8 +1107,10 @@ class _PostContentHeader extends StatelessWidget {
onTap: () { onTap: () {
GoRouter.of(context).pushNamed( GoRouter.of(context).pushNamed(
'postEditor', 'postEditor',
pathParameters: {'mode': 'stories'}, queryParameters: {
queryParameters: {'reposting': data.id.toString()}, 'reposting': data.id.toString(),
'mode': 'stories',
},
); );
}, },
), ),

View File

@ -25,7 +25,8 @@ class PostMiniEditor extends StatefulWidget {
} }
class _PostMiniEditorState extends State<PostMiniEditor> { class _PostMiniEditorState extends State<PostMiniEditor> {
final PostWriteController _writeController = PostWriteController(doLoadFromTemporary: false); final PostWriteController _writeController =
PostWriteController(doLoadFromTemporary: false);
bool _isFetching = false; bool _isFetching = false;
@ -44,8 +45,9 @@ class _PostMiniEditorState extends State<PostMiniEditor> {
resp.data?.map((e) => SnPublisher.fromJson(e)) ?? [], resp.data?.map((e) => SnPublisher.fromJson(e)) ?? [],
); );
final beforeId = config.prefs.getInt('int_last_publisher_id'); final beforeId = config.prefs.getInt('int_last_publisher_id');
_writeController _writeController.setPublisher(
.setPublisher(_publishers?.where((ele) => ele.id == beforeId).firstOrNull ?? _publishers?.firstOrNull); _publishers?.where((ele) => ele.id == beforeId).firstOrNull ??
_publishers?.firstOrNull);
} catch (err) { } catch (err) {
if (!mounted) return; if (!mounted) return;
context.showErrorDialog(err); context.showErrorDialog(err);
@ -99,11 +101,17 @@ class _PostMiniEditorState extends State<PostMiniEditor> {
Expanded( Expanded(
child: Column( child: Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment:
CrossAxisAlignment.start,
children: [ children: [
Text(item.nick).textStyle(Theme.of(context).textTheme.bodyMedium!), Text(item.nick).textStyle(
Theme.of(context)
.textTheme
.bodyMedium!),
Text('@${item.name}') Text('@${item.name}')
.textStyle(Theme.of(context).textTheme.bodySmall!) .textStyle(Theme.of(context)
.textTheme
.bodySmall!)
.fontSize(12), .fontSize(12),
], ],
), ),
@ -120,7 +128,8 @@ class _PostMiniEditorState extends State<PostMiniEditor> {
CircleAvatar( CircleAvatar(
radius: 16, radius: 16,
backgroundColor: Colors.transparent, backgroundColor: Colors.transparent,
foregroundColor: Theme.of(context).colorScheme.onSurface, foregroundColor:
Theme.of(context).colorScheme.onSurface,
child: const Icon(Symbols.add), child: const Icon(Symbols.add),
), ),
const Gap(8), const Gap(8),
@ -129,7 +138,8 @@ class _PostMiniEditorState extends State<PostMiniEditor> {
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text('publishersNew').tr().textStyle(Theme.of(context).textTheme.bodyMedium!), Text('publishersNew').tr().textStyle(
Theme.of(context).textTheme.bodyMedium!),
], ],
), ),
), ),
@ -140,7 +150,9 @@ class _PostMiniEditorState extends State<PostMiniEditor> {
value: _writeController.publisher, value: _writeController.publisher,
onChanged: (SnPublisher? value) { onChanged: (SnPublisher? value) {
if (value == null) { if (value == null) {
GoRouter.of(context).pushNamed('accountPublisherNew').then((value) { GoRouter.of(context)
.pushNamed('accountPublisherNew')
.then((value) {
if (value == true) { if (value == true) {
_publishers = null; _publishers = null;
_fetchPublishers(); _fetchPublishers();
@ -176,7 +188,8 @@ class _PostMiniEditorState extends State<PostMiniEditor> {
), ),
border: InputBorder.none, border: InputBorder.none,
), ),
onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(), onTapOutside: (_) =>
FocusManager.instance.primaryFocus?.unfocus(),
), ),
), ),
const Gap(8), const Gap(8),
@ -185,7 +198,8 @@ class _PostMiniEditorState extends State<PostMiniEditor> {
TweenAnimationBuilder<double>( TweenAnimationBuilder<double>(
tween: Tween(begin: 0, end: _writeController.progress), tween: Tween(begin: 0, end: _writeController.progress),
duration: Duration(milliseconds: 300), duration: Duration(milliseconds: 300),
builder: (context, value, _) => LinearProgressIndicator(value: value, minHeight: 2), builder: (context, value, _) =>
LinearProgressIndicator(value: value, minHeight: 2),
) )
else if (_writeController.isBusy) else if (_writeController.isBusy)
const LinearProgressIndicator(value: null, minHeight: 2), const LinearProgressIndicator(value: null, minHeight: 2),
@ -200,15 +214,17 @@ class _PostMiniEditorState extends State<PostMiniEditor> {
onPressed: () { onPressed: () {
GoRouter.of(context).pushNamed( GoRouter.of(context).pushNamed(
'postEditor', 'postEditor',
pathParameters: {'mode': 'stories'},
queryParameters: { queryParameters: {
if (widget.postReplyId != null) 'replying': widget.postReplyId.toString(), if (widget.postReplyId != null)
'replying': widget.postReplyId.toString(),
'mode': 'stories',
}, },
); );
}, },
), ),
TextButton.icon( TextButton.icon(
onPressed: (_writeController.isBusy || _writeController.publisher == null) onPressed: (_writeController.isBusy ||
_writeController.publisher == null)
? null ? null
: () { : () {
_writeController.sendPost(context).then((_) { _writeController.sendPost(context).then((_) {

View File

@ -9,10 +9,9 @@ import 'package:surface/providers/sn_network.dart';
import 'package:surface/providers/user_directory.dart'; import 'package:surface/providers/user_directory.dart';
import 'package:surface/types/post.dart'; import 'package:surface/types/post.dart';
import 'package:surface/widgets/account/account_image.dart'; import 'package:surface/widgets/account/account_image.dart';
import 'package:surface/widgets/account/badge.dart';
import 'package:surface/widgets/universal_image.dart'; import 'package:surface/widgets/universal_image.dart';
import '../../screens/account/profile_page.dart' show kBadgesMeta;
class PublisherPopoverCard extends StatelessWidget { class PublisherPopoverCard extends StatelessWidget {
final SnPublisher data; final SnPublisher data;
@ -76,39 +75,22 @@ class PublisherPopoverCard extends StatelessWidget {
const Gap(8) const Gap(8)
], ],
).padding(horizontal: 16), ).padding(horizontal: 16),
if (user != null && user.badges.isNotEmpty) const Gap(16),
if (user != null && user.badges.isNotEmpty) if (user != null && user.badges.isNotEmpty)
Wrap( Wrap(
spacing: 4, spacing: 4,
children: user.badges children: user.badges
.map( .map(
(ele) => Tooltip( (ele) => AccountBadge(badge: ele),
richMessage: TextSpan(
children: [
TextSpan(
text: kBadgesMeta[ele.type]?.$1.tr() ??
'unknown'.tr()),
if (ele.metadata['title'] != null)
TextSpan(
text: '\n${ele.metadata['title']}',
style: const TextStyle(fontWeight: FontWeight.bold),
),
TextSpan(text: '\n'),
TextSpan(
text: DateFormat.yMEd().format(ele.createdAt),
),
],
),
child: Icon(
kBadgesMeta[ele.type]?.$2 ?? Symbols.question_mark,
color: kBadgesMeta[ele.type]?.$3,
fill: 1,
),
),
) )
.toList(), .toList(),
).padding(horizontal: 24), ).padding(horizontal: 24, top: 16),
const Gap(16), const Gap(16),
if (data.description.isNotEmpty)
Text(
data.description,
maxLines: 2,
overflow: TextOverflow.ellipsis,
).padding(horizontal: 26, bottom: 20),
Row( Row(
children: [ children: [
Expanded( Expanded(

View File

@ -34,11 +34,14 @@ class UniversalImage extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final devicePixelRatio = MediaQuery.of(context).devicePixelRatio; final devicePixelRatio = MediaQuery.of(context).devicePixelRatio;
final double? resizeHeight = cacheHeight != null ? (cacheHeight! * devicePixelRatio) : null; final double? resizeHeight =
final double? resizeWidth = cacheWidth != null ? (cacheWidth! * devicePixelRatio) : null; cacheHeight != null ? (cacheHeight! * devicePixelRatio) : null;
final double? resizeWidth =
cacheWidth != null ? (cacheWidth! * devicePixelRatio) : null;
return Image( return Image(
filterQuality: filterQuality ?? context.read<ConfigProvider>().imageQuality, filterQuality:
filterQuality ?? context.read<ConfigProvider>().imageQuality,
image: kIsWeb image: kIsWeb
? UniversalImage.provider(url) ? UniversalImage.provider(url)
: ResizeImage( : ResizeImage(
@ -52,7 +55,8 @@ class UniversalImage extends StatelessWidget {
fit: fit, fit: fit,
loadingBuilder: noProgressIndicator loadingBuilder: noProgressIndicator
? null ? null
: (BuildContext context, Widget child, ImageChunkEvent? loadingProgress) { : (BuildContext context, Widget child,
ImageChunkEvent? loadingProgress) {
if (loadingProgress == null) return child; if (loadingProgress == null) return child;
return Container( return Container(
constraints: BoxConstraints(maxHeight: 80), constraints: BoxConstraints(maxHeight: 80),
@ -61,12 +65,15 @@ class UniversalImage extends StatelessWidget {
tween: Tween( tween: Tween(
begin: 0, begin: 0,
end: loadingProgress.expectedTotalBytes != null end: loadingProgress.expectedTotalBytes != null
? loadingProgress.cumulativeBytesLoaded / loadingProgress.expectedTotalBytes! ? loadingProgress.cumulativeBytesLoaded /
loadingProgress.expectedTotalBytes!
: 0, : 0,
), ),
duration: const Duration(milliseconds: 300), duration: const Duration(milliseconds: 300),
builder: (context, value, _) => CircularProgressIndicator( builder: (context, value, _) => CircularProgressIndicator(
value: loadingProgress.expectedTotalBytes != null ? value.toDouble() : null, value: loadingProgress.expectedTotalBytes != null
? value.toDouble()
: null,
), ),
), ),
), ),
@ -114,6 +121,7 @@ class AutoResizeUniversalImage extends StatelessWidget {
final BoxFit? fit; final BoxFit? fit;
final bool noProgressIndicator; final bool noProgressIndicator;
final bool noErrorWidget; final bool noErrorWidget;
final FilterQuality? filterQuality;
const AutoResizeUniversalImage( const AutoResizeUniversalImage(
this.url, { this.url, {
@ -123,6 +131,7 @@ class AutoResizeUniversalImage extends StatelessWidget {
this.fit, this.fit,
this.noProgressIndicator = false, this.noProgressIndicator = false,
this.noErrorWidget = false, this.noErrorWidget = false,
this.filterQuality,
}); });
@override @override
@ -137,6 +146,7 @@ class AutoResizeUniversalImage extends StatelessWidget {
noErrorWidget: noErrorWidget, noErrorWidget: noErrorWidget,
cacheHeight: constraints.maxHeight, cacheHeight: constraints.maxHeight,
cacheWidth: constraints.maxWidth, cacheWidth: constraints.maxWidth,
filterQuality: filterQuality,
); );
}); });
} }

View File

@ -173,10 +173,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: built_value name: built_value
sha256: "8b158ab94ec6913e480dc3f752418348b5ae099eb75868b5f4775f0572999c61" sha256: ea90e81dc4a25a043d9bee692d20ed6d1c4a1662a28c03a96417446c093ed6b4
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "8.9.4" version: "8.9.5"
cached_network_image: cached_network_image:
dependency: "direct main" dependency: "direct main"
description: description:
@ -301,10 +301,10 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: croppy name: croppy
sha256: bf99b00023df0d7d047e04d27d496d87cbefd968f578d0bd30f342ff75570a12 sha256: "99f4fbb4a4b44d2712e8dcd61c57c1acac151bd53cab11de3babec80407ed266"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.3.3" version: "1.3.5"
cross_file: cross_file:
dependency: "direct main" dependency: "direct main"
description: description:
@ -429,18 +429,18 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: drift name: drift
sha256: "97d5832657d49f26e7a8e07de397ddc63790b039372878d5117af816d0fdb5cb" sha256: "14a61af39d4584faf1d73b5b35e4b758a43008cf4c0fdb0576ec8e7032c0d9a5"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.25.1" version: "2.26.0"
drift_dev: drift_dev:
dependency: "direct dev" dependency: "direct dev"
description: description:
name: drift_dev name: drift_dev
sha256: f1db88482dbb016b9bbddddf746d5d0a6938b156ff20e07320052981f97388cc sha256: "0d3f8b33b76cf1c6a82ee34d9511c40957549c4674b8f1688609e6d6c7306588"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.25.2" version: "2.26.0"
drift_flutter: drift_flutter:
dependency: "direct main" dependency: "direct main"
description: description:
@ -710,6 +710,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:
@ -831,10 +839,10 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: flutter_map name: flutter_map
sha256: bbf145e8220531f2f727608c431871c7457f3b134e513543913afd00fdc1cd47 sha256: f7d0379477274f323c3f3bc12d369a2b42eb86d1e7bd2970ae1ea3cff782449a
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "8.1.0" version: "8.1.1"
flutter_markdown: flutter_markdown:
dependency: "direct main" dependency: "direct main"
description: description:
@ -1177,10 +1185,10 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: image_picker_android name: image_picker_android
sha256: "82652a75e3dd667a91187769a6a2cc81bd8c111bbead698d8e938d2b63e5e89a" sha256: "8bd392ba8b0c8957a157ae0dc9fcf48c58e6c20908d5880aea1d79734df090e9"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.8.12+21" version: "0.8.12+22"
image_picker_for_web: image_picker_for_web:
dependency: transitive dependency: transitive
description: description:
@ -1409,10 +1417,10 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: material_symbols_icons name: material_symbols_icons
sha256: "1403944c2a68dbdf934fca2b2115a372e70a4a185c136ab71a9d16e2108d0b14" sha256: ca30ccbd97763353bde6bb1076aa4f4d17a40db0804384da77df142102aa225d
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "4.2805.1" version: "4.2808.0"
media_kit: media_kit:
dependency: "direct main" dependency: "direct main"
description: description:
@ -1601,10 +1609,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: path_provider_android name: path_provider_android
sha256: "4adf4fd5423ec60a29506c76581bc05854c55e3a0b72d35bb28d661c9686edf2" sha256: "0ca7359dad67fd7063cb2892ab0c0737b2daafd807cf1acecd62374c8fae6c12"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.2.15" version: "2.2.16"
path_provider_foundation: path_provider_foundation:
dependency: transitive dependency: transitive
description: description:
@ -1785,10 +1793,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: pub_semver name: pub_semver
sha256: "7b3cfbf654f3edd0c6298ecd5be782ce997ddf0e00531b9464b55245185bbbbd" sha256: "5bfcf68ca79ef689f8990d1160781b4bad40a3bd5e5218ad4076ddb7f4081585"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.1.5" version: "2.2.0"
pubspec_parse: pubspec_parse:
dependency: transitive dependency: transitive
description: description:
@ -1953,10 +1961,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: shared_preferences_android name: shared_preferences_android
sha256: a768fc8ede5f0c8e6150476e14f38e2417c0864ca36bb4582be8e21925a03c22 sha256: "3ec7210872c4ba945e3244982918e502fa2bfb5230dff6832459ca0e1879b7ad"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.4.6" version: "2.4.8"
shared_preferences_foundation: shared_preferences_foundation:
dependency: transitive dependency: transitive
description: description:
@ -2118,10 +2126,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: sqlite3 name: sqlite3
sha256: "32b632dda27d664f85520093ed6f735ae5c49b5b75345afb8b19411bc59bb53d" sha256: "310af39c40dd0bb2058538333c9d9840a2725ae0b9f77e4fd09ad6696aa8f66e"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.7.4" version: "2.7.5"
sqlite3_flutter_libs: sqlite3_flutter_libs:
dependency: transitive dependency: transitive
description: description:
@ -2326,10 +2334,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: url_launcher_android name: url_launcher_android
sha256: "6fc2f56536ee873eeb867ad176ae15f304ccccc357848b351f6f0d8d4a40d193" sha256: "1d0eae19bd7606ef60fe69ef3b312a437a16549476c42321d5dc1506c9ca3bf4"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "6.3.14" version: "6.3.15"
url_launcher_ios: url_launcher_ios:
dependency: transitive dependency: transitive
description: description:

View File

@ -16,7 +16,7 @@ publish_to: "none" # Remove this line if you wish to publish to pub.dev
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
# In Windows, build-name is used as the major, minor, and patch parts # In Windows, build-name is used as the major, minor, and patch parts
# of the product and file versions while build-number is used as the build suffix. # of the product and file versions while build-number is used as the build suffix.
version: 2.4.2+76 version: 2.4.2+78
environment: environment:
sdk: ^3.5.4 sdk: ^3.5.4
@ -138,6 +138,7 @@ dependencies:
flutter_map: ^8.1.0 flutter_map: ^8.1.0
geolocator: ^13.0.2 geolocator: ^13.0.2
fast_rsa: ^3.8.0 fast_rsa: ^3.8.0
flutter_card_swiper: ^7.0.2
dev_dependencies: dev_dependencies:
flutter_test: flutter_test:
@ -187,18 +188,14 @@ flutter:
# "family" key with the font family name, and a "fonts" key with a # "family" key with the font family name, and a "fonts" key with a
# list giving the asset and other descriptors for the font. For # list giving the asset and other descriptors for the font. For
# example: # example:
# fonts: fonts:
# - family: Schyler - family: Nunito
# fonts: fonts:
# - asset: fonts/Schyler-Regular.ttf - asset: assets/fonts/Nunito-Regular.ttf
# - asset: fonts/Schyler-Italic.ttf - asset: assets/fonts/Nunito-Bold.ttf
# style: italic weight: 700
# - family: Trajan Pro - asset: assets/fonts/Nunito-Italic.ttf
# fonts: style: italic
# - asset: fonts/TrajanPro.ttf
# - asset: fonts/TrajanPro_Bold.ttf
# weight: 700
#
# For details regarding fonts from package dependencies, # For details regarding fonts from package dependencies,
# see https://flutter.dev/to/font-from-package # see https://flutter.dev/to/font-from-package

View File

@ -1,192 +0,0 @@
var CanvasKitInit = (() => {
var _scriptName = import.meta.url;
return (
function(moduleArg = {}) {
var moduleRtn;
var r=moduleArg,aa,ca,da=new Promise((a,b)=>{aa=a;ca=b}),ea="object"==typeof window,ha="function"==typeof importScripts;
(function(a){a.ce=a.ce||[];a.ce.push(function(){a.MakeSWCanvasSurface=function(b){var c=b,e="undefined"!==typeof OffscreenCanvas&&c instanceof OffscreenCanvas;if(!("undefined"!==typeof HTMLCanvasElement&&c instanceof HTMLCanvasElement||e||(c=document.getElementById(b),c)))throw"Canvas with id "+b+" was not found";if(b=a.MakeSurface(c.width,c.height))b.Ae=c;return b};a.MakeCanvasSurface||(a.MakeCanvasSurface=a.MakeSWCanvasSurface);a.MakeSurface=function(b,c){var e={width:b,height:c,colorType:a.ColorType.RGBA_8888,
alphaType:a.AlphaType.Unpremul,colorSpace:a.ColorSpace.SRGB},f=b*c*4,k=a._malloc(f);if(e=a.Surface._makeRasterDirect(e,k,4*b))e.Ae=null,e.$e=b,e.Xe=c,e.Ye=f,e.He=k,e.getCanvas().clear(a.TRANSPARENT);return e};a.MakeRasterDirectSurface=function(b,c,e){return a.Surface._makeRasterDirect(b,c.byteOffset,e)};a.Surface.prototype.flush=function(b){a.$d(this.Zd);this._flush();if(this.Ae){var c=new Uint8ClampedArray(a.HEAPU8.buffer,this.He,this.Ye);c=new ImageData(c,this.$e,this.Xe);b?this.Ae.getContext("2d").putImageData(c,
0,0,b[0],b[1],b[2]-b[0],b[3]-b[1]):this.Ae.getContext("2d").putImageData(c,0,0)}};a.Surface.prototype.dispose=function(){this.He&&a._free(this.He);this.delete()};a.$d=a.$d||function(){};a.Be=a.Be||function(){return null}})})(r);
(function(a){a.ce=a.ce||[];a.ce.push(function(){function b(l,p,v){return l&&l.hasOwnProperty(p)?l[p]:v}function c(l){var p=ja(ka);ka[p]=l;return p}function e(l){return l.naturalHeight||l.videoHeight||l.displayHeight||l.height}function f(l){return l.naturalWidth||l.videoWidth||l.displayWidth||l.width}function k(l,p,v,w){l.bindTexture(l.TEXTURE_2D,p);w||v.alphaType!==a.AlphaType.Premul||l.pixelStorei(l.UNPACK_PREMULTIPLY_ALPHA_WEBGL,!0);return p}function n(l,p,v){v||p.alphaType!==a.AlphaType.Premul||
l.pixelStorei(l.UNPACK_PREMULTIPLY_ALPHA_WEBGL,!1);l.bindTexture(l.TEXTURE_2D,null)}a.GetWebGLContext=function(l,p){if(!l)throw"null canvas passed into makeWebGLContext";var v={alpha:b(p,"alpha",1),depth:b(p,"depth",1),stencil:b(p,"stencil",8),antialias:b(p,"antialias",0),premultipliedAlpha:b(p,"premultipliedAlpha",1),preserveDrawingBuffer:b(p,"preserveDrawingBuffer",0),preferLowPowerToHighPerformance:b(p,"preferLowPowerToHighPerformance",0),failIfMajorPerformanceCaveat:b(p,"failIfMajorPerformanceCaveat",
0),enableExtensionsByDefault:b(p,"enableExtensionsByDefault",1),explicitSwapControl:b(p,"explicitSwapControl",0),renderViaOffscreenBackBuffer:b(p,"renderViaOffscreenBackBuffer",0)};v.majorVersion=p&&p.majorVersion?p.majorVersion:"undefined"!==typeof WebGL2RenderingContext?2:1;if(v.explicitSwapControl)throw"explicitSwapControl is not supported";l=la(l,v);if(!l)return 0;oa(l);z.le.getExtension("WEBGL_debug_renderer_info");return l};a.deleteContext=function(l){z===pa[l]&&(z=null);"object"==typeof JSEvents&&
JSEvents.Af(pa[l].le.canvas);pa[l]&&pa[l].le.canvas&&(pa[l].le.canvas.Ve=void 0);pa[l]=null};a._setTextureCleanup({deleteTexture:function(l,p){var v=ka[p];v&&pa[l].le.deleteTexture(v);ka[p]=null}});a.MakeWebGLContext=function(l){if(!this.$d(l))return null;var p=this._MakeGrContext();if(!p)return null;p.Zd=l;var v=p.delete.bind(p);p["delete"]=function(){a.$d(this.Zd);v()}.bind(p);return z.Je=p};a.MakeGrContext=a.MakeWebGLContext;a.GrDirectContext.prototype.getResourceCacheLimitBytes=function(){a.$d(this.Zd);
this._getResourceCacheLimitBytes()};a.GrDirectContext.prototype.getResourceCacheUsageBytes=function(){a.$d(this.Zd);this._getResourceCacheUsageBytes()};a.GrDirectContext.prototype.releaseResourcesAndAbandonContext=function(){a.$d(this.Zd);this._releaseResourcesAndAbandonContext()};a.GrDirectContext.prototype.setResourceCacheLimitBytes=function(l){a.$d(this.Zd);this._setResourceCacheLimitBytes(l)};a.MakeOnScreenGLSurface=function(l,p,v,w,A,D){if(!this.$d(l.Zd))return null;p=void 0===A||void 0===D?
this._MakeOnScreenGLSurface(l,p,v,w):this._MakeOnScreenGLSurface(l,p,v,w,A,D);if(!p)return null;p.Zd=l.Zd;return p};a.MakeRenderTarget=function(){var l=arguments[0];if(!this.$d(l.Zd))return null;if(3===arguments.length){var p=this._MakeRenderTargetWH(l,arguments[1],arguments[2]);if(!p)return null}else if(2===arguments.length){if(p=this._MakeRenderTargetII(l,arguments[1]),!p)return null}else return null;p.Zd=l.Zd;return p};a.MakeWebGLCanvasSurface=function(l,p,v){p=p||null;var w=l,A="undefined"!==
typeof OffscreenCanvas&&w instanceof OffscreenCanvas;if(!("undefined"!==typeof HTMLCanvasElement&&w instanceof HTMLCanvasElement||A||(w=document.getElementById(l),w)))throw"Canvas with id "+l+" was not found";l=this.GetWebGLContext(w,v);if(!l||0>l)throw"failed to create webgl context: err "+l;l=this.MakeWebGLContext(l);p=this.MakeOnScreenGLSurface(l,w.width,w.height,p);return p?p:(p=w.cloneNode(!0),w.parentNode.replaceChild(p,w),p.classList.add("ck-replaced"),a.MakeSWCanvasSurface(p))};a.MakeCanvasSurface=
a.MakeWebGLCanvasSurface;a.Surface.prototype.makeImageFromTexture=function(l,p){a.$d(this.Zd);l=c(l);if(p=this._makeImageFromTexture(this.Zd,l,p))p.ue=l;return p};a.Surface.prototype.makeImageFromTextureSource=function(l,p,v){p||={height:e(l),width:f(l),colorType:a.ColorType.RGBA_8888,alphaType:v?a.AlphaType.Premul:a.AlphaType.Unpremul};p.colorSpace||(p.colorSpace=a.ColorSpace.SRGB);a.$d(this.Zd);var w=z.le;v=k(w,w.createTexture(),p,v);2===z.version?w.texImage2D(w.TEXTURE_2D,0,w.RGBA,p.width,p.height,
0,w.RGBA,w.UNSIGNED_BYTE,l):w.texImage2D(w.TEXTURE_2D,0,w.RGBA,w.RGBA,w.UNSIGNED_BYTE,l);n(w,p);this._resetContext();return this.makeImageFromTexture(v,p)};a.Surface.prototype.updateTextureFromSource=function(l,p,v){if(l.ue){a.$d(this.Zd);var w=l.getImageInfo(),A=z.le,D=k(A,ka[l.ue],w,v);2===z.version?A.texImage2D(A.TEXTURE_2D,0,A.RGBA,f(p),e(p),0,A.RGBA,A.UNSIGNED_BYTE,p):A.texImage2D(A.TEXTURE_2D,0,A.RGBA,A.RGBA,A.UNSIGNED_BYTE,p);n(A,w,v);this._resetContext();ka[l.ue]=null;l.ue=c(D);w.colorSpace=
l.getColorSpace();p=this._makeImageFromTexture(this.Zd,l.ue,w);v=l.Yd.ae;A=l.Yd.ee;l.Yd.ae=p.Yd.ae;l.Yd.ee=p.Yd.ee;p.Yd.ae=v;p.Yd.ee=A;p.delete();w.colorSpace.delete()}};a.MakeLazyImageFromTextureSource=function(l,p,v){p||={height:e(l),width:f(l),colorType:a.ColorType.RGBA_8888,alphaType:v?a.AlphaType.Premul:a.AlphaType.Unpremul};p.colorSpace||(p.colorSpace=a.ColorSpace.SRGB);var w={makeTexture:function(){var A=z,D=A.le,I=k(D,D.createTexture(),p,v);2===A.version?D.texImage2D(D.TEXTURE_2D,0,D.RGBA,
p.width,p.height,0,D.RGBA,D.UNSIGNED_BYTE,l):D.texImage2D(D.TEXTURE_2D,0,D.RGBA,D.RGBA,D.UNSIGNED_BYTE,l);n(D,p,v);return c(I)},freeSrc:function(){}};"VideoFrame"===l.constructor.name&&(w.freeSrc=function(){l.close()});return a.Image._makeFromGenerator(p,w)};a.$d=function(l){return l?oa(l):!1};a.Be=function(){return z&&z.Je&&!z.Je.isDeleted()?z.Je:null}})})(r);
(function(a){function b(g){return(f(255*g[3])<<24|f(255*g[0])<<16|f(255*g[1])<<8|f(255*g[2])<<0)>>>0}function c(g){if(g&&g._ck)return g;if(g instanceof Float32Array){for(var d=Math.floor(g.length/4),h=new Uint32Array(d),m=0;m<d;m++)h[m]=b(g.slice(4*m,4*(m+1)));return h}if(g instanceof Uint32Array)return g;if(g instanceof Array&&g[0]instanceof Float32Array)return g.map(b)}function e(g){if(void 0===g)return 1;var d=parseFloat(g);return g&&-1!==g.indexOf("%")?d/100:d}function f(g){return Math.round(Math.max(0,
Math.min(g||0,255)))}function k(g,d){d&&d._ck||a._free(g)}function n(g,d,h){if(!g||!g.length)return K;if(g&&g._ck)return g.byteOffset;var m=a[d].BYTES_PER_ELEMENT;h||=a._malloc(g.length*m);a[d].set(g,h/m);return h}function l(g){var d={he:K,count:g.length,colorType:a.ColorType.RGBA_F32};if(g instanceof Float32Array)d.he=n(g,"HEAPF32"),d.count=g.length/4;else if(g instanceof Uint32Array)d.he=n(g,"HEAPU32"),d.colorType=a.ColorType.RGBA_8888;else if(g instanceof Array){if(g&&g.length){for(var h=a._malloc(16*
g.length),m=0,t=h/4,u=0;u<g.length;u++)for(var x=0;4>x;x++)a.HEAPF32[t+m]=g[u][x],m++;g=h}else g=K;d.he=g}else throw"Invalid argument to copyFlexibleColorArray, Not a color array "+typeof g;return d}function p(g){if(!g)return K;var d=ba.toTypedArray();if(g.length){if(6===g.length||9===g.length)return n(g,"HEAPF32",P),6===g.length&&a.HEAPF32.set(Wc,6+P/4),P;if(16===g.length)return d[0]=g[0],d[1]=g[1],d[2]=g[3],d[3]=g[4],d[4]=g[5],d[5]=g[7],d[6]=g[12],d[7]=g[13],d[8]=g[15],P;throw"invalid matrix size";
}if(void 0===g.m11)throw"invalid matrix argument";d[0]=g.m11;d[1]=g.m21;d[2]=g.m41;d[3]=g.m12;d[4]=g.m22;d[5]=g.m42;d[6]=g.m14;d[7]=g.m24;d[8]=g.m44;return P}function v(g){if(!g)return K;var d=Y.toTypedArray();if(g.length){if(16!==g.length&&6!==g.length&&9!==g.length)throw"invalid matrix size";if(16===g.length)return n(g,"HEAPF32",ma);d.fill(0);d[0]=g[0];d[1]=g[1];d[3]=g[2];d[4]=g[3];d[5]=g[4];d[7]=g[5];d[10]=1;d[12]=g[6];d[13]=g[7];d[15]=g[8];6===g.length&&(d[12]=0,d[13]=0,d[15]=1);return ma}if(void 0===
g.m11)throw"invalid matrix argument";d[0]=g.m11;d[1]=g.m21;d[2]=g.m31;d[3]=g.m41;d[4]=g.m12;d[5]=g.m22;d[6]=g.m32;d[7]=g.m42;d[8]=g.m13;d[9]=g.m23;d[10]=g.m33;d[11]=g.m43;d[12]=g.m14;d[13]=g.m24;d[14]=g.m34;d[15]=g.m44;return ma}function w(g,d){return n(g,"HEAPF32",d||ia)}function A(g,d,h,m){var t=Ea.toTypedArray();t[0]=g;t[1]=d;t[2]=h;t[3]=m;return ia}function D(g){for(var d=new Float32Array(4),h=0;4>h;h++)d[h]=a.HEAPF32[g/4+h];return d}function I(g,d){return n(g,"HEAPF32",d||W)}function R(g,d){return n(g,
"HEAPF32",d||ub)}a.Color=function(g,d,h,m){void 0===m&&(m=1);return a.Color4f(f(g)/255,f(d)/255,f(h)/255,m)};a.ColorAsInt=function(g,d,h,m){void 0===m&&(m=255);return(f(m)<<24|f(g)<<16|f(d)<<8|f(h)<<0&268435455)>>>0};a.Color4f=function(g,d,h,m){void 0===m&&(m=1);return Float32Array.of(g,d,h,m)};Object.defineProperty(a,"TRANSPARENT",{get:function(){return a.Color4f(0,0,0,0)}});Object.defineProperty(a,"BLACK",{get:function(){return a.Color4f(0,0,0,1)}});Object.defineProperty(a,"WHITE",{get:function(){return a.Color4f(1,
1,1,1)}});Object.defineProperty(a,"RED",{get:function(){return a.Color4f(1,0,0,1)}});Object.defineProperty(a,"GREEN",{get:function(){return a.Color4f(0,1,0,1)}});Object.defineProperty(a,"BLUE",{get:function(){return a.Color4f(0,0,1,1)}});Object.defineProperty(a,"YELLOW",{get:function(){return a.Color4f(1,1,0,1)}});Object.defineProperty(a,"CYAN",{get:function(){return a.Color4f(0,1,1,1)}});Object.defineProperty(a,"MAGENTA",{get:function(){return a.Color4f(1,0,1,1)}});a.getColorComponents=function(g){return[Math.floor(255*
g[0]),Math.floor(255*g[1]),Math.floor(255*g[2]),g[3]]};a.parseColorString=function(g,d){g=g.toLowerCase();if(g.startsWith("#")){d=255;switch(g.length){case 9:d=parseInt(g.slice(7,9),16);case 7:var h=parseInt(g.slice(1,3),16);var m=parseInt(g.slice(3,5),16);var t=parseInt(g.slice(5,7),16);break;case 5:d=17*parseInt(g.slice(4,5),16);case 4:h=17*parseInt(g.slice(1,2),16),m=17*parseInt(g.slice(2,3),16),t=17*parseInt(g.slice(3,4),16)}return a.Color(h,m,t,d/255)}return g.startsWith("rgba")?(g=g.slice(5,
-1),g=g.split(","),a.Color(+g[0],+g[1],+g[2],e(g[3]))):g.startsWith("rgb")?(g=g.slice(4,-1),g=g.split(","),a.Color(+g[0],+g[1],+g[2],e(g[3]))):g.startsWith("gray(")||g.startsWith("hsl")||!d||(g=d[g],void 0===g)?a.BLACK:g};a.multiplyByAlpha=function(g,d){g=g.slice();g[3]=Math.max(0,Math.min(g[3]*d,1));return g};a.Malloc=function(g,d){var h=a._malloc(d*g.BYTES_PER_ELEMENT);return{_ck:!0,length:d,byteOffset:h,qe:null,subarray:function(m,t){m=this.toTypedArray().subarray(m,t);m._ck=!0;return m},toTypedArray:function(){if(this.qe&&
this.qe.length)return this.qe;this.qe=new g(a.HEAPU8.buffer,h,d);this.qe._ck=!0;return this.qe}}};a.Free=function(g){a._free(g.byteOffset);g.byteOffset=K;g.toTypedArray=null;g.qe=null};var P=K,ba,ma=K,Y,ia=K,Ea,fa,W=K,Ub,Ba=K,Vb,vb=K,Wb,wb=K,$a,Na=K,Xb,ub=K,Yb,Zb=K,Wc=Float32Array.of(0,0,1),K=0;a.onRuntimeInitialized=function(){function g(d,h,m,t,u,x,C){x||(x=4*t.width,t.colorType===a.ColorType.RGBA_F16?x*=2:t.colorType===a.ColorType.RGBA_F32&&(x*=4));var G=x*t.height;var F=u?u.byteOffset:a._malloc(G);
if(C?!d._readPixels(t,F,x,h,m,C):!d._readPixels(t,F,x,h,m))return u||a._free(F),null;if(u)return u.toTypedArray();switch(t.colorType){case a.ColorType.RGBA_8888:case a.ColorType.RGBA_F16:d=(new Uint8Array(a.HEAPU8.buffer,F,G)).slice();break;case a.ColorType.RGBA_F32:d=(new Float32Array(a.HEAPU8.buffer,F,G)).slice();break;default:return null}a._free(F);return d}Ea=a.Malloc(Float32Array,4);ia=Ea.byteOffset;Y=a.Malloc(Float32Array,16);ma=Y.byteOffset;ba=a.Malloc(Float32Array,9);P=ba.byteOffset;Xb=a.Malloc(Float32Array,
12);ub=Xb.byteOffset;Yb=a.Malloc(Float32Array,12);Zb=Yb.byteOffset;fa=a.Malloc(Float32Array,4);W=fa.byteOffset;Ub=a.Malloc(Float32Array,4);Ba=Ub.byteOffset;Vb=a.Malloc(Float32Array,3);vb=Vb.byteOffset;Wb=a.Malloc(Float32Array,3);wb=Wb.byteOffset;$a=a.Malloc(Int32Array,4);Na=$a.byteOffset;a.ColorSpace.SRGB=a.ColorSpace._MakeSRGB();a.ColorSpace.DISPLAY_P3=a.ColorSpace._MakeDisplayP3();a.ColorSpace.ADOBE_RGB=a.ColorSpace._MakeAdobeRGB();a.GlyphRunFlags={IsWhiteSpace:a._GlyphRunFlags_isWhiteSpace};a.Path.MakeFromCmds=
function(d){var h=n(d,"HEAPF32"),m=a.Path._MakeFromCmds(h,d.length);k(h,d);return m};a.Path.MakeFromVerbsPointsWeights=function(d,h,m){var t=n(d,"HEAPU8"),u=n(h,"HEAPF32"),x=n(m,"HEAPF32"),C=a.Path._MakeFromVerbsPointsWeights(t,d.length,u,h.length,x,m&&m.length||0);k(t,d);k(u,h);k(x,m);return C};a.Path.prototype.addArc=function(d,h,m){d=I(d);this._addArc(d,h,m);return this};a.Path.prototype.addCircle=function(d,h,m,t){this._addCircle(d,h,m,!!t);return this};a.Path.prototype.addOval=function(d,h,m){void 0===
m&&(m=1);d=I(d);this._addOval(d,!!h,m);return this};a.Path.prototype.addPath=function(){var d=Array.prototype.slice.call(arguments),h=d[0],m=!1;"boolean"===typeof d[d.length-1]&&(m=d.pop());if(1===d.length)this._addPath(h,1,0,0,0,1,0,0,0,1,m);else if(2===d.length)d=d[1],this._addPath(h,d[0],d[1],d[2],d[3],d[4],d[5],d[6]||0,d[7]||0,d[8]||1,m);else if(7===d.length||10===d.length)this._addPath(h,d[1],d[2],d[3],d[4],d[5],d[6],d[7]||0,d[8]||0,d[9]||1,m);else return null;return this};a.Path.prototype.addPoly=
function(d,h){var m=n(d,"HEAPF32");this._addPoly(m,d.length/2,h);k(m,d);return this};a.Path.prototype.addRect=function(d,h){d=I(d);this._addRect(d,!!h);return this};a.Path.prototype.addRRect=function(d,h){d=R(d);this._addRRect(d,!!h);return this};a.Path.prototype.addVerbsPointsWeights=function(d,h,m){var t=n(d,"HEAPU8"),u=n(h,"HEAPF32"),x=n(m,"HEAPF32");this._addVerbsPointsWeights(t,d.length,u,h.length,x,m&&m.length||0);k(t,d);k(u,h);k(x,m)};a.Path.prototype.arc=function(d,h,m,t,u,x){d=a.LTRBRect(d-
m,h-m,d+m,h+m);u=(u-t)/Math.PI*180-360*!!x;x=new a.Path;x.addArc(d,t/Math.PI*180,u);this.addPath(x,!0);x.delete();return this};a.Path.prototype.arcToOval=function(d,h,m,t){d=I(d);this._arcToOval(d,h,m,t);return this};a.Path.prototype.arcToRotated=function(d,h,m,t,u,x,C){this._arcToRotated(d,h,m,!!t,!!u,x,C);return this};a.Path.prototype.arcToTangent=function(d,h,m,t,u){this._arcToTangent(d,h,m,t,u);return this};a.Path.prototype.close=function(){this._close();return this};a.Path.prototype.conicTo=
function(d,h,m,t,u){this._conicTo(d,h,m,t,u);return this};a.Path.prototype.computeTightBounds=function(d){this._computeTightBounds(W);var h=fa.toTypedArray();return d?(d.set(h),d):h.slice()};a.Path.prototype.cubicTo=function(d,h,m,t,u,x){this._cubicTo(d,h,m,t,u,x);return this};a.Path.prototype.dash=function(d,h,m){return this._dash(d,h,m)?this:null};a.Path.prototype.getBounds=function(d){this._getBounds(W);var h=fa.toTypedArray();return d?(d.set(h),d):h.slice()};a.Path.prototype.lineTo=function(d,
h){this._lineTo(d,h);return this};a.Path.prototype.moveTo=function(d,h){this._moveTo(d,h);return this};a.Path.prototype.offset=function(d,h){this._transform(1,0,d,0,1,h,0,0,1);return this};a.Path.prototype.quadTo=function(d,h,m,t){this._quadTo(d,h,m,t);return this};a.Path.prototype.rArcTo=function(d,h,m,t,u,x,C){this._rArcTo(d,h,m,t,u,x,C);return this};a.Path.prototype.rConicTo=function(d,h,m,t,u){this._rConicTo(d,h,m,t,u);return this};a.Path.prototype.rCubicTo=function(d,h,m,t,u,x){this._rCubicTo(d,
h,m,t,u,x);return this};a.Path.prototype.rLineTo=function(d,h){this._rLineTo(d,h);return this};a.Path.prototype.rMoveTo=function(d,h){this._rMoveTo(d,h);return this};a.Path.prototype.rQuadTo=function(d,h,m,t){this._rQuadTo(d,h,m,t);return this};a.Path.prototype.stroke=function(d){d=d||{};d.width=d.width||1;d.miter_limit=d.miter_limit||4;d.cap=d.cap||a.StrokeCap.Butt;d.join=d.join||a.StrokeJoin.Miter;d.precision=d.precision||1;return this._stroke(d)?this:null};a.Path.prototype.transform=function(){if(1===
arguments.length){var d=arguments[0];this._transform(d[0],d[1],d[2],d[3],d[4],d[5],d[6]||0,d[7]||0,d[8]||1)}else if(6===arguments.length||9===arguments.length)d=arguments,this._transform(d[0],d[1],d[2],d[3],d[4],d[5],d[6]||0,d[7]||0,d[8]||1);else throw"transform expected to take 1 or 9 arguments. Got "+arguments.length;return this};a.Path.prototype.trim=function(d,h,m){return this._trim(d,h,!!m)?this:null};a.Image.prototype.encodeToBytes=function(d,h){var m=a.Be();d=d||a.ImageFormat.PNG;h=h||100;
return m?this._encodeToBytes(d,h,m):this._encodeToBytes(d,h)};a.Image.prototype.makeShaderCubic=function(d,h,m,t,u){u=p(u);return this._makeShaderCubic(d,h,m,t,u)};a.Image.prototype.makeShaderOptions=function(d,h,m,t,u){u=p(u);return this._makeShaderOptions(d,h,m,t,u)};a.Image.prototype.readPixels=function(d,h,m,t,u){var x=a.Be();return g(this,d,h,m,t,u,x)};a.Canvas.prototype.clear=function(d){a.$d(this.Zd);d=w(d);this._clear(d)};a.Canvas.prototype.clipRRect=function(d,h,m){a.$d(this.Zd);d=R(d);this._clipRRect(d,
h,m)};a.Canvas.prototype.clipRect=function(d,h,m){a.$d(this.Zd);d=I(d);this._clipRect(d,h,m)};a.Canvas.prototype.concat=function(d){a.$d(this.Zd);d=v(d);this._concat(d)};a.Canvas.prototype.drawArc=function(d,h,m,t,u){a.$d(this.Zd);d=I(d);this._drawArc(d,h,m,t,u)};a.Canvas.prototype.drawAtlas=function(d,h,m,t,u,x,C){if(d&&t&&h&&m&&h.length===m.length){a.$d(this.Zd);u||(u=a.BlendMode.SrcOver);var G=n(h,"HEAPF32"),F=n(m,"HEAPF32"),T=m.length/4,U=n(c(x),"HEAPU32");if(C&&"B"in C&&"C"in C)this._drawAtlasCubic(d,
F,G,U,T,u,C.B,C.C,t);else{let q=a.FilterMode.Linear,y=a.MipmapMode.None;C&&(q=C.filter,"mipmap"in C&&(y=C.mipmap));this._drawAtlasOptions(d,F,G,U,T,u,q,y,t)}k(G,h);k(F,m);k(U,x)}};a.Canvas.prototype.drawCircle=function(d,h,m,t){a.$d(this.Zd);this._drawCircle(d,h,m,t)};a.Canvas.prototype.drawColor=function(d,h){a.$d(this.Zd);d=w(d);void 0!==h?this._drawColor(d,h):this._drawColor(d)};a.Canvas.prototype.drawColorInt=function(d,h){a.$d(this.Zd);this._drawColorInt(d,h||a.BlendMode.SrcOver)};a.Canvas.prototype.drawColorComponents=
function(d,h,m,t,u){a.$d(this.Zd);d=A(d,h,m,t);void 0!==u?this._drawColor(d,u):this._drawColor(d)};a.Canvas.prototype.drawDRRect=function(d,h,m){a.$d(this.Zd);d=R(d,ub);h=R(h,Zb);this._drawDRRect(d,h,m)};a.Canvas.prototype.drawImage=function(d,h,m,t){a.$d(this.Zd);this._drawImage(d,h,m,t||null)};a.Canvas.prototype.drawImageCubic=function(d,h,m,t,u,x){a.$d(this.Zd);this._drawImageCubic(d,h,m,t,u,x||null)};a.Canvas.prototype.drawImageOptions=function(d,h,m,t,u,x){a.$d(this.Zd);this._drawImageOptions(d,
h,m,t,u,x||null)};a.Canvas.prototype.drawImageNine=function(d,h,m,t,u){a.$d(this.Zd);h=n(h,"HEAP32",Na);m=I(m);this._drawImageNine(d,h,m,t,u||null)};a.Canvas.prototype.drawImageRect=function(d,h,m,t,u){a.$d(this.Zd);I(h,W);I(m,Ba);this._drawImageRect(d,W,Ba,t,!!u)};a.Canvas.prototype.drawImageRectCubic=function(d,h,m,t,u,x){a.$d(this.Zd);I(h,W);I(m,Ba);this._drawImageRectCubic(d,W,Ba,t,u,x||null)};a.Canvas.prototype.drawImageRectOptions=function(d,h,m,t,u,x){a.$d(this.Zd);I(h,W);I(m,Ba);this._drawImageRectOptions(d,
W,Ba,t,u,x||null)};a.Canvas.prototype.drawLine=function(d,h,m,t,u){a.$d(this.Zd);this._drawLine(d,h,m,t,u)};a.Canvas.prototype.drawOval=function(d,h){a.$d(this.Zd);d=I(d);this._drawOval(d,h)};a.Canvas.prototype.drawPaint=function(d){a.$d(this.Zd);this._drawPaint(d)};a.Canvas.prototype.drawParagraph=function(d,h,m){a.$d(this.Zd);this._drawParagraph(d,h,m)};a.Canvas.prototype.drawPatch=function(d,h,m,t,u){if(24>d.length)throw"Need 12 cubic points";if(h&&4>h.length)throw"Need 4 colors";if(m&&8>m.length)throw"Need 4 shader coordinates";
a.$d(this.Zd);const x=n(d,"HEAPF32"),C=h?n(c(h),"HEAPU32"):K,G=m?n(m,"HEAPF32"):K;t||(t=a.BlendMode.Modulate);this._drawPatch(x,C,G,t,u);k(G,m);k(C,h);k(x,d)};a.Canvas.prototype.drawPath=function(d,h){a.$d(this.Zd);this._drawPath(d,h)};a.Canvas.prototype.drawPicture=function(d){a.$d(this.Zd);this._drawPicture(d)};a.Canvas.prototype.drawPoints=function(d,h,m){a.$d(this.Zd);var t=n(h,"HEAPF32");this._drawPoints(d,t,h.length/2,m);k(t,h)};a.Canvas.prototype.drawRRect=function(d,h){a.$d(this.Zd);d=R(d);
this._drawRRect(d,h)};a.Canvas.prototype.drawRect=function(d,h){a.$d(this.Zd);d=I(d);this._drawRect(d,h)};a.Canvas.prototype.drawRect4f=function(d,h,m,t,u){a.$d(this.Zd);this._drawRect4f(d,h,m,t,u)};a.Canvas.prototype.drawShadow=function(d,h,m,t,u,x,C){a.$d(this.Zd);var G=n(u,"HEAPF32"),F=n(x,"HEAPF32");h=n(h,"HEAPF32",vb);m=n(m,"HEAPF32",wb);this._drawShadow(d,h,m,t,G,F,C);k(G,u);k(F,x)};a.getShadowLocalBounds=function(d,h,m,t,u,x,C){d=p(d);m=n(m,"HEAPF32",vb);t=n(t,"HEAPF32",wb);if(!this._getShadowLocalBounds(d,
h,m,t,u,x,W))return null;h=fa.toTypedArray();return C?(C.set(h),C):h.slice()};a.Canvas.prototype.drawTextBlob=function(d,h,m,t){a.$d(this.Zd);this._drawTextBlob(d,h,m,t)};a.Canvas.prototype.drawVertices=function(d,h,m){a.$d(this.Zd);this._drawVertices(d,h,m)};a.Canvas.prototype.getDeviceClipBounds=function(d){this._getDeviceClipBounds(Na);var h=$a.toTypedArray();d?d.set(h):d=h.slice();return d};a.Canvas.prototype.quickReject=function(d){d=I(d);return this._quickReject(d)};a.Canvas.prototype.getLocalToDevice=
function(){this._getLocalToDevice(ma);for(var d=ma,h=Array(16),m=0;16>m;m++)h[m]=a.HEAPF32[d/4+m];return h};a.Canvas.prototype.getTotalMatrix=function(){this._getTotalMatrix(P);for(var d=Array(9),h=0;9>h;h++)d[h]=a.HEAPF32[P/4+h];return d};a.Canvas.prototype.makeSurface=function(d){d=this._makeSurface(d);d.Zd=this.Zd;return d};a.Canvas.prototype.readPixels=function(d,h,m,t,u){a.$d(this.Zd);return g(this,d,h,m,t,u)};a.Canvas.prototype.saveLayer=function(d,h,m,t,u){h=I(h);return this._saveLayer(d||
null,h,m||null,t||0,u||a.TileMode.Clamp)};a.Canvas.prototype.writePixels=function(d,h,m,t,u,x,C,G){if(d.byteLength%(h*m))throw"pixels length must be a multiple of the srcWidth * srcHeight";a.$d(this.Zd);var F=d.byteLength/(h*m);x=x||a.AlphaType.Unpremul;C=C||a.ColorType.RGBA_8888;G=G||a.ColorSpace.SRGB;var T=F*h;F=n(d,"HEAPU8");h=this._writePixels({width:h,height:m,colorType:C,alphaType:x,colorSpace:G},F,T,t,u);k(F,d);return h};a.ColorFilter.MakeBlend=function(d,h,m){d=w(d);m=m||a.ColorSpace.SRGB;
return a.ColorFilter._MakeBlend(d,h,m)};a.ColorFilter.MakeMatrix=function(d){if(!d||20!==d.length)throw"invalid color matrix";var h=n(d,"HEAPF32"),m=a.ColorFilter._makeMatrix(h);k(h,d);return m};a.ContourMeasure.prototype.getPosTan=function(d,h){this._getPosTan(d,W);d=fa.toTypedArray();return h?(h.set(d),h):d.slice()};a.ImageFilter.prototype.getOutputBounds=function(d,h,m){d=I(d,W);h=p(h);this._getOutputBounds(d,h,Na);h=$a.toTypedArray();return m?(m.set(h),m):h.slice()};a.ImageFilter.MakeDropShadow=
function(d,h,m,t,u,x){u=w(u,ia);return a.ImageFilter._MakeDropShadow(d,h,m,t,u,x)};a.ImageFilter.MakeDropShadowOnly=function(d,h,m,t,u,x){u=w(u,ia);return a.ImageFilter._MakeDropShadowOnly(d,h,m,t,u,x)};a.ImageFilter.MakeImage=function(d,h,m,t){m=I(m,W);t=I(t,Ba);if("B"in h&&"C"in h)return a.ImageFilter._MakeImageCubic(d,h.B,h.C,m,t);const u=h.filter;let x=a.MipmapMode.None;"mipmap"in h&&(x=h.mipmap);return a.ImageFilter._MakeImageOptions(d,u,x,m,t)};a.ImageFilter.MakeMatrixTransform=function(d,h,
m){d=p(d);if("B"in h&&"C"in h)return a.ImageFilter._MakeMatrixTransformCubic(d,h.B,h.C,m);const t=h.filter;let u=a.MipmapMode.None;"mipmap"in h&&(u=h.mipmap);return a.ImageFilter._MakeMatrixTransformOptions(d,t,u,m)};a.Paint.prototype.getColor=function(){this._getColor(ia);return D(ia)};a.Paint.prototype.setColor=function(d,h){h=h||null;d=w(d);this._setColor(d,h)};a.Paint.prototype.setColorComponents=function(d,h,m,t,u){u=u||null;d=A(d,h,m,t);this._setColor(d,u)};a.Path.prototype.getPoint=function(d,
h){this._getPoint(d,W);d=fa.toTypedArray();return h?(h[0]=d[0],h[1]=d[1],h):d.slice(0,2)};a.Picture.prototype.makeShader=function(d,h,m,t,u){t=p(t);u=I(u);return this._makeShader(d,h,m,t,u)};a.Picture.prototype.cullRect=function(d){this._cullRect(W);var h=fa.toTypedArray();return d?(d.set(h),d):h.slice()};a.PictureRecorder.prototype.beginRecording=function(d,h){d=I(d);return this._beginRecording(d,!!h)};a.Surface.prototype.getCanvas=function(){var d=this._getCanvas();d.Zd=this.Zd;return d};a.Surface.prototype.makeImageSnapshot=
function(d){a.$d(this.Zd);d=n(d,"HEAP32",Na);return this._makeImageSnapshot(d)};a.Surface.prototype.makeSurface=function(d){a.$d(this.Zd);d=this._makeSurface(d);d.Zd=this.Zd;return d};a.Surface.prototype.Ze=function(d,h){this.te||(this.te=this.getCanvas());return requestAnimationFrame(function(){a.$d(this.Zd);d(this.te);this.flush(h)}.bind(this))};a.Surface.prototype.requestAnimationFrame||(a.Surface.prototype.requestAnimationFrame=a.Surface.prototype.Ze);a.Surface.prototype.We=function(d,h){this.te||
(this.te=this.getCanvas());requestAnimationFrame(function(){a.$d(this.Zd);d(this.te);this.flush(h);this.dispose()}.bind(this))};a.Surface.prototype.drawOnce||(a.Surface.prototype.drawOnce=a.Surface.prototype.We);a.PathEffect.MakeDash=function(d,h){h||=0;if(!d.length||1===d.length%2)throw"Intervals array must have even length";var m=n(d,"HEAPF32");h=a.PathEffect._MakeDash(m,d.length,h);k(m,d);return h};a.PathEffect.MakeLine2D=function(d,h){h=p(h);return a.PathEffect._MakeLine2D(d,h)};a.PathEffect.MakePath2D=
function(d,h){d=p(d);return a.PathEffect._MakePath2D(d,h)};a.Shader.MakeColor=function(d,h){h=h||null;d=w(d);return a.Shader._MakeColor(d,h)};a.Shader.Blend=a.Shader.MakeBlend;a.Shader.Color=a.Shader.MakeColor;a.Shader.MakeLinearGradient=function(d,h,m,t,u,x,C,G){G=G||null;var F=l(m),T=n(t,"HEAPF32");C=C||0;x=p(x);var U=fa.toTypedArray();U.set(d);U.set(h,2);d=a.Shader._MakeLinearGradient(W,F.he,F.colorType,T,F.count,u,C,x,G);k(F.he,m);t&&k(T,t);return d};a.Shader.MakeRadialGradient=function(d,h,m,
t,u,x,C,G){G=G||null;var F=l(m),T=n(t,"HEAPF32");C=C||0;x=p(x);d=a.Shader._MakeRadialGradient(d[0],d[1],h,F.he,F.colorType,T,F.count,u,C,x,G);k(F.he,m);t&&k(T,t);return d};a.Shader.MakeSweepGradient=function(d,h,m,t,u,x,C,G,F,T){T=T||null;var U=l(m),q=n(t,"HEAPF32");C=C||0;G=G||0;F=F||360;x=p(x);d=a.Shader._MakeSweepGradient(d,h,U.he,U.colorType,q,U.count,u,G,F,C,x,T);k(U.he,m);t&&k(q,t);return d};a.Shader.MakeTwoPointConicalGradient=function(d,h,m,t,u,x,C,G,F,T){T=T||null;var U=l(u),q=n(x,"HEAPF32");
F=F||0;G=p(G);var y=fa.toTypedArray();y.set(d);y.set(m,2);d=a.Shader._MakeTwoPointConicalGradient(W,h,t,U.he,U.colorType,q,U.count,C,F,G,T);k(U.he,u);x&&k(q,x);return d};a.Vertices.prototype.bounds=function(d){this._bounds(W);var h=fa.toTypedArray();return d?(d.set(h),d):h.slice()};a.ce&&a.ce.forEach(function(d){d()})};a.computeTonalColors=function(g){var d=n(g.ambient,"HEAPF32"),h=n(g.spot,"HEAPF32");this._computeTonalColors(d,h);var m={ambient:D(d),spot:D(h)};k(d,g.ambient);k(h,g.spot);return m};
a.LTRBRect=function(g,d,h,m){return Float32Array.of(g,d,h,m)};a.XYWHRect=function(g,d,h,m){return Float32Array.of(g,d,g+h,d+m)};a.LTRBiRect=function(g,d,h,m){return Int32Array.of(g,d,h,m)};a.XYWHiRect=function(g,d,h,m){return Int32Array.of(g,d,g+h,d+m)};a.RRectXY=function(g,d,h){return Float32Array.of(g[0],g[1],g[2],g[3],d,h,d,h,d,h,d,h)};a.MakeAnimatedImageFromEncoded=function(g){g=new Uint8Array(g);var d=a._malloc(g.byteLength);a.HEAPU8.set(g,d);return(g=a._decodeAnimatedImage(d,g.byteLength))?
g:null};a.MakeImageFromEncoded=function(g){g=new Uint8Array(g);var d=a._malloc(g.byteLength);a.HEAPU8.set(g,d);return(g=a._decodeImage(d,g.byteLength))?g:null};var ab=null;a.MakeImageFromCanvasImageSource=function(g){var d=g.width,h=g.height;ab||=document.createElement("canvas");ab.width=d;ab.height=h;var m=ab.getContext("2d",{willReadFrequently:!0});m.drawImage(g,0,0);g=m.getImageData(0,0,d,h);return a.MakeImage({width:d,height:h,alphaType:a.AlphaType.Unpremul,colorType:a.ColorType.RGBA_8888,colorSpace:a.ColorSpace.SRGB},
g.data,4*d)};a.MakeImage=function(g,d,h){var m=a._malloc(d.length);a.HEAPU8.set(d,m);return a._MakeImage(g,m,d.length,h)};a.MakeVertices=function(g,d,h,m,t,u){var x=t&&t.length||0,C=0;h&&h.length&&(C|=1);m&&m.length&&(C|=2);void 0===u||u||(C|=4);g=new a._VerticesBuilder(g,d.length/2,x,C);n(d,"HEAPF32",g.positions());g.texCoords()&&n(h,"HEAPF32",g.texCoords());g.colors()&&n(c(m),"HEAPU32",g.colors());g.indices()&&n(t,"HEAPU16",g.indices());return g.detach()};(function(g){g.ce=g.ce||[];g.ce.push(function(){function d(q){q&&
(q.dir=0===q.dir?g.TextDirection.RTL:g.TextDirection.LTR);return q}function h(q){if(!q||!q.length)return[];for(var y=[],N=0;N<q.length;N+=5){var X=g.LTRBRect(q[N],q[N+1],q[N+2],q[N+3]),xa=g.TextDirection.LTR;0===q[N+4]&&(xa=g.TextDirection.RTL);y.push({rect:X,dir:xa})}g._free(q.byteOffset);return y}function m(q){q=q||{};void 0===q.weight&&(q.weight=g.FontWeight.Normal);q.width=q.width||g.FontWidth.Normal;q.slant=q.slant||g.FontSlant.Upright;return q}function t(q){if(!q||!q.length)return K;for(var y=
[],N=0;N<q.length;N++){var X=u(q[N]);y.push(X)}return n(y,"HEAPU32")}function u(q){if(G[q])return G[q];var y=qa(q)+1,N=g._malloc(y);ra(q,N,y);return G[q]=N}function x(q){q._colorPtr=w(q.color);q._foregroundColorPtr=K;q._backgroundColorPtr=K;q._decorationColorPtr=K;q.foregroundColor&&(q._foregroundColorPtr=w(q.foregroundColor,F));q.backgroundColor&&(q._backgroundColorPtr=w(q.backgroundColor,T));q.decorationColor&&(q._decorationColorPtr=w(q.decorationColor,U));Array.isArray(q.fontFamilies)&&q.fontFamilies.length?
(q._fontFamiliesPtr=t(q.fontFamilies),q._fontFamiliesLen=q.fontFamilies.length):(q._fontFamiliesPtr=K,q._fontFamiliesLen=0);if(q.locale){var y=q.locale;q._localePtr=u(y);q._localeLen=qa(y)}else q._localePtr=K,q._localeLen=0;if(Array.isArray(q.shadows)&&q.shadows.length){y=q.shadows;var N=y.map(function(na){return na.color||g.BLACK}),X=y.map(function(na){return na.blurRadius||0});q._shadowLen=y.length;for(var xa=g._malloc(8*y.length),xb=xa/4,yb=0;yb<y.length;yb++){var $b=y[yb].offset||[0,0];g.HEAPF32[xb]=
$b[0];g.HEAPF32[xb+1]=$b[1];xb+=2}q._shadowColorsPtr=l(N).he;q._shadowOffsetsPtr=xa;q._shadowBlurRadiiPtr=n(X,"HEAPF32")}else q._shadowLen=0,q._shadowColorsPtr=K,q._shadowOffsetsPtr=K,q._shadowBlurRadiiPtr=K;Array.isArray(q.fontFeatures)&&q.fontFeatures.length?(y=q.fontFeatures,N=y.map(function(na){return na.name}),X=y.map(function(na){return na.value}),q._fontFeatureLen=y.length,q._fontFeatureNamesPtr=t(N),q._fontFeatureValuesPtr=n(X,"HEAPU32")):(q._fontFeatureLen=0,q._fontFeatureNamesPtr=K,q._fontFeatureValuesPtr=
K);Array.isArray(q.fontVariations)&&q.fontVariations.length?(y=q.fontVariations,N=y.map(function(na){return na.axis}),X=y.map(function(na){return na.value}),q._fontVariationLen=y.length,q._fontVariationAxesPtr=t(N),q._fontVariationValuesPtr=n(X,"HEAPF32")):(q._fontVariationLen=0,q._fontVariationAxesPtr=K,q._fontVariationValuesPtr=K)}function C(q){g._free(q._fontFamiliesPtr);g._free(q._shadowColorsPtr);g._free(q._shadowOffsetsPtr);g._free(q._shadowBlurRadiiPtr);g._free(q._fontFeatureNamesPtr);g._free(q._fontFeatureValuesPtr);
g._free(q._fontVariationAxesPtr);g._free(q._fontVariationValuesPtr)}g.Paragraph.prototype.getRectsForRange=function(q,y,N,X){q=this._getRectsForRange(q,y,N,X);return h(q)};g.Paragraph.prototype.getRectsForPlaceholders=function(){var q=this._getRectsForPlaceholders();return h(q)};g.Paragraph.prototype.getGlyphInfoAt=function(q){return d(this._getGlyphInfoAt(q))};g.Paragraph.prototype.getClosestGlyphInfoAtCoordinate=function(q,y){return d(this._getClosestGlyphInfoAtCoordinate(q,y))};g.TypefaceFontProvider.prototype.registerFont=
function(q,y){q=g.Typeface.MakeTypefaceFromData(q);if(!q)return null;y=u(y);this._registerFont(q,y)};g.ParagraphStyle=function(q){q.disableHinting=q.disableHinting||!1;if(q.ellipsis){var y=q.ellipsis;q._ellipsisPtr=u(y);q._ellipsisLen=qa(y)}else q._ellipsisPtr=K,q._ellipsisLen=0;null==q.heightMultiplier&&(q.heightMultiplier=-1);q.maxLines=q.maxLines||0;q.replaceTabCharacters=q.replaceTabCharacters||!1;y=(y=q.strutStyle)||{};y.strutEnabled=y.strutEnabled||!1;y.strutEnabled&&Array.isArray(y.fontFamilies)&&
y.fontFamilies.length?(y._fontFamiliesPtr=t(y.fontFamilies),y._fontFamiliesLen=y.fontFamilies.length):(y._fontFamiliesPtr=K,y._fontFamiliesLen=0);y.fontStyle=m(y.fontStyle);null==y.fontSize&&(y.fontSize=-1);null==y.heightMultiplier&&(y.heightMultiplier=-1);y.halfLeading=y.halfLeading||!1;y.leading=y.leading||0;y.forceStrutHeight=y.forceStrutHeight||!1;q.strutStyle=y;q.textAlign=q.textAlign||g.TextAlign.Start;q.textDirection=q.textDirection||g.TextDirection.LTR;q.textHeightBehavior=q.textHeightBehavior||
g.TextHeightBehavior.All;q.textStyle=g.TextStyle(q.textStyle);q.applyRoundingHack=!1!==q.applyRoundingHack;return q};g.TextStyle=function(q){q.color||(q.color=g.BLACK);q.decoration=q.decoration||0;q.decorationThickness=q.decorationThickness||0;q.decorationStyle=q.decorationStyle||g.DecorationStyle.Solid;q.textBaseline=q.textBaseline||g.TextBaseline.Alphabetic;null==q.fontSize&&(q.fontSize=-1);q.letterSpacing=q.letterSpacing||0;q.wordSpacing=q.wordSpacing||0;null==q.heightMultiplier&&(q.heightMultiplier=
-1);q.halfLeading=q.halfLeading||!1;q.fontStyle=m(q.fontStyle);return q};var G={},F=g._malloc(16),T=g._malloc(16),U=g._malloc(16);g.ParagraphBuilder.Make=function(q,y){x(q.textStyle);y=g.ParagraphBuilder._Make(q,y);C(q.textStyle);return y};g.ParagraphBuilder.MakeFromFontProvider=function(q,y){x(q.textStyle);y=g.ParagraphBuilder._MakeFromFontProvider(q,y);C(q.textStyle);return y};g.ParagraphBuilder.MakeFromFontCollection=function(q,y){x(q.textStyle);y=g.ParagraphBuilder._MakeFromFontCollection(q,y);
C(q.textStyle);return y};g.ParagraphBuilder.ShapeText=function(q,y,N){let X=0;for(const xa of y)X+=xa.length;if(X!==q.length)throw"Accumulated block lengths must equal text.length";return g.ParagraphBuilder._ShapeText(q,y,N)};g.ParagraphBuilder.prototype.pushStyle=function(q){x(q);this._pushStyle(q);C(q)};g.ParagraphBuilder.prototype.pushPaintStyle=function(q,y,N){x(q);this._pushPaintStyle(q,y,N);C(q)};g.ParagraphBuilder.prototype.addPlaceholder=function(q,y,N,X,xa){N=N||g.PlaceholderAlignment.Baseline;
X=X||g.TextBaseline.Alphabetic;this._addPlaceholder(q||0,y||0,N,X,xa||0)};g.ParagraphBuilder.prototype.setWordsUtf8=function(q){var y=n(q,"HEAPU32");this._setWordsUtf8(y,q&&q.length||0);k(y,q)};g.ParagraphBuilder.prototype.setWordsUtf16=function(q){var y=n(q,"HEAPU32");this._setWordsUtf16(y,q&&q.length||0);k(y,q)};g.ParagraphBuilder.prototype.setGraphemeBreaksUtf8=function(q){var y=n(q,"HEAPU32");this._setGraphemeBreaksUtf8(y,q&&q.length||0);k(y,q)};g.ParagraphBuilder.prototype.setGraphemeBreaksUtf16=
function(q){var y=n(q,"HEAPU32");this._setGraphemeBreaksUtf16(y,q&&q.length||0);k(y,q)};g.ParagraphBuilder.prototype.setLineBreaksUtf8=function(q){var y=n(q,"HEAPU32");this._setLineBreaksUtf8(y,q&&q.length||0);k(y,q)};g.ParagraphBuilder.prototype.setLineBreaksUtf16=function(q){var y=n(q,"HEAPU32");this._setLineBreaksUtf16(y,q&&q.length||0);k(y,q)}})})(r);a.ce=a.ce||[];a.ce.push(function(){a.Path.prototype.op=function(g,d){return this._op(g,d)?this:null};a.Path.prototype.simplify=function(){return this._simplify()?
this:null}});a.ce=a.ce||[];a.ce.push(function(){a.Canvas.prototype.drawText=function(g,d,h,m,t){var u=qa(g),x=a._malloc(u+1);ra(g,x,u+1);this._drawSimpleText(x,u,d,h,t,m);a._free(x)};a.Canvas.prototype.drawGlyphs=function(g,d,h,m,t,u){if(!(2*g.length<=d.length))throw"Not enough positions for the array of gyphs";a.$d(this.Zd);const x=n(g,"HEAPU16"),C=n(d,"HEAPF32");this._drawGlyphs(g.length,x,C,h,m,t,u);k(C,d);k(x,g)};a.Font.prototype.getGlyphBounds=function(g,d,h){var m=n(g,"HEAPU16"),t=a._malloc(16*
g.length);this._getGlyphWidthBounds(m,g.length,K,t,d||null);d=new Float32Array(a.HEAPU8.buffer,t,4*g.length);k(m,g);if(h)return h.set(d),a._free(t),h;g=Float32Array.from(d);a._free(t);return g};a.Font.prototype.getGlyphIDs=function(g,d,h){d||(d=g.length);var m=qa(g)+1,t=a._malloc(m);ra(g,t,m);g=a._malloc(2*d);d=this._getGlyphIDs(t,m-1,d,g);a._free(t);if(0>d)return a._free(g),null;t=new Uint16Array(a.HEAPU8.buffer,g,d);if(h)return h.set(t),a._free(g),h;h=Uint16Array.from(t);a._free(g);return h};a.Font.prototype.getGlyphIntercepts=
function(g,d,h,m){var t=n(g,"HEAPU16"),u=n(d,"HEAPF32");return this._getGlyphIntercepts(t,g.length,!(g&&g._ck),u,d.length,!(d&&d._ck),h,m)};a.Font.prototype.getGlyphWidths=function(g,d,h){var m=n(g,"HEAPU16"),t=a._malloc(4*g.length);this._getGlyphWidthBounds(m,g.length,t,K,d||null);d=new Float32Array(a.HEAPU8.buffer,t,g.length);k(m,g);if(h)return h.set(d),a._free(t),h;g=Float32Array.from(d);a._free(t);return g};a.FontMgr.FromData=function(){if(!arguments.length)return null;var g=arguments;1===g.length&&
Array.isArray(g[0])&&(g=arguments[0]);if(!g.length)return null;for(var d=[],h=[],m=0;m<g.length;m++){var t=new Uint8Array(g[m]),u=n(t,"HEAPU8");d.push(u);h.push(t.byteLength)}d=n(d,"HEAPU32");h=n(h,"HEAPU32");g=a.FontMgr._fromData(d,h,g.length);a._free(d);a._free(h);return g};a.Typeface.MakeTypefaceFromData=function(g){g=new Uint8Array(g);var d=n(g,"HEAPU8");return(g=a.Typeface._MakeTypefaceFromData(d,g.byteLength))?g:null};a.Typeface.MakeFreeTypeFaceFromData=a.Typeface.MakeTypefaceFromData;a.Typeface.prototype.getGlyphIDs=
function(g,d,h){d||(d=g.length);var m=qa(g)+1,t=a._malloc(m);ra(g,t,m);g=a._malloc(2*d);d=this._getGlyphIDs(t,m-1,d,g);a._free(t);if(0>d)return a._free(g),null;t=new Uint16Array(a.HEAPU8.buffer,g,d);if(h)return h.set(t),a._free(g),h;h=Uint16Array.from(t);a._free(g);return h};a.TextBlob.MakeOnPath=function(g,d,h,m){if(g&&g.length&&d&&d.countPoints()){if(1===d.countPoints())return this.MakeFromText(g,h);m||=0;var t=h.getGlyphIDs(g);t=h.getGlyphWidths(t);var u=[];d=new a.ContourMeasureIter(d,!1,1);for(var x=
d.next(),C=new Float32Array(4),G=0;G<g.length&&x;G++){var F=t[G];m+=F/2;if(m>x.length()){x.delete();x=d.next();if(!x){g=g.substring(0,G);break}m=F/2}x.getPosTan(m,C);var T=C[2],U=C[3];u.push(T,U,C[0]-F/2*T,C[1]-F/2*U);m+=F/2}g=this.MakeFromRSXform(g,u,h);x&&x.delete();d.delete();return g}};a.TextBlob.MakeFromRSXform=function(g,d,h){var m=qa(g)+1,t=a._malloc(m);ra(g,t,m);g=n(d,"HEAPF32");h=a.TextBlob._MakeFromRSXform(t,m-1,g,h);a._free(t);return h?h:null};a.TextBlob.MakeFromRSXformGlyphs=function(g,
d,h){var m=n(g,"HEAPU16");d=n(d,"HEAPF32");h=a.TextBlob._MakeFromRSXformGlyphs(m,2*g.length,d,h);k(m,g);return h?h:null};a.TextBlob.MakeFromGlyphs=function(g,d){var h=n(g,"HEAPU16");d=a.TextBlob._MakeFromGlyphs(h,2*g.length,d);k(h,g);return d?d:null};a.TextBlob.MakeFromText=function(g,d){var h=qa(g)+1,m=a._malloc(h);ra(g,m,h);g=a.TextBlob._MakeFromText(m,h-1,d);a._free(m);return g?g:null};a.MallocGlyphIDs=function(g){return a.Malloc(Uint16Array,g)}});a.ce=a.ce||[];a.ce.push(function(){a.MakePicture=
function(g){g=new Uint8Array(g);var d=a._malloc(g.byteLength);a.HEAPU8.set(g,d);return(g=a._MakePicture(d,g.byteLength))?g:null}});a.ce=a.ce||[];a.ce.push(function(){a.RuntimeEffect.Make=function(g,d){return a.RuntimeEffect._Make(g,{onError:d||function(h){console.log("RuntimeEffect error",h)}})};a.RuntimeEffect.MakeForBlender=function(g,d){return a.RuntimeEffect._MakeForBlender(g,{onError:d||function(h){console.log("RuntimeEffect error",h)}})};a.RuntimeEffect.prototype.makeShader=function(g,d){var h=
!g._ck,m=n(g,"HEAPF32");d=p(d);return this._makeShader(m,4*g.length,h,d)};a.RuntimeEffect.prototype.makeShaderWithChildren=function(g,d,h){var m=!g._ck,t=n(g,"HEAPF32");h=p(h);for(var u=[],x=0;x<d.length;x++)u.push(d[x].Yd.ae);d=n(u,"HEAPU32");return this._makeShaderWithChildren(t,4*g.length,m,d,u.length,h)};a.RuntimeEffect.prototype.makeBlender=function(g){var d=!g._ck,h=n(g,"HEAPF32");return this._makeBlender(h,4*g.length,d)}})})(r);var sa=Object.assign({},r),ta="",ua,va;
if(ea||ha)ha?ta=self.location.href:"undefined"!=typeof document&&document.currentScript&&(ta=document.currentScript.src),_scriptName&&(ta=_scriptName),ta.startsWith("blob:")?ta="":ta=ta.substr(0,ta.replace(/[?#].*/,"").lastIndexOf("/")+1),ha&&(va=a=>{var b=new XMLHttpRequest;b.open("GET",a,!1);b.responseType="arraybuffer";b.send(null);return new Uint8Array(b.response)}),ua=a=>fetch(a,{credentials:"same-origin"}).then(b=>b.ok?b.arrayBuffer():Promise.reject(Error(b.status+" : "+b.url)));
var wa=console.log.bind(console),ya=console.error.bind(console);Object.assign(r,sa);sa=null;var za,Aa=!1,Ca,B,Da,Fa,E,H,J,Ga;function Ha(){var a=za.buffer;r.HEAP8=Ca=new Int8Array(a);r.HEAP16=Da=new Int16Array(a);r.HEAPU8=B=new Uint8Array(a);r.HEAPU16=Fa=new Uint16Array(a);r.HEAP32=E=new Int32Array(a);r.HEAPU32=H=new Uint32Array(a);r.HEAPF32=J=new Float32Array(a);r.HEAPF64=Ga=new Float64Array(a)}var Ia=[],Ja=[],Ka=[],La=0,Ma=null,Oa=null;
function Pa(a){a="Aborted("+a+")";ya(a);Aa=!0;a=new WebAssembly.RuntimeError(a+". Build with -sASSERTIONS for more info.");ca(a);throw a;}var Qa=a=>a.startsWith("data:application/octet-stream;base64,"),Ra;function Sa(a){return ua(a).then(b=>new Uint8Array(b),()=>{if(va)var b=va(a);else throw"both async and sync fetching of the wasm failed";return b})}function Ta(a,b,c){return Sa(a).then(e=>WebAssembly.instantiate(e,b)).then(c,e=>{ya(`failed to asynchronously prepare wasm: ${e}`);Pa(e)})}
function Ua(a,b){var c=Ra;return"function"!=typeof WebAssembly.instantiateStreaming||Qa(c)||"function"!=typeof fetch?Ta(c,a,b):fetch(c,{credentials:"same-origin"}).then(e=>WebAssembly.instantiateStreaming(e,a).then(b,function(f){ya(`wasm streaming compile failed: ${f}`);ya("falling back to ArrayBuffer instantiation");return Ta(c,a,b)}))}function Va(a){this.name="ExitStatus";this.message=`Program terminated with exit(${a})`;this.status=a}var Wa=a=>{a.forEach(b=>b(r))};
class Xa{constructor(a){this.ae=a-24}}
var Ya=0,Za=0,bb="undefined"!=typeof TextDecoder?new TextDecoder:void 0,cb=(a,b=0,c=NaN)=>{var e=b+c;for(c=b;a[c]&&!(c>=e);)++c;if(16<c-b&&a.buffer&&bb)return bb.decode(a.subarray(b,c));for(e="";b<c;){var f=a[b++];if(f&128){var k=a[b++]&63;if(192==(f&224))e+=String.fromCharCode((f&31)<<6|k);else{var n=a[b++]&63;f=224==(f&240)?(f&15)<<12|k<<6|n:(f&7)<<18|k<<12|n<<6|a[b++]&63;65536>f?e+=String.fromCharCode(f):(f-=65536,e+=String.fromCharCode(55296|f>>10,56320|f&1023))}}else e+=String.fromCharCode(f)}return e},
db={},eb=a=>{for(;a.length;){var b=a.pop();a.pop()(b)}};function fb(a){return this.fromWireType(H[a>>2])}
var gb={},hb={},ib={},jb,lb=(a,b,c)=>{function e(l){l=c(l);if(l.length!==a.length)throw new jb("Mismatched type converter count");for(var p=0;p<a.length;++p)kb(a[p],l[p])}a.forEach(l=>ib[l]=b);var f=Array(b.length),k=[],n=0;b.forEach((l,p)=>{hb.hasOwnProperty(l)?f[p]=hb[l]:(k.push(l),gb.hasOwnProperty(l)||(gb[l]=[]),gb[l].push(()=>{f[p]=hb[l];++n;n===k.length&&e(f)}))});0===k.length&&e(f)},mb,L=a=>{for(var b="";B[a];)b+=mb[B[a++]];return b},M;
function nb(a,b,c={}){var e=b.name;if(!a)throw new M(`type "${e}" must have a positive integer typeid pointer`);if(hb.hasOwnProperty(a)){if(c.lf)return;throw new M(`Cannot register type '${e}' twice`);}hb[a]=b;delete ib[a];gb.hasOwnProperty(a)&&(b=gb[a],delete gb[a],b.forEach(f=>f()))}function kb(a,b,c={}){return nb(a,b,c)}
var ob=a=>{throw new M(a.Yd.de.be.name+" instance already deleted");},pb=!1,qb=()=>{},rb=(a,b,c)=>{if(b===c)return a;if(void 0===c.ge)return null;a=rb(a,b,c.ge);return null===a?null:c.cf(a)},sb={},tb={},zb=(a,b)=>{if(void 0===b)throw new M("ptr should not be undefined");for(;a.ge;)b=a.ye(b),a=a.ge;return tb[b]},Bb=(a,b)=>{if(!b.de||!b.ae)throw new jb("makeClassHandle requires ptr and ptrType");if(!!b.ie!==!!b.ee)throw new jb("Both smartPtrType and smartPtr must be specified");b.count={value:1};return Ab(Object.create(a,
{Yd:{value:b,writable:!0}}))},Ab=a=>{if("undefined"===typeof FinalizationRegistry)return Ab=b=>b,a;pb=new FinalizationRegistry(b=>{b=b.Yd;--b.count.value;0===b.count.value&&(b.ee?b.ie.ne(b.ee):b.de.be.ne(b.ae))});Ab=b=>{var c=b.Yd;c.ee&&pb.register(b,{Yd:c},b);return b};qb=b=>{pb.unregister(b)};return Ab(a)},Cb=[];function Db(){}
var Eb=(a,b)=>Object.defineProperty(b,"name",{value:a}),Fb=(a,b,c)=>{if(void 0===a[b].fe){var e=a[b];a[b]=function(...f){if(!a[b].fe.hasOwnProperty(f.length))throw new M(`Function '${c}' called with an invalid number of arguments (${f.length}) - expects one of (${a[b].fe})!`);return a[b].fe[f.length].apply(this,f)};a[b].fe=[];a[b].fe[e.oe]=e}},Gb=(a,b,c)=>{if(r.hasOwnProperty(a)){if(void 0===c||void 0!==r[a].fe&&void 0!==r[a].fe[c])throw new M(`Cannot register public name '${a}' twice`);Fb(r,a,a);
if(r[a].fe.hasOwnProperty(c))throw new M(`Cannot register multiple overloads of a function with the same number of arguments (${c})!`);r[a].fe[c]=b}else r[a]=b,r[a].oe=c},Hb=a=>{a=a.replace(/[^a-zA-Z0-9_]/g,"$");var b=a.charCodeAt(0);return 48<=b&&57>=b?`_${a}`:a};function Ib(a,b,c,e,f,k,n,l){this.name=a;this.constructor=b;this.se=c;this.ne=e;this.ge=f;this.ff=k;this.ye=n;this.cf=l;this.pf=[]}
var Jb=(a,b,c)=>{for(;b!==c;){if(!b.ye)throw new M(`Expected null or instance of ${c.name}, got an instance of ${b.name}`);a=b.ye(a);b=b.ge}return a};function Kb(a,b){if(null===b){if(this.Ke)throw new M(`null is not a valid ${this.name}`);return 0}if(!b.Yd)throw new M(`Cannot pass "${Lb(b)}" as a ${this.name}`);if(!b.Yd.ae)throw new M(`Cannot pass deleted object as a pointer of type ${this.name}`);return Jb(b.Yd.ae,b.Yd.de.be,this.be)}
function Mb(a,b){if(null===b){if(this.Ke)throw new M(`null is not a valid ${this.name}`);if(this.De){var c=this.Le();null!==a&&a.push(this.ne,c);return c}return 0}if(!b||!b.Yd)throw new M(`Cannot pass "${Lb(b)}" as a ${this.name}`);if(!b.Yd.ae)throw new M(`Cannot pass deleted object as a pointer of type ${this.name}`);if(!this.Ce&&b.Yd.de.Ce)throw new M(`Cannot convert argument of type ${b.Yd.ie?b.Yd.ie.name:b.Yd.de.name} to parameter type ${this.name}`);c=Jb(b.Yd.ae,b.Yd.de.be,this.be);if(this.De){if(void 0===
b.Yd.ee)throw new M("Passing raw pointer to smart pointer is illegal");switch(this.uf){case 0:if(b.Yd.ie===this)c=b.Yd.ee;else throw new M(`Cannot convert argument of type ${b.Yd.ie?b.Yd.ie.name:b.Yd.de.name} to parameter type ${this.name}`);break;case 1:c=b.Yd.ee;break;case 2:if(b.Yd.ie===this)c=b.Yd.ee;else{var e=b.clone();c=this.qf(c,Nb(()=>e["delete"]()));null!==a&&a.push(this.ne,c)}break;default:throw new M("Unsupporting sharing policy");}}return c}
function Ob(a,b){if(null===b){if(this.Ke)throw new M(`null is not a valid ${this.name}`);return 0}if(!b.Yd)throw new M(`Cannot pass "${Lb(b)}" as a ${this.name}`);if(!b.Yd.ae)throw new M(`Cannot pass deleted object as a pointer of type ${this.name}`);if(b.Yd.de.Ce)throw new M(`Cannot convert argument of type ${b.Yd.de.name} to parameter type ${this.name}`);return Jb(b.Yd.ae,b.Yd.de.be,this.be)}
function Pb(a,b,c,e,f,k,n,l,p,v,w){this.name=a;this.be=b;this.Ke=c;this.Ce=e;this.De=f;this.nf=k;this.uf=n;this.Se=l;this.Le=p;this.qf=v;this.ne=w;f||void 0!==b.ge?this.toWireType=Mb:(this.toWireType=e?Kb:Ob,this.ke=null)}
var Qb=(a,b,c)=>{if(!r.hasOwnProperty(a))throw new jb("Replacing nonexistent public symbol");void 0!==r[a].fe&&void 0!==c?r[a].fe[c]=b:(r[a]=b,r[a].oe=c)},O,Rb=(a,b,c=[])=>{a.includes("j")?(a=a.replace(/p/g,"i"),b=(0,r["dynCall_"+a])(b,...c)):b=O.get(b)(...c);return b},Sb=(a,b)=>(...c)=>Rb(a,b,c),Tb=(a,b)=>{a=L(a);var c=a.includes("j")?Sb(a,b):O.get(b);if("function"!=typeof c)throw new M(`unknown function pointer with signature ${a}: ${b}`);return c},ac,dc=a=>{a=bc(a);var b=L(a);cc(a);return b},ec=
(a,b)=>{function c(k){f[k]||hb[k]||(ib[k]?ib[k].forEach(c):(e.push(k),f[k]=!0))}var e=[],f={};b.forEach(c);throw new ac(`${a}: `+e.map(dc).join([", "]));};function fc(a){for(var b=1;b<a.length;++b)if(null!==a[b]&&void 0===a[b].ke)return!0;return!1}
function gc(a,b,c,e,f){var k=b.length;if(2>k)throw new M("argTypes array size mismatch! Must at least get return value and 'this' types!");var n=null!==b[1]&&null!==c,l=fc(b),p="void"!==b[0].name,v=k-2,w=Array(v),A=[],D=[];return Eb(a,function(...I){D.length=0;A.length=n?2:1;A[0]=f;if(n){var R=b[1].toWireType(D,this);A[1]=R}for(var P=0;P<v;++P)w[P]=b[P+2].toWireType(D,I[P]),A.push(w[P]);I=e(...A);if(l)eb(D);else for(P=n?1:2;P<b.length;P++){var ba=1===P?R:w[P-2];null!==b[P].ke&&b[P].ke(ba)}R=p?b[0].fromWireType(I):
void 0;return R})}
var hc=(a,b)=>{for(var c=[],e=0;e<a;e++)c.push(H[b+4*e>>2]);return c},ic=a=>{a=a.trim();const b=a.indexOf("(");return-1!==b?a.substr(0,b):a},jc=[],kc=[],lc=a=>{9<a&&0===--kc[a+1]&&(kc[a]=void 0,jc.push(a))},mc=a=>{if(!a)throw new M("Cannot use deleted val. handle = "+a);return kc[a]},Nb=a=>{switch(a){case void 0:return 2;case null:return 4;case !0:return 6;case !1:return 8;default:const b=jc.pop()||kc.length;kc[b]=a;kc[b+1]=1;return b}},nc={name:"emscripten::val",fromWireType:a=>{var b=mc(a);lc(a);
return b},toWireType:(a,b)=>Nb(b),je:8,readValueFromPointer:fb,ke:null},oc=(a,b,c)=>{switch(b){case 1:return c?function(e){return this.fromWireType(Ca[e])}:function(e){return this.fromWireType(B[e])};case 2:return c?function(e){return this.fromWireType(Da[e>>1])}:function(e){return this.fromWireType(Fa[e>>1])};case 4:return c?function(e){return this.fromWireType(E[e>>2])}:function(e){return this.fromWireType(H[e>>2])};default:throw new TypeError(`invalid integer width (${b}): ${a}`);}},pc=(a,b)=>
{var c=hb[a];if(void 0===c)throw a=`${b} has unknown type ${dc(a)}`,new M(a);return c},Lb=a=>{if(null===a)return"null";var b=typeof a;return"object"===b||"array"===b||"function"===b?a.toString():""+a},qc=(a,b)=>{switch(b){case 4:return function(c){return this.fromWireType(J[c>>2])};case 8:return function(c){return this.fromWireType(Ga[c>>3])};default:throw new TypeError(`invalid float width (${b}): ${a}`);}},rc=(a,b,c)=>{switch(b){case 1:return c?e=>Ca[e]:e=>B[e];case 2:return c?e=>Da[e>>1]:e=>Fa[e>>
1];case 4:return c?e=>E[e>>2]:e=>H[e>>2];default:throw new TypeError(`invalid integer width (${b}): ${a}`);}},ra=(a,b,c)=>{var e=B;if(!(0<c))return 0;var f=b;c=b+c-1;for(var k=0;k<a.length;++k){var n=a.charCodeAt(k);if(55296<=n&&57343>=n){var l=a.charCodeAt(++k);n=65536+((n&1023)<<10)|l&1023}if(127>=n){if(b>=c)break;e[b++]=n}else{if(2047>=n){if(b+1>=c)break;e[b++]=192|n>>6}else{if(65535>=n){if(b+2>=c)break;e[b++]=224|n>>12}else{if(b+3>=c)break;e[b++]=240|n>>18;e[b++]=128|n>>12&63}e[b++]=128|n>>6&
63}e[b++]=128|n&63}}e[b]=0;return b-f},qa=a=>{for(var b=0,c=0;c<a.length;++c){var e=a.charCodeAt(c);127>=e?b++:2047>=e?b+=2:55296<=e&&57343>=e?(b+=4,++c):b+=3}return b},sc="undefined"!=typeof TextDecoder?new TextDecoder("utf-16le"):void 0,tc=(a,b)=>{var c=a>>1;for(var e=c+b/2;!(c>=e)&&Fa[c];)++c;c<<=1;if(32<c-a&&sc)return sc.decode(B.subarray(a,c));c="";for(e=0;!(e>=b/2);++e){var f=Da[a+2*e>>1];if(0==f)break;c+=String.fromCharCode(f)}return c},uc=(a,b,c)=>{c??=2147483647;if(2>c)return 0;c-=2;var e=
b;c=c<2*a.length?c/2:a.length;for(var f=0;f<c;++f)Da[b>>1]=a.charCodeAt(f),b+=2;Da[b>>1]=0;return b-e},vc=a=>2*a.length,wc=(a,b)=>{for(var c=0,e="";!(c>=b/4);){var f=E[a+4*c>>2];if(0==f)break;++c;65536<=f?(f-=65536,e+=String.fromCharCode(55296|f>>10,56320|f&1023)):e+=String.fromCharCode(f)}return e},xc=(a,b,c)=>{c??=2147483647;if(4>c)return 0;var e=b;c=e+c-4;for(var f=0;f<a.length;++f){var k=a.charCodeAt(f);if(55296<=k&&57343>=k){var n=a.charCodeAt(++f);k=65536+((k&1023)<<10)|n&1023}E[b>>2]=k;b+=
4;if(b+4>c)break}E[b>>2]=0;return b-e},yc=a=>{for(var b=0,c=0;c<a.length;++c){var e=a.charCodeAt(c);55296<=e&&57343>=e&&++c;b+=4}return b},zc=(a,b,c)=>{var e=[];a=a.toWireType(e,c);e.length&&(H[b>>2]=Nb(e));return a},Ac=[],Bc={},Cc=a=>{var b=Bc[a];return void 0===b?L(a):b},Dc=()=>{function a(b){b.$$$embind_global$$$=b;var c="object"==typeof $$$embind_global$$$&&b.$$$embind_global$$$==b;c||delete b.$$$embind_global$$$;return c}if("object"==typeof globalThis)return globalThis;if("object"==typeof $$$embind_global$$$)return $$$embind_global$$$;
"object"==typeof global&&a(global)?$$$embind_global$$$=global:"object"==typeof self&&a(self)&&($$$embind_global$$$=self);if("object"==typeof $$$embind_global$$$)return $$$embind_global$$$;throw Error("unable to get global object.");},Ec=a=>{var b=Ac.length;Ac.push(a);return b},Fc=(a,b)=>{for(var c=Array(a),e=0;e<a;++e)c[e]=pc(H[b+4*e>>2],"parameter "+e);return c},Gc=Reflect.construct,Q,Hc=a=>{var b=a.getExtension("ANGLE_instanced_arrays");b&&(a.vertexAttribDivisor=(c,e)=>b.vertexAttribDivisorANGLE(c,
e),a.drawArraysInstanced=(c,e,f,k)=>b.drawArraysInstancedANGLE(c,e,f,k),a.drawElementsInstanced=(c,e,f,k,n)=>b.drawElementsInstancedANGLE(c,e,f,k,n))},Ic=a=>{var b=a.getExtension("OES_vertex_array_object");b&&(a.createVertexArray=()=>b.createVertexArrayOES(),a.deleteVertexArray=c=>b.deleteVertexArrayOES(c),a.bindVertexArray=c=>b.bindVertexArrayOES(c),a.isVertexArray=c=>b.isVertexArrayOES(c))},Jc=a=>{var b=a.getExtension("WEBGL_draw_buffers");b&&(a.drawBuffers=(c,e)=>b.drawBuffersWEBGL(c,e))},Kc=a=>
{var b="ANGLE_instanced_arrays EXT_blend_minmax EXT_disjoint_timer_query EXT_frag_depth EXT_shader_texture_lod EXT_sRGB OES_element_index_uint OES_fbo_render_mipmap OES_standard_derivatives OES_texture_float OES_texture_half_float OES_texture_half_float_linear OES_vertex_array_object WEBGL_color_buffer_float WEBGL_depth_texture WEBGL_draw_buffers EXT_color_buffer_float EXT_conservative_depth EXT_disjoint_timer_query_webgl2 EXT_texture_norm16 NV_shader_noperspective_interpolation WEBGL_clip_cull_distance EXT_clip_control EXT_color_buffer_half_float EXT_depth_clamp EXT_float_blend EXT_polygon_offset_clamp EXT_texture_compression_bptc EXT_texture_compression_rgtc EXT_texture_filter_anisotropic KHR_parallel_shader_compile OES_texture_float_linear WEBGL_blend_func_extended WEBGL_compressed_texture_astc WEBGL_compressed_texture_etc WEBGL_compressed_texture_etc1 WEBGL_compressed_texture_s3tc WEBGL_compressed_texture_s3tc_srgb WEBGL_debug_renderer_info WEBGL_debug_shaders WEBGL_lose_context WEBGL_multi_draw WEBGL_polygon_mode".split(" ");
return(a.getSupportedExtensions()||[]).filter(c=>b.includes(c))},Lc=1,Mc=[],Nc=[],Oc=[],Pc=[],ka=[],Qc=[],Rc=[],pa=[],Sc=[],Tc=[],Uc=[],Vc={},Xc={},Yc=4,Zc=0,ja=a=>{for(var b=Lc++,c=a.length;c<b;c++)a[c]=null;return b},$c=(a,b,c,e)=>{for(var f=0;f<a;f++){var k=Q[c](),n=k&&ja(e);k?(k.name=n,e[n]=k):S||=1282;E[b+4*f>>2]=n}},la=(a,b)=>{a.Ne||(a.Ne=a.getContext,a.getContext=function(e,f){f=a.Ne(e,f);return"webgl"==e==f instanceof WebGLRenderingContext?f:null});var c=1<b.majorVersion?a.getContext("webgl2",
b):a.getContext("webgl",b);return c?ad(c,b):0},ad=(a,b)=>{var c=ja(pa),e={handle:c,attributes:b,version:b.majorVersion,le:a};a.canvas&&(a.canvas.Ve=e);pa[c]=e;("undefined"==typeof b.df||b.df)&&bd(e);return c},oa=a=>{z=pa[a];r.vf=Q=z?.le;return!(a&&!Q)},bd=a=>{a||=z;if(!a.mf){a.mf=!0;var b=a.le;b.zf=b.getExtension("WEBGL_multi_draw");b.xf=b.getExtension("EXT_polygon_offset_clamp");b.wf=b.getExtension("EXT_clip_control");b.Bf=b.getExtension("WEBGL_polygon_mode");Hc(b);Ic(b);Jc(b);b.Pe=b.getExtension("WEBGL_draw_instanced_base_vertex_base_instance");
b.Re=b.getExtension("WEBGL_multi_draw_instanced_base_vertex_base_instance");2<=a.version&&(b.me=b.getExtension("EXT_disjoint_timer_query_webgl2"));if(2>a.version||!b.me)b.me=b.getExtension("EXT_disjoint_timer_query");Kc(b).forEach(c=>{c.includes("lose_context")||c.includes("debug")||b.getExtension(c)})}},z,S,cd=(a,b)=>{Q.bindFramebuffer(a,Oc[b])},dd=a=>{Q.bindVertexArray(Rc[a])},ed=a=>Q.clear(a),fd=(a,b,c,e)=>Q.clearColor(a,b,c,e),gd=a=>Q.clearStencil(a),hd=(a,b)=>{for(var c=0;c<a;c++){var e=E[b+
4*c>>2];Q.deleteVertexArray(Rc[e]);Rc[e]=null}},jd=[],kd=(a,b)=>{$c(a,b,"createVertexArray",Rc)};function ld(){var a=Kc(Q);return a=a.concat(a.map(b=>"GL_"+b))}
var md=(a,b,c)=>{if(b){var e=void 0;switch(a){case 36346:e=1;break;case 36344:0!=c&&1!=c&&(S||=1280);return;case 34814:case 36345:e=0;break;case 34466:var f=Q.getParameter(34467);e=f?f.length:0;break;case 33309:if(2>z.version){S||=1282;return}e=ld().length;break;case 33307:case 33308:if(2>z.version){S||=1280;return}e=33307==a?3:0}if(void 0===e)switch(f=Q.getParameter(a),typeof f){case "number":e=f;break;case "boolean":e=f?1:0;break;case "string":S||=1280;return;case "object":if(null===f)switch(a){case 34964:case 35725:case 34965:case 36006:case 36007:case 32873:case 34229:case 36662:case 36663:case 35053:case 35055:case 36010:case 35097:case 35869:case 32874:case 36389:case 35983:case 35368:case 34068:e=
0;break;default:S||=1280;return}else{if(f instanceof Float32Array||f instanceof Uint32Array||f instanceof Int32Array||f instanceof Array){for(a=0;a<f.length;++a)switch(c){case 0:E[b+4*a>>2]=f[a];break;case 2:J[b+4*a>>2]=f[a];break;case 4:Ca[b+a]=f[a]?1:0}return}try{e=f.name|0}catch(k){S||=1280;ya(`GL_INVALID_ENUM in glGet${c}v: Unknown object returned from WebGL getParameter(${a})! (error: ${k})`);return}}break;default:S||=1280;ya(`GL_INVALID_ENUM in glGet${c}v: Native code calling glGet${c}v(${a}) and it returns ${f} of type ${typeof f}!`);
return}switch(c){case 1:c=e;H[b>>2]=c;H[b+4>>2]=(c-H[b>>2])/4294967296;break;case 0:E[b>>2]=e;break;case 2:J[b>>2]=e;break;case 4:Ca[b]=e?1:0}}else S||=1281},nd=(a,b)=>md(a,b,0),od=(a,b,c)=>{if(c){a=Sc[a];b=2>z.version?Q.me.getQueryObjectEXT(a,b):Q.getQueryParameter(a,b);var e;"boolean"==typeof b?e=b?1:0:e=b;H[c>>2]=e;H[c+4>>2]=(e-H[c>>2])/4294967296}else S||=1281},qd=a=>{var b=qa(a)+1,c=pd(b);c&&ra(a,c,b);return c},rd=a=>{var b=Vc[a];if(!b){switch(a){case 7939:b=qd(ld().join(" "));break;case 7936:case 7937:case 37445:case 37446:(b=
Q.getParameter(a))||(S||=1280);b=b?qd(b):0;break;case 7938:b=Q.getParameter(7938);var c=`OpenGL ES 2.0 (${b})`;2<=z.version&&(c=`OpenGL ES 3.0 (${b})`);b=qd(c);break;case 35724:b=Q.getParameter(35724);c=b.match(/^WebGL GLSL ES ([0-9]\.[0-9][0-9]?)(?:$| .*)/);null!==c&&(3==c[1].length&&(c[1]+="0"),b=`OpenGL ES GLSL ES ${c[1]} (${b})`);b=qd(b);break;default:S||=1280}Vc[a]=b}return b},sd=(a,b)=>{if(2>z.version)return S||=1282,0;var c=Xc[a];if(c)return 0>b||b>=c.length?(S||=1281,0):c[b];switch(a){case 7939:return c=
ld().map(qd),c=Xc[a]=c,0>b||b>=c.length?(S||=1281,0):c[b];default:return S||=1280,0}},td=a=>"]"==a.slice(-1)&&a.lastIndexOf("["),ud=a=>{a-=5120;return 0==a?Ca:1==a?B:2==a?Da:4==a?E:6==a?J:5==a||28922==a||28520==a||30779==a||30782==a?H:Fa},vd=(a,b,c,e,f)=>{a=ud(a);b=e*((Zc||c)*({5:3,6:4,8:2,29502:3,29504:4,26917:2,26918:2,29846:3,29847:4}[b-6402]||1)*a.BYTES_PER_ELEMENT+Yc-1&-Yc);return a.subarray(f>>>31-Math.clz32(a.BYTES_PER_ELEMENT),f+b>>>31-Math.clz32(a.BYTES_PER_ELEMENT))},V=a=>{var b=Q.bf;if(b){var c=
b.xe[a];"number"==typeof c&&(b.xe[a]=c=Q.getUniformLocation(b,b.Te[a]+(0<c?`[${c}]`:"")));return c}S||=1282},wd=[],xd=[],yd={},Ad=()=>{if(!zd){var a={USER:"web_user",LOGNAME:"web_user",PATH:"/",PWD:"/",HOME:"/home/web_user",LANG:("object"==typeof navigator&&navigator.languages&&navigator.languages[0]||"C").replace("-","_")+".UTF-8",_:"./this.program"},b;for(b in yd)void 0===yd[b]?delete a[b]:a[b]=yd[b];var c=[];for(b in a)c.push(`${b}=${a[b]}`);zd=c}return zd},zd,Bd=[null,[],[]];
jb=r.InternalError=class extends Error{constructor(a){super(a);this.name="InternalError"}};for(var Cd=Array(256),Dd=0;256>Dd;++Dd)Cd[Dd]=String.fromCharCode(Dd);mb=Cd;M=r.BindingError=class extends Error{constructor(a){super(a);this.name="BindingError"}};
Object.assign(Db.prototype,{isAliasOf:function(a){if(!(this instanceof Db&&a instanceof Db))return!1;var b=this.Yd.de.be,c=this.Yd.ae;a.Yd=a.Yd;var e=a.Yd.de.be;for(a=a.Yd.ae;b.ge;)c=b.ye(c),b=b.ge;for(;e.ge;)a=e.ye(a),e=e.ge;return b===e&&c===a},clone:function(){this.Yd.ae||ob(this);if(this.Yd.we)return this.Yd.count.value+=1,this;var a=Ab,b=Object,c=b.create,e=Object.getPrototypeOf(this),f=this.Yd;a=a(c.call(b,e,{Yd:{value:{count:f.count,ve:f.ve,we:f.we,ae:f.ae,de:f.de,ee:f.ee,ie:f.ie}}}));a.Yd.count.value+=
1;a.Yd.ve=!1;return a},["delete"](){this.Yd.ae||ob(this);if(this.Yd.ve&&!this.Yd.we)throw new M("Object already scheduled for deletion");qb(this);var a=this.Yd;--a.count.value;0===a.count.value&&(a.ee?a.ie.ne(a.ee):a.de.be.ne(a.ae));this.Yd.we||(this.Yd.ee=void 0,this.Yd.ae=void 0)},isDeleted:function(){return!this.Yd.ae},deleteLater:function(){this.Yd.ae||ob(this);if(this.Yd.ve&&!this.Yd.we)throw new M("Object already scheduled for deletion");Cb.push(this);this.Yd.ve=!0;return this}});
Object.assign(Pb.prototype,{gf(a){this.Se&&(a=this.Se(a));return a},Oe(a){this.ne?.(a)},je:8,readValueFromPointer:fb,fromWireType:function(a){function b(){return this.De?Bb(this.be.se,{de:this.nf,ae:c,ie:this,ee:a}):Bb(this.be.se,{de:this,ae:a})}var c=this.gf(a);if(!c)return this.Oe(a),null;var e=zb(this.be,c);if(void 0!==e){if(0===e.Yd.count.value)return e.Yd.ae=c,e.Yd.ee=a,e.clone();e=e.clone();this.Oe(a);return e}e=this.be.ff(c);e=sb[e];if(!e)return b.call(this);e=this.Ce?e.af:e.pointerType;var f=
rb(c,this.be,e.be);return null===f?b.call(this):this.De?Bb(e.be.se,{de:e,ae:f,ie:this,ee:a}):Bb(e.be.se,{de:e,ae:f})}});ac=r.UnboundTypeError=((a,b)=>{var c=Eb(b,function(e){this.name=b;this.message=e;e=Error(e).stack;void 0!==e&&(this.stack=this.toString()+"\n"+e.replace(/^Error(:[^\n]*)?\n/,""))});c.prototype=Object.create(a.prototype);c.prototype.constructor=c;c.prototype.toString=function(){return void 0===this.message?this.name:`${this.name}: ${this.message}`};return c})(Error,"UnboundTypeError");
kc.push(0,1,void 0,1,null,1,!0,1,!1,1);r.count_emval_handles=()=>kc.length/2-5-jc.length;for(var Ed=0;32>Ed;++Ed)jd.push(Array(Ed));var Fd=new Float32Array(288);for(Ed=0;288>=Ed;++Ed)wd[Ed]=Fd.subarray(0,Ed);var Gd=new Int32Array(288);for(Ed=0;288>=Ed;++Ed)xd[Ed]=Gd.subarray(0,Ed);
var Vd={F:(a,b,c)=>{var e=new Xa(a);H[e.ae+16>>2]=0;H[e.ae+4>>2]=b;H[e.ae+8>>2]=c;Ya=a;Za++;throw Ya;},V:function(){return 0},vd:()=>{},ud:function(){return 0},td:()=>{},sd:()=>{},U:function(){},rd:()=>{},nd:()=>{Pa("")},B:a=>{var b=db[a];delete db[a];var c=b.Le,e=b.ne,f=b.Qe,k=f.map(n=>n.kf).concat(f.map(n=>n.sf));lb([a],k,n=>{var l={};f.forEach((p,v)=>{var w=n[v],A=p.hf,D=p.jf,I=n[v+f.length],R=p.rf,P=p.tf;l[p.ef]={read:ba=>w.fromWireType(A(D,ba)),write:(ba,ma)=>{var Y=[];R(P,ba,I.toWireType(Y,
ma));eb(Y)}}});return[{name:b.name,fromWireType:p=>{var v={},w;for(w in l)v[w]=l[w].read(p);e(p);return v},toWireType:(p,v)=>{for(var w in l)if(!(w in v))throw new TypeError(`Missing field: "${w}"`);var A=c();for(w in l)l[w].write(A,v[w]);null!==p&&p.push(e,A);return A},je:8,readValueFromPointer:fb,ke:e}]})},Y:()=>{},md:(a,b,c,e)=>{b=L(b);kb(a,{name:b,fromWireType:function(f){return!!f},toWireType:function(f,k){return k?c:e},je:8,readValueFromPointer:function(f){return this.fromWireType(B[f])},ke:null})},
l:(a,b,c,e,f,k,n,l,p,v,w,A,D)=>{w=L(w);k=Tb(f,k);l&&=Tb(n,l);v&&=Tb(p,v);D=Tb(A,D);var I=Hb(w);Gb(I,function(){ec(`Cannot construct ${w} due to unbound types`,[e])});lb([a,b,c],e?[e]:[],R=>{R=R[0];if(e){var P=R.be;var ba=P.se}else ba=Db.prototype;R=Eb(w,function(...Ea){if(Object.getPrototypeOf(this)!==ma)throw new M("Use 'new' to construct "+w);if(void 0===Y.pe)throw new M(w+" has no accessible constructor");var fa=Y.pe[Ea.length];if(void 0===fa)throw new M(`Tried to invoke ctor of ${w} with invalid number of parameters (${Ea.length}) - expected (${Object.keys(Y.pe).toString()}) parameters instead!`);
return fa.apply(this,Ea)});var ma=Object.create(ba,{constructor:{value:R}});R.prototype=ma;var Y=new Ib(w,R,ma,D,P,k,l,v);if(Y.ge){var ia;(ia=Y.ge).ze??(ia.ze=[]);Y.ge.ze.push(Y)}P=new Pb(w,Y,!0,!1,!1);ia=new Pb(w+"*",Y,!1,!1,!1);ba=new Pb(w+" const*",Y,!1,!0,!1);sb[a]={pointerType:ia,af:ba};Qb(I,R);return[P,ia,ba]})},e:(a,b,c,e,f,k,n)=>{var l=hc(c,e);b=L(b);b=ic(b);k=Tb(f,k);lb([],[a],p=>{function v(){ec(`Cannot call ${w} due to unbound types`,l)}p=p[0];var w=`${p.name}.${b}`;b.startsWith("@@")&&
(b=Symbol[b.substring(2)]);var A=p.be.constructor;void 0===A[b]?(v.oe=c-1,A[b]=v):(Fb(A,b,w),A[b].fe[c-1]=v);lb([],l,D=>{D=[D[0],null].concat(D.slice(1));D=gc(w,D,null,k,n);void 0===A[b].fe?(D.oe=c-1,A[b]=D):A[b].fe[c-1]=D;if(p.be.ze)for(const I of p.be.ze)I.constructor.hasOwnProperty(b)||(I.constructor[b]=D);return[]});return[]})},z:(a,b,c,e,f,k)=>{var n=hc(b,c);f=Tb(e,f);lb([],[a],l=>{l=l[0];var p=`constructor ${l.name}`;void 0===l.be.pe&&(l.be.pe=[]);if(void 0!==l.be.pe[b-1])throw new M(`Cannot register multiple constructors with identical number of parameters (${b-
1}) for class '${l.name}'! Overload resolution is currently only performed using the parameter count, not actual type info!`);l.be.pe[b-1]=()=>{ec(`Cannot construct ${l.name} due to unbound types`,n)};lb([],n,v=>{v.splice(1,0,null);l.be.pe[b-1]=gc(p,v,null,f,k);return[]});return[]})},a:(a,b,c,e,f,k,n,l)=>{var p=hc(c,e);b=L(b);b=ic(b);k=Tb(f,k);lb([],[a],v=>{function w(){ec(`Cannot call ${A} due to unbound types`,p)}v=v[0];var A=`${v.name}.${b}`;b.startsWith("@@")&&(b=Symbol[b.substring(2)]);l&&v.be.pf.push(b);
var D=v.be.se,I=D[b];void 0===I||void 0===I.fe&&I.className!==v.name&&I.oe===c-2?(w.oe=c-2,w.className=v.name,D[b]=w):(Fb(D,b,A),D[b].fe[c-2]=w);lb([],p,R=>{R=gc(A,R,v,k,n);void 0===D[b].fe?(R.oe=c-2,D[b]=R):D[b].fe[c-2]=R;return[]});return[]})},q:(a,b,c)=>{a=L(a);lb([],[b],e=>{e=e[0];r[a]=e.fromWireType(c);return[]})},ld:a=>kb(a,nc),j:(a,b,c,e)=>{function f(){}b=L(b);f.values={};kb(a,{name:b,constructor:f,fromWireType:function(k){return this.constructor.values[k]},toWireType:(k,n)=>n.value,je:8,
readValueFromPointer:oc(b,c,e),ke:null});Gb(b,f)},b:(a,b,c)=>{var e=pc(a,"enum");b=L(b);a=e.constructor;e=Object.create(e.constructor.prototype,{value:{value:c},constructor:{value:Eb(`${e.name}_${b}`,function(){})}});a.values[c]=e;a[b]=e},S:(a,b,c)=>{b=L(b);kb(a,{name:b,fromWireType:e=>e,toWireType:(e,f)=>f,je:8,readValueFromPointer:qc(b,c),ke:null})},w:(a,b,c,e,f,k)=>{var n=hc(b,c);a=L(a);a=ic(a);f=Tb(e,f);Gb(a,function(){ec(`Cannot call ${a} due to unbound types`,n)},b-1);lb([],n,l=>{l=[l[0],null].concat(l.slice(1));
Qb(a,gc(a,l,null,f,k),b-1);return[]})},C:(a,b,c,e,f)=>{b=L(b);-1===f&&(f=4294967295);f=l=>l;if(0===e){var k=32-8*c;f=l=>l<<k>>>k}var n=b.includes("unsigned")?function(l,p){return p>>>0}:function(l,p){return p};kb(a,{name:b,fromWireType:f,toWireType:n,je:8,readValueFromPointer:rc(b,c,0!==e),ke:null})},p:(a,b,c)=>{function e(k){return new f(Ca.buffer,H[k+4>>2],H[k>>2])}var f=[Int8Array,Uint8Array,Int16Array,Uint16Array,Int32Array,Uint32Array,Float32Array,Float64Array][b];c=L(c);kb(a,{name:c,fromWireType:e,
je:8,readValueFromPointer:e},{lf:!0})},o:(a,b,c,e,f,k,n,l,p,v,w,A)=>{c=L(c);k=Tb(f,k);l=Tb(n,l);v=Tb(p,v);A=Tb(w,A);lb([a],[b],D=>{D=D[0];return[new Pb(c,D.be,!1,!1,!0,D,e,k,l,v,A)]})},R:(a,b)=>{b=L(b);var c="std::string"===b;kb(a,{name:b,fromWireType:function(e){var f=H[e>>2],k=e+4;if(c)for(var n=k,l=0;l<=f;++l){var p=k+l;if(l==f||0==B[p]){n=n?cb(B,n,p-n):"";if(void 0===v)var v=n;else v+=String.fromCharCode(0),v+=n;n=p+1}}else{v=Array(f);for(l=0;l<f;++l)v[l]=String.fromCharCode(B[k+l]);v=v.join("")}cc(e);
return v},toWireType:function(e,f){f instanceof ArrayBuffer&&(f=new Uint8Array(f));var k="string"==typeof f;if(!(k||f instanceof Uint8Array||f instanceof Uint8ClampedArray||f instanceof Int8Array))throw new M("Cannot pass non-string to std::string");var n=c&&k?qa(f):f.length;var l=pd(4+n+1),p=l+4;H[l>>2]=n;if(c&&k)ra(f,p,n+1);else if(k)for(k=0;k<n;++k){var v=f.charCodeAt(k);if(255<v)throw cc(p),new M("String has UTF-16 code units that do not fit in 8 bits");B[p+k]=v}else for(k=0;k<n;++k)B[p+k]=f[k];
null!==e&&e.push(cc,l);return l},je:8,readValueFromPointer:fb,ke(e){cc(e)}})},M:(a,b,c)=>{c=L(c);if(2===b){var e=tc;var f=uc;var k=vc;var n=l=>Fa[l>>1]}else 4===b&&(e=wc,f=xc,k=yc,n=l=>H[l>>2]);kb(a,{name:c,fromWireType:l=>{for(var p=H[l>>2],v,w=l+4,A=0;A<=p;++A){var D=l+4+A*b;if(A==p||0==n(D))w=e(w,D-w),void 0===v?v=w:(v+=String.fromCharCode(0),v+=w),w=D+b}cc(l);return v},toWireType:(l,p)=>{if("string"!=typeof p)throw new M(`Cannot pass non-string to C++ string type ${c}`);var v=k(p),w=pd(4+v+b);
H[w>>2]=v/b;f(p,w+4,v+b);null!==l&&l.push(cc,w);return w},je:8,readValueFromPointer:fb,ke(l){cc(l)}})},A:(a,b,c,e,f,k)=>{db[a]={name:L(b),Le:Tb(c,e),ne:Tb(f,k),Qe:[]}},d:(a,b,c,e,f,k,n,l,p,v)=>{db[a].Qe.push({ef:L(b),kf:c,hf:Tb(e,f),jf:k,sf:n,rf:Tb(l,p),tf:v})},kd:(a,b)=>{b=L(b);kb(a,{yf:!0,name:b,je:0,fromWireType:()=>{},toWireType:()=>{}})},jd:()=>1,id:()=>{throw Infinity;},E:(a,b,c)=>{a=mc(a);b=pc(b,"emval::as");return zc(b,c,a)},L:(a,b,c,e)=>{a=Ac[a];b=mc(b);return a(null,b,c,e)},t:(a,b,c,e,f)=>
{a=Ac[a];b=mc(b);c=Cc(c);return a(b,b[c],e,f)},c:lc,K:a=>{if(0===a)return Nb(Dc());a=Cc(a);return Nb(Dc()[a])},n:(a,b,c)=>{var e=Fc(a,b),f=e.shift();a--;var k=Array(a);b=`methodCaller<(${e.map(n=>n.name).join(", ")}) => ${f.name}>`;return Ec(Eb(b,(n,l,p,v)=>{for(var w=0,A=0;A<a;++A)k[A]=e[A].readValueFromPointer(v+w),w+=e[A].je;n=1===c?Gc(l,k):l.apply(n,k);return zc(f,p,n)}))},y:(a,b)=>{a=mc(a);b=mc(b);return Nb(a[b])},H:a=>{9<a&&(kc[a+1]+=1)},G:()=>Nb([]),f:a=>Nb(Cc(a)),D:()=>Nb({}),hd:a=>{a=mc(a);
return!a},m:a=>{var b=mc(a);eb(b);lc(a)},h:(a,b,c)=>{a=mc(a);b=mc(b);c=mc(c);a[b]=c},g:(a,b)=>{a=pc(a,"_emval_take_value");a=a.readValueFromPointer(b);return Nb(a)},X:function(){return-52},W:function(){},gd:(a,b,c,e)=>{var f=(new Date).getFullYear(),k=(new Date(f,0,1)).getTimezoneOffset();f=(new Date(f,6,1)).getTimezoneOffset();H[a>>2]=60*Math.max(k,f);E[b>>2]=Number(k!=f);b=n=>{var l=Math.abs(n);return`UTC${0<=n?"-":"+"}${String(Math.floor(l/60)).padStart(2,"0")}${String(l%60).padStart(2,"0")}`};
a=b(k);b=b(f);f<k?(ra(a,c,17),ra(b,e,17)):(ra(a,e,17),ra(b,c,17))},fd:()=>performance.now(),ed:a=>Q.activeTexture(a),dd:(a,b)=>{Q.attachShader(Nc[a],Qc[b])},cd:(a,b)=>{Q.beginQuery(a,Sc[b])},bd:(a,b)=>{Q.me.beginQueryEXT(a,Sc[b])},ad:(a,b,c)=>{Q.bindAttribLocation(Nc[a],b,c?cb(B,c):"")},$c:(a,b)=>{35051==a?Q.Ie=b:35052==a&&(Q.re=b);Q.bindBuffer(a,Mc[b])},_c:cd,Zc:(a,b)=>{Q.bindRenderbuffer(a,Pc[b])},Yc:(a,b)=>{Q.bindSampler(a,Tc[b])},Xc:(a,b)=>{Q.bindTexture(a,ka[b])},Wc:dd,Vc:dd,Uc:(a,b,c,e)=>Q.blendColor(a,
b,c,e),Tc:a=>Q.blendEquation(a),Sc:(a,b)=>Q.blendFunc(a,b),Rc:(a,b,c,e,f,k,n,l,p,v)=>Q.blitFramebuffer(a,b,c,e,f,k,n,l,p,v),Qc:(a,b,c,e)=>{2<=z.version?c&&b?Q.bufferData(a,B,e,c,b):Q.bufferData(a,b,e):Q.bufferData(a,c?B.subarray(c,c+b):b,e)},Pc:(a,b,c,e)=>{2<=z.version?c&&Q.bufferSubData(a,b,B,e,c):Q.bufferSubData(a,b,B.subarray(e,e+c))},Oc:a=>Q.checkFramebufferStatus(a),Nc:ed,Mc:fd,Lc:gd,Kc:(a,b,c,e)=>Q.clientWaitSync(Uc[a],b,(c>>>0)+4294967296*e),Jc:(a,b,c,e)=>{Q.colorMask(!!a,!!b,!!c,!!e)},Ic:a=>
{Q.compileShader(Qc[a])},Hc:(a,b,c,e,f,k,n,l)=>{2<=z.version?Q.re||!n?Q.compressedTexImage2D(a,b,c,e,f,k,n,l):Q.compressedTexImage2D(a,b,c,e,f,k,B,l,n):Q.compressedTexImage2D(a,b,c,e,f,k,B.subarray(l,l+n))},Gc:(a,b,c,e,f,k,n,l,p)=>{2<=z.version?Q.re||!l?Q.compressedTexSubImage2D(a,b,c,e,f,k,n,l,p):Q.compressedTexSubImage2D(a,b,c,e,f,k,n,B,p,l):Q.compressedTexSubImage2D(a,b,c,e,f,k,n,B.subarray(p,p+l))},Fc:(a,b,c,e,f)=>Q.copyBufferSubData(a,b,c,e,f),Ec:(a,b,c,e,f,k,n,l)=>Q.copyTexSubImage2D(a,b,c,
e,f,k,n,l),Dc:()=>{var a=ja(Nc),b=Q.createProgram();b.name=a;b.Ge=b.Ee=b.Fe=0;b.Me=1;Nc[a]=b;return a},Cc:a=>{var b=ja(Qc);Qc[b]=Q.createShader(a);return b},Bc:a=>Q.cullFace(a),Ac:(a,b)=>{for(var c=0;c<a;c++){var e=E[b+4*c>>2],f=Mc[e];f&&(Q.deleteBuffer(f),f.name=0,Mc[e]=null,e==Q.Ie&&(Q.Ie=0),e==Q.re&&(Q.re=0))}},zc:(a,b)=>{for(var c=0;c<a;++c){var e=E[b+4*c>>2],f=Oc[e];f&&(Q.deleteFramebuffer(f),f.name=0,Oc[e]=null)}},yc:a=>{if(a){var b=Nc[a];b?(Q.deleteProgram(b),b.name=0,Nc[a]=null):S||=1281}},
xc:(a,b)=>{for(var c=0;c<a;c++){var e=E[b+4*c>>2],f=Sc[e];f&&(Q.deleteQuery(f),Sc[e]=null)}},wc:(a,b)=>{for(var c=0;c<a;c++){var e=E[b+4*c>>2],f=Sc[e];f&&(Q.me.deleteQueryEXT(f),Sc[e]=null)}},vc:(a,b)=>{for(var c=0;c<a;c++){var e=E[b+4*c>>2],f=Pc[e];f&&(Q.deleteRenderbuffer(f),f.name=0,Pc[e]=null)}},uc:(a,b)=>{for(var c=0;c<a;c++){var e=E[b+4*c>>2],f=Tc[e];f&&(Q.deleteSampler(f),f.name=0,Tc[e]=null)}},tc:a=>{if(a){var b=Qc[a];b?(Q.deleteShader(b),Qc[a]=null):S||=1281}},sc:a=>{if(a){var b=Uc[a];b?
(Q.deleteSync(b),b.name=0,Uc[a]=null):S||=1281}},rc:(a,b)=>{for(var c=0;c<a;c++){var e=E[b+4*c>>2],f=ka[e];f&&(Q.deleteTexture(f),f.name=0,ka[e]=null)}},qc:hd,pc:hd,oc:a=>{Q.depthMask(!!a)},nc:a=>Q.disable(a),mc:a=>{Q.disableVertexAttribArray(a)},lc:(a,b,c)=>{Q.drawArrays(a,b,c)},kc:(a,b,c,e)=>{Q.drawArraysInstanced(a,b,c,e)},jc:(a,b,c,e,f)=>{Q.Pe.drawArraysInstancedBaseInstanceWEBGL(a,b,c,e,f)},ic:(a,b)=>{for(var c=jd[a],e=0;e<a;e++)c[e]=E[b+4*e>>2];Q.drawBuffers(c)},hc:(a,b,c,e)=>{Q.drawElements(a,
b,c,e)},gc:(a,b,c,e,f)=>{Q.drawElementsInstanced(a,b,c,e,f)},fc:(a,b,c,e,f,k,n)=>{Q.Pe.drawElementsInstancedBaseVertexBaseInstanceWEBGL(a,b,c,e,f,k,n)},ec:(a,b,c,e,f,k)=>{Q.drawElements(a,e,f,k)},dc:a=>Q.enable(a),cc:a=>{Q.enableVertexAttribArray(a)},bc:a=>Q.endQuery(a),ac:a=>{Q.me.endQueryEXT(a)},$b:(a,b)=>(a=Q.fenceSync(a,b))?(b=ja(Uc),a.name=b,Uc[b]=a,b):0,_b:()=>Q.finish(),Zb:()=>Q.flush(),Yb:(a,b,c,e)=>{Q.framebufferRenderbuffer(a,b,c,Pc[e])},Xb:(a,b,c,e,f)=>{Q.framebufferTexture2D(a,b,c,ka[e],
f)},Wb:a=>Q.frontFace(a),Vb:(a,b)=>{$c(a,b,"createBuffer",Mc)},Ub:(a,b)=>{$c(a,b,"createFramebuffer",Oc)},Tb:(a,b)=>{$c(a,b,"createQuery",Sc)},Sb:(a,b)=>{for(var c=0;c<a;c++){var e=Q.me.createQueryEXT();if(!e){for(S||=1282;c<a;)E[b+4*c++>>2]=0;break}var f=ja(Sc);e.name=f;Sc[f]=e;E[b+4*c>>2]=f}},Rb:(a,b)=>{$c(a,b,"createRenderbuffer",Pc)},Qb:(a,b)=>{$c(a,b,"createSampler",Tc)},Pb:(a,b)=>{$c(a,b,"createTexture",ka)},Ob:kd,Nb:kd,Mb:a=>Q.generateMipmap(a),Lb:(a,b,c)=>{c?E[c>>2]=Q.getBufferParameter(a,
b):S||=1281},Kb:()=>{var a=Q.getError()||S;S=0;return a},Jb:(a,b)=>md(a,b,2),Ib:(a,b,c,e)=>{a=Q.getFramebufferAttachmentParameter(a,b,c);if(a instanceof WebGLRenderbuffer||a instanceof WebGLTexture)a=a.name|0;E[e>>2]=a},Hb:nd,Gb:(a,b,c,e)=>{a=Q.getProgramInfoLog(Nc[a]);null===a&&(a="(unknown error)");b=0<b&&e?ra(a,e,b):0;c&&(E[c>>2]=b)},Fb:(a,b,c)=>{if(c)if(a>=Lc)S||=1281;else if(a=Nc[a],35716==b)a=Q.getProgramInfoLog(a),null===a&&(a="(unknown error)"),E[c>>2]=a.length+1;else if(35719==b){if(!a.Ge){var e=
Q.getProgramParameter(a,35718);for(b=0;b<e;++b)a.Ge=Math.max(a.Ge,Q.getActiveUniform(a,b).name.length+1)}E[c>>2]=a.Ge}else if(35722==b){if(!a.Ee)for(e=Q.getProgramParameter(a,35721),b=0;b<e;++b)a.Ee=Math.max(a.Ee,Q.getActiveAttrib(a,b).name.length+1);E[c>>2]=a.Ee}else if(35381==b){if(!a.Fe)for(e=Q.getProgramParameter(a,35382),b=0;b<e;++b)a.Fe=Math.max(a.Fe,Q.getActiveUniformBlockName(a,b).length+1);E[c>>2]=a.Fe}else E[c>>2]=Q.getProgramParameter(a,b);else S||=1281},Eb:od,Db:od,Cb:(a,b,c)=>{if(c){a=
Q.getQueryParameter(Sc[a],b);var e;"boolean"==typeof a?e=a?1:0:e=a;E[c>>2]=e}else S||=1281},Bb:(a,b,c)=>{if(c){a=Q.me.getQueryObjectEXT(Sc[a],b);var e;"boolean"==typeof a?e=a?1:0:e=a;E[c>>2]=e}else S||=1281},Ab:(a,b,c)=>{c?E[c>>2]=Q.getQuery(a,b):S||=1281},zb:(a,b,c)=>{c?E[c>>2]=Q.me.getQueryEXT(a,b):S||=1281},yb:(a,b,c)=>{c?E[c>>2]=Q.getRenderbufferParameter(a,b):S||=1281},xb:(a,b,c,e)=>{a=Q.getShaderInfoLog(Qc[a]);null===a&&(a="(unknown error)");b=0<b&&e?ra(a,e,b):0;c&&(E[c>>2]=b)},wb:(a,b,c,e)=>
{a=Q.getShaderPrecisionFormat(a,b);E[c>>2]=a.rangeMin;E[c+4>>2]=a.rangeMax;E[e>>2]=a.precision},vb:(a,b,c)=>{c?35716==b?(a=Q.getShaderInfoLog(Qc[a]),null===a&&(a="(unknown error)"),E[c>>2]=a?a.length+1:0):35720==b?(a=Q.getShaderSource(Qc[a]),E[c>>2]=a?a.length+1:0):E[c>>2]=Q.getShaderParameter(Qc[a],b):S||=1281},ub:rd,tb:sd,sb:(a,b)=>{b=b?cb(B,b):"";if(a=Nc[a]){var c=a,e=c.xe,f=c.Ue,k;if(!e){c.xe=e={};c.Te={};var n=Q.getProgramParameter(c,35718);for(k=0;k<n;++k){var l=Q.getActiveUniform(c,k);var p=
l.name;l=l.size;var v=td(p);v=0<v?p.slice(0,v):p;var w=c.Me;c.Me+=l;f[v]=[l,w];for(p=0;p<l;++p)e[w]=p,c.Te[w++]=v}}c=a.xe;e=0;f=b;k=td(b);0<k&&(e=parseInt(b.slice(k+1))>>>0,f=b.slice(0,k));if((f=a.Ue[f])&&e<f[0]&&(e+=f[1],c[e]=c[e]||Q.getUniformLocation(a,b)))return e}else S||=1281;return-1},rb:(a,b,c)=>{for(var e=jd[b],f=0;f<b;f++)e[f]=E[c+4*f>>2];Q.invalidateFramebuffer(a,e)},qb:(a,b,c,e,f,k,n)=>{for(var l=jd[b],p=0;p<b;p++)l[p]=E[c+4*p>>2];Q.invalidateSubFramebuffer(a,l,e,f,k,n)},pb:a=>Q.isSync(Uc[a]),
ob:a=>(a=ka[a])?Q.isTexture(a):0,nb:a=>Q.lineWidth(a),mb:a=>{a=Nc[a];Q.linkProgram(a);a.xe=0;a.Ue={}},lb:(a,b,c,e,f,k)=>{Q.Re.multiDrawArraysInstancedBaseInstanceWEBGL(a,E,b>>2,E,c>>2,E,e>>2,H,f>>2,k)},kb:(a,b,c,e,f,k,n,l)=>{Q.Re.multiDrawElementsInstancedBaseVertexBaseInstanceWEBGL(a,E,b>>2,c,E,e>>2,E,f>>2,E,k>>2,H,n>>2,l)},jb:(a,b)=>{3317==a?Yc=b:3314==a&&(Zc=b);Q.pixelStorei(a,b)},ib:(a,b)=>{Q.me.queryCounterEXT(Sc[a],b)},hb:a=>Q.readBuffer(a),gb:(a,b,c,e,f,k,n)=>{if(2<=z.version)if(Q.Ie)Q.readPixels(a,
b,c,e,f,k,n);else{var l=ud(k);n>>>=31-Math.clz32(l.BYTES_PER_ELEMENT);Q.readPixels(a,b,c,e,f,k,l,n)}else(l=vd(k,f,c,e,n))?Q.readPixels(a,b,c,e,f,k,l):S||=1280},fb:(a,b,c,e)=>Q.renderbufferStorage(a,b,c,e),eb:(a,b,c,e,f)=>Q.renderbufferStorageMultisample(a,b,c,e,f),db:(a,b,c)=>{Q.samplerParameterf(Tc[a],b,c)},cb:(a,b,c)=>{Q.samplerParameteri(Tc[a],b,c)},bb:(a,b,c)=>{Q.samplerParameteri(Tc[a],b,E[c>>2])},ab:(a,b,c,e)=>Q.scissor(a,b,c,e),$a:(a,b,c,e)=>{for(var f="",k=0;k<b;++k){var n=(n=H[c+4*k>>2])?
cb(B,n,e?H[e+4*k>>2]:void 0):"";f+=n}Q.shaderSource(Qc[a],f)},_a:(a,b,c)=>Q.stencilFunc(a,b,c),Za:(a,b,c,e)=>Q.stencilFuncSeparate(a,b,c,e),Ya:a=>Q.stencilMask(a),Xa:(a,b)=>Q.stencilMaskSeparate(a,b),Wa:(a,b,c)=>Q.stencilOp(a,b,c),Va:(a,b,c,e)=>Q.stencilOpSeparate(a,b,c,e),Ua:(a,b,c,e,f,k,n,l,p)=>{if(2<=z.version){if(Q.re){Q.texImage2D(a,b,c,e,f,k,n,l,p);return}if(p){var v=ud(l);p>>>=31-Math.clz32(v.BYTES_PER_ELEMENT);Q.texImage2D(a,b,c,e,f,k,n,l,v,p);return}}v=p?vd(l,n,e,f,p):null;Q.texImage2D(a,
b,c,e,f,k,n,l,v)},Ta:(a,b,c)=>Q.texParameterf(a,b,c),Sa:(a,b,c)=>{Q.texParameterf(a,b,J[c>>2])},Ra:(a,b,c)=>Q.texParameteri(a,b,c),Qa:(a,b,c)=>{Q.texParameteri(a,b,E[c>>2])},Pa:(a,b,c,e,f)=>Q.texStorage2D(a,b,c,e,f),Oa:(a,b,c,e,f,k,n,l,p)=>{if(2<=z.version){if(Q.re){Q.texSubImage2D(a,b,c,e,f,k,n,l,p);return}if(p){var v=ud(l);Q.texSubImage2D(a,b,c,e,f,k,n,l,v,p>>>31-Math.clz32(v.BYTES_PER_ELEMENT));return}}p=p?vd(l,n,f,k,p):null;Q.texSubImage2D(a,b,c,e,f,k,n,l,p)},Na:(a,b)=>{Q.uniform1f(V(a),b)},Ma:(a,
b,c)=>{if(2<=z.version)b&&Q.uniform1fv(V(a),J,c>>2,b);else{if(288>=b)for(var e=wd[b],f=0;f<b;++f)e[f]=J[c+4*f>>2];else e=J.subarray(c>>2,c+4*b>>2);Q.uniform1fv(V(a),e)}},La:(a,b)=>{Q.uniform1i(V(a),b)},Ka:(a,b,c)=>{if(2<=z.version)b&&Q.uniform1iv(V(a),E,c>>2,b);else{if(288>=b)for(var e=xd[b],f=0;f<b;++f)e[f]=E[c+4*f>>2];else e=E.subarray(c>>2,c+4*b>>2);Q.uniform1iv(V(a),e)}},Ja:(a,b,c)=>{Q.uniform2f(V(a),b,c)},Ia:(a,b,c)=>{if(2<=z.version)b&&Q.uniform2fv(V(a),J,c>>2,2*b);else{if(144>=b){b*=2;for(var e=
wd[b],f=0;f<b;f+=2)e[f]=J[c+4*f>>2],e[f+1]=J[c+(4*f+4)>>2]}else e=J.subarray(c>>2,c+8*b>>2);Q.uniform2fv(V(a),e)}},Ha:(a,b,c)=>{Q.uniform2i(V(a),b,c)},Ga:(a,b,c)=>{if(2<=z.version)b&&Q.uniform2iv(V(a),E,c>>2,2*b);else{if(144>=b){b*=2;for(var e=xd[b],f=0;f<b;f+=2)e[f]=E[c+4*f>>2],e[f+1]=E[c+(4*f+4)>>2]}else e=E.subarray(c>>2,c+8*b>>2);Q.uniform2iv(V(a),e)}},Fa:(a,b,c,e)=>{Q.uniform3f(V(a),b,c,e)},Ea:(a,b,c)=>{if(2<=z.version)b&&Q.uniform3fv(V(a),J,c>>2,3*b);else{if(96>=b){b*=3;for(var e=wd[b],f=0;f<
b;f+=3)e[f]=J[c+4*f>>2],e[f+1]=J[c+(4*f+4)>>2],e[f+2]=J[c+(4*f+8)>>2]}else e=J.subarray(c>>2,c+12*b>>2);Q.uniform3fv(V(a),e)}},Da:(a,b,c,e)=>{Q.uniform3i(V(a),b,c,e)},Ca:(a,b,c)=>{if(2<=z.version)b&&Q.uniform3iv(V(a),E,c>>2,3*b);else{if(96>=b){b*=3;for(var e=xd[b],f=0;f<b;f+=3)e[f]=E[c+4*f>>2],e[f+1]=E[c+(4*f+4)>>2],e[f+2]=E[c+(4*f+8)>>2]}else e=E.subarray(c>>2,c+12*b>>2);Q.uniform3iv(V(a),e)}},Ba:(a,b,c,e,f)=>{Q.uniform4f(V(a),b,c,e,f)},Aa:(a,b,c)=>{if(2<=z.version)b&&Q.uniform4fv(V(a),J,c>>2,4*
b);else{if(72>=b){var e=wd[4*b],f=J;c>>=2;b*=4;for(var k=0;k<b;k+=4){var n=c+k;e[k]=f[n];e[k+1]=f[n+1];e[k+2]=f[n+2];e[k+3]=f[n+3]}}else e=J.subarray(c>>2,c+16*b>>2);Q.uniform4fv(V(a),e)}},za:(a,b,c,e,f)=>{Q.uniform4i(V(a),b,c,e,f)},ya:(a,b,c)=>{if(2<=z.version)b&&Q.uniform4iv(V(a),E,c>>2,4*b);else{if(72>=b){b*=4;for(var e=xd[b],f=0;f<b;f+=4)e[f]=E[c+4*f>>2],e[f+1]=E[c+(4*f+4)>>2],e[f+2]=E[c+(4*f+8)>>2],e[f+3]=E[c+(4*f+12)>>2]}else e=E.subarray(c>>2,c+16*b>>2);Q.uniform4iv(V(a),e)}},xa:(a,b,c,e)=>
{if(2<=z.version)b&&Q.uniformMatrix2fv(V(a),!!c,J,e>>2,4*b);else{if(72>=b){b*=4;for(var f=wd[b],k=0;k<b;k+=4)f[k]=J[e+4*k>>2],f[k+1]=J[e+(4*k+4)>>2],f[k+2]=J[e+(4*k+8)>>2],f[k+3]=J[e+(4*k+12)>>2]}else f=J.subarray(e>>2,e+16*b>>2);Q.uniformMatrix2fv(V(a),!!c,f)}},wa:(a,b,c,e)=>{if(2<=z.version)b&&Q.uniformMatrix3fv(V(a),!!c,J,e>>2,9*b);else{if(32>=b){b*=9;for(var f=wd[b],k=0;k<b;k+=9)f[k]=J[e+4*k>>2],f[k+1]=J[e+(4*k+4)>>2],f[k+2]=J[e+(4*k+8)>>2],f[k+3]=J[e+(4*k+12)>>2],f[k+4]=J[e+(4*k+16)>>2],f[k+
5]=J[e+(4*k+20)>>2],f[k+6]=J[e+(4*k+24)>>2],f[k+7]=J[e+(4*k+28)>>2],f[k+8]=J[e+(4*k+32)>>2]}else f=J.subarray(e>>2,e+36*b>>2);Q.uniformMatrix3fv(V(a),!!c,f)}},va:(a,b,c,e)=>{if(2<=z.version)b&&Q.uniformMatrix4fv(V(a),!!c,J,e>>2,16*b);else{if(18>=b){var f=wd[16*b],k=J;e>>=2;b*=16;for(var n=0;n<b;n+=16){var l=e+n;f[n]=k[l];f[n+1]=k[l+1];f[n+2]=k[l+2];f[n+3]=k[l+3];f[n+4]=k[l+4];f[n+5]=k[l+5];f[n+6]=k[l+6];f[n+7]=k[l+7];f[n+8]=k[l+8];f[n+9]=k[l+9];f[n+10]=k[l+10];f[n+11]=k[l+11];f[n+12]=k[l+12];f[n+
13]=k[l+13];f[n+14]=k[l+14];f[n+15]=k[l+15]}}else f=J.subarray(e>>2,e+64*b>>2);Q.uniformMatrix4fv(V(a),!!c,f)}},ua:a=>{a=Nc[a];Q.useProgram(a);Q.bf=a},ta:(a,b)=>Q.vertexAttrib1f(a,b),sa:(a,b)=>{Q.vertexAttrib2f(a,J[b>>2],J[b+4>>2])},ra:(a,b)=>{Q.vertexAttrib3f(a,J[b>>2],J[b+4>>2],J[b+8>>2])},qa:(a,b)=>{Q.vertexAttrib4f(a,J[b>>2],J[b+4>>2],J[b+8>>2],J[b+12>>2])},pa:(a,b)=>{Q.vertexAttribDivisor(a,b)},oa:(a,b,c,e,f)=>{Q.vertexAttribIPointer(a,b,c,e,f)},na:(a,b,c,e,f,k)=>{Q.vertexAttribPointer(a,b,c,
!!e,f,k)},ma:(a,b,c,e)=>Q.viewport(a,b,c,e),la:(a,b,c,e)=>{Q.waitSync(Uc[a],b,(c>>>0)+4294967296*e)},ka:a=>{var b=B.length;a>>>=0;if(2147483648<a)return!1;for(var c=1;4>=c;c*=2){var e=b*(1+1/c);e=Math.min(e,a+100663296);a:{e=(Math.min(2147483648,65536*Math.ceil(Math.max(a,e)/65536))-za.buffer.byteLength+65535)/65536|0;try{za.grow(e);Ha();var f=1;break a}catch(k){}f=void 0}if(f)return!0}return!1},ja:()=>z?z.handle:0,qd:(a,b)=>{var c=0;Ad().forEach((e,f)=>{var k=b+c;f=H[a+4*f>>2]=k;for(k=0;k<e.length;++k)Ca[f++]=
e.charCodeAt(k);Ca[f]=0;c+=e.length+1});return 0},pd:(a,b)=>{var c=Ad();H[a>>2]=c.length;var e=0;c.forEach(f=>e+=f.length+1);H[b>>2]=e;return 0},ia:a=>{throw new Va(a);},N:()=>52,_:function(){return 52},od:()=>52,Z:function(){return 70},T:(a,b,c,e)=>{for(var f=0,k=0;k<c;k++){var n=H[b>>2],l=H[b+4>>2];b+=8;for(var p=0;p<l;p++){var v=B[n+p],w=Bd[a];0===v||10===v?((1===a?wa:ya)(cb(w)),w.length=0):w.push(v)}f+=l}H[e>>2]=f;return 0},ha:cd,ga:ed,fa:fd,ea:gd,J:nd,Q:rd,da:sd,k:Hd,u:Id,i:Jd,I:Kd,ca:Ld,P:Md,
O:Nd,s:Od,x:Pd,r:Qd,v:Rd,ba:Sd,aa:Td,$:Ud},Z=function(){var a={a:Vd};La++;Ra??=r.locateFile?Qa("canvaskit.wasm")?"canvaskit.wasm":ta+"canvaskit.wasm":(new URL("canvaskit.wasm",import.meta.url)).href;Ua(a,function(b){Z=b.instance.exports;za=Z.wd;Ha();O=Z.zd;Ja.unshift(Z.xd);La--;0==La&&(null!==Ma&&(clearInterval(Ma),Ma=null),Oa&&(b=Oa,Oa=null,b()))}).catch(ca);return{}}(),bc=a=>(bc=Z.yd)(a),pd=r._malloc=a=>(pd=r._malloc=Z.Ad)(a),cc=r._free=a=>(cc=r._free=Z.Bd)(a),Wd=(a,b)=>(Wd=Z.Cd)(a,b),
Xd=a=>(Xd=Z.Dd)(a),Yd=()=>(Yd=Z.Ed)();r.dynCall_viji=(a,b,c,e,f)=>(r.dynCall_viji=Z.Fd)(a,b,c,e,f);r.dynCall_vijiii=(a,b,c,e,f,k,n)=>(r.dynCall_vijiii=Z.Gd)(a,b,c,e,f,k,n);r.dynCall_viiiiij=(a,b,c,e,f,k,n,l)=>(r.dynCall_viiiiij=Z.Hd)(a,b,c,e,f,k,n,l);r.dynCall_iiiji=(a,b,c,e,f,k)=>(r.dynCall_iiiji=Z.Id)(a,b,c,e,f,k);r.dynCall_jii=(a,b,c)=>(r.dynCall_jii=Z.Jd)(a,b,c);r.dynCall_vij=(a,b,c,e)=>(r.dynCall_vij=Z.Kd)(a,b,c,e);r.dynCall_jiiiiii=(a,b,c,e,f,k,n)=>(r.dynCall_jiiiiii=Z.Ld)(a,b,c,e,f,k,n);
r.dynCall_jiiiiji=(a,b,c,e,f,k,n,l)=>(r.dynCall_jiiiiji=Z.Md)(a,b,c,e,f,k,n,l);r.dynCall_ji=(a,b)=>(r.dynCall_ji=Z.Nd)(a,b);r.dynCall_iijj=(a,b,c,e,f,k)=>(r.dynCall_iijj=Z.Od)(a,b,c,e,f,k);r.dynCall_iiji=(a,b,c,e,f)=>(r.dynCall_iiji=Z.Pd)(a,b,c,e,f);r.dynCall_iijjiii=(a,b,c,e,f,k,n,l,p)=>(r.dynCall_iijjiii=Z.Qd)(a,b,c,e,f,k,n,l,p);r.dynCall_iij=(a,b,c,e)=>(r.dynCall_iij=Z.Rd)(a,b,c,e);r.dynCall_vijjjii=(a,b,c,e,f,k,n,l,p,v)=>(r.dynCall_vijjjii=Z.Sd)(a,b,c,e,f,k,n,l,p,v);
r.dynCall_jiji=(a,b,c,e,f)=>(r.dynCall_jiji=Z.Td)(a,b,c,e,f);r.dynCall_viijii=(a,b,c,e,f,k,n)=>(r.dynCall_viijii=Z.Ud)(a,b,c,e,f,k,n);r.dynCall_iiiiij=(a,b,c,e,f,k,n)=>(r.dynCall_iiiiij=Z.Vd)(a,b,c,e,f,k,n);r.dynCall_iiiiijj=(a,b,c,e,f,k,n,l,p)=>(r.dynCall_iiiiijj=Z.Wd)(a,b,c,e,f,k,n,l,p);r.dynCall_iiiiiijj=(a,b,c,e,f,k,n,l,p,v)=>(r.dynCall_iiiiiijj=Z.Xd)(a,b,c,e,f,k,n,l,p,v);function Rd(a,b,c,e,f){var k=Yd();try{O.get(a)(b,c,e,f)}catch(n){Xd(k);if(n!==n+0)throw n;Wd(1,0)}}
function Id(a,b,c){var e=Yd();try{return O.get(a)(b,c)}catch(f){Xd(e);if(f!==f+0)throw f;Wd(1,0)}}function Pd(a,b,c){var e=Yd();try{O.get(a)(b,c)}catch(f){Xd(e);if(f!==f+0)throw f;Wd(1,0)}}function Hd(a,b){var c=Yd();try{return O.get(a)(b)}catch(e){Xd(c);if(e!==e+0)throw e;Wd(1,0)}}function Od(a,b){var c=Yd();try{O.get(a)(b)}catch(e){Xd(c);if(e!==e+0)throw e;Wd(1,0)}}function Jd(a,b,c,e){var f=Yd();try{return O.get(a)(b,c,e)}catch(k){Xd(f);if(k!==k+0)throw k;Wd(1,0)}}
function Ud(a,b,c,e,f,k,n,l,p,v){var w=Yd();try{O.get(a)(b,c,e,f,k,n,l,p,v)}catch(A){Xd(w);if(A!==A+0)throw A;Wd(1,0)}}function Qd(a,b,c,e){var f=Yd();try{O.get(a)(b,c,e)}catch(k){Xd(f);if(k!==k+0)throw k;Wd(1,0)}}function Td(a,b,c,e,f,k,n){var l=Yd();try{O.get(a)(b,c,e,f,k,n)}catch(p){Xd(l);if(p!==p+0)throw p;Wd(1,0)}}function Md(a,b,c,e,f,k,n,l){var p=Yd();try{return O.get(a)(b,c,e,f,k,n,l)}catch(v){Xd(p);if(v!==v+0)throw v;Wd(1,0)}}
function Sd(a,b,c,e,f,k){var n=Yd();try{O.get(a)(b,c,e,f,k)}catch(l){Xd(n);if(l!==l+0)throw l;Wd(1,0)}}function Kd(a,b,c,e,f){var k=Yd();try{return O.get(a)(b,c,e,f)}catch(n){Xd(k);if(n!==n+0)throw n;Wd(1,0)}}function Nd(a,b,c,e,f,k,n,l,p,v){var w=Yd();try{return O.get(a)(b,c,e,f,k,n,l,p,v)}catch(A){Xd(w);if(A!==A+0)throw A;Wd(1,0)}}function Ld(a,b,c,e,f,k,n){var l=Yd();try{return O.get(a)(b,c,e,f,k,n)}catch(p){Xd(l);if(p!==p+0)throw p;Wd(1,0)}}var Zd,$d;Oa=function ae(){Zd||be();Zd||(Oa=ae)};
function be(){if(!(0<La)){if(!$d&&($d=1,Wa(Ia),0<La))return;Zd||(Zd=1,r.calledRun=1,Aa||(Wa(Ja),aa(r),r.onRuntimeInitialized?.(),Wa(Ka)))}}be();moduleRtn=da;
return moduleRtn;
}
);
})();
export default CanvasKitInit;

Binary file not shown.

View File

@ -127,9 +127,29 @@
<img class="center" aria-hidden="true" src="splash/img/light-1x.png" alt=""> <img class="center" aria-hidden="true" src="splash/img/light-1x.png" alt="">
</picture> </picture>
<script>
{{flutter_js}}
{{flutter_build_config}}
<script src="flutter_bootstrap.js" async=""></script> const searchParams = new URLSearchParams(window.location.search);
const renderer = searchParams.get('renderer');
let cdn = searchParams.get('cdn');
if (cdn) {
localStorage.setItem('sn-web-canvaskit-cdn', cdn);
} else {
const storagedCdn = localStorage.getItem('sn-web-canvaskit-cdn');
cdn = storagedCdn ?? 'com';
}
_flutter.loader.load({
config: {
renderer: renderer ?? 'canvaskit',
canvasKitVariant: 'full',
canvasKitBaseUrl: `https://www.gstatic.${cdn}/flutter-canvaskit/f73bfc4522dd0bc87bbcdb4bb3088082755c5e87`,
},
});
</script>
</body> </body>
</html> </html>