Compare commits
11 Commits
Author | SHA1 | Date | |
---|---|---|---|
cb2b71d194 | |||
7ed508e2bb | |||
dad869967e | |||
2d5b3b554e | |||
74882116e3 | |||
a97c3bce3a | |||
1aa70827dc | |||
fe028860e9 | |||
a2d2ce4d38 | |||
167c11b9eb | |||
8cb3933fcc |
@ -12,9 +12,9 @@ post {
|
|||||||
|
|
||||||
body:json {
|
body:json {
|
||||||
{
|
{
|
||||||
"alias": "Meltdown",
|
"alias": "BaLoading",
|
||||||
"name": "Meltdown",
|
"name": "BaLoading",
|
||||||
"attachment_id": "IpDPHEbWDDCbBofX",
|
"attachment_id": "2JCI2uh21mKkfk9P",
|
||||||
"pack_id": 4
|
"pack_id": 3
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -154,9 +154,12 @@
|
|||||||
"fieldPublisherBelongToRealmUnset": "Unset Publisher Belongs to Realm",
|
"fieldPublisherBelongToRealmUnset": "Unset Publisher Belongs to Realm",
|
||||||
"writePostTypeStory": "Post a story",
|
"writePostTypeStory": "Post a story",
|
||||||
"writePostTypeArticle": "Write an article",
|
"writePostTypeArticle": "Write an article",
|
||||||
|
"writePostTypeQuestion": "Ask a question",
|
||||||
|
"writePostTypeVideo": "Post a video",
|
||||||
"fieldPostPublisher": "Post publisher",
|
"fieldPostPublisher": "Post publisher",
|
||||||
"fieldPostContent": "What happened?!",
|
"fieldPostContent": "What happened?!",
|
||||||
"fieldPostTitle": "Title",
|
"fieldPostTitle": "Title",
|
||||||
|
"fieldPostQuestionReward": "Answer Rewards (Source Points)",
|
||||||
"fieldPostDescription": "Description",
|
"fieldPostDescription": "Description",
|
||||||
"fieldPostTags": "Tags",
|
"fieldPostTags": "Tags",
|
||||||
"fieldPostCategories": "Categories",
|
"fieldPostCategories": "Categories",
|
||||||
@ -166,9 +169,9 @@
|
|||||||
"postPosted": "Post has been posted.",
|
"postPosted": "Post has been posted.",
|
||||||
"postPublishedAt": "Published At",
|
"postPublishedAt": "Published At",
|
||||||
"postPublishedUntil": "Published Until",
|
"postPublishedUntil": "Published Until",
|
||||||
"postEditingNotice": "You're about to editing a post that posted {}.",
|
"postEditingNotice": "You're about to editing a post that posted by {}.",
|
||||||
"postReplyingNotice": "You're about to reply to a post that posted {}.",
|
"postReplyingNotice": "You're about to reply to a post that posted by {}.",
|
||||||
"postRepostingNotice": "You're about to repost a post that posted {}.",
|
"postRepostingNotice": "You're about to repost a post that posted by {}.",
|
||||||
"postReact": "React",
|
"postReact": "React",
|
||||||
"postReactions": "Reactions of Post",
|
"postReactions": "Reactions of Post",
|
||||||
"postReactionUpvote": {
|
"postReactionUpvote": {
|
||||||
@ -610,5 +613,11 @@
|
|||||||
},
|
},
|
||||||
"aiThinkingProcess": "AI Thinking Process",
|
"aiThinkingProcess": "AI Thinking Process",
|
||||||
"accountSettingsApplied": "Account settings have been applied.",
|
"accountSettingsApplied": "Account settings have been applied.",
|
||||||
"trayMenuExit": "Exit"
|
"trayMenuExit": "Exit",
|
||||||
|
"postQuestionUnanswered": "Unanswered Question",
|
||||||
|
"postQuestionUnansweredWithReward": "Unanswered Question, reward source points {}",
|
||||||
|
"postQuestionAnswered": "Answered Question",
|
||||||
|
"postQuestionAnswerSelect": "Select as Answer",
|
||||||
|
"postQuestionAnswerSelected": "Answer has been selected, reward has been applied.",
|
||||||
|
"postVideoUpload": "Upload Video"
|
||||||
}
|
}
|
||||||
|
@ -138,9 +138,12 @@
|
|||||||
"fieldPublisherBelongToRealmUnset": "未设置发布者所属领域",
|
"fieldPublisherBelongToRealmUnset": "未设置发布者所属领域",
|
||||||
"writePostTypeStory": "发动态",
|
"writePostTypeStory": "发动态",
|
||||||
"writePostTypeArticle": "写文章",
|
"writePostTypeArticle": "写文章",
|
||||||
|
"writePostTypeQuestion": "提问题",
|
||||||
|
"writePostTypeVideo": "发视频",
|
||||||
"fieldPostPublisher": "帖子发布者",
|
"fieldPostPublisher": "帖子发布者",
|
||||||
"fieldPostContent": "发生什么事了?!",
|
"fieldPostContent": "发生什么事了?!",
|
||||||
"fieldPostTitle": "标题",
|
"fieldPostTitle": "标题",
|
||||||
|
"fieldPostQuestionReward": "回答奖励源点",
|
||||||
"fieldPostDescription": "描述",
|
"fieldPostDescription": "描述",
|
||||||
"fieldPostTags": "标签",
|
"fieldPostTags": "标签",
|
||||||
"fieldPostCategories": "分类",
|
"fieldPostCategories": "分类",
|
||||||
@ -608,5 +611,12 @@
|
|||||||
},
|
},
|
||||||
"aiThinkingProcess": "AI 思考过程",
|
"aiThinkingProcess": "AI 思考过程",
|
||||||
"accountSettingsApplied": "帐号设置已应用。",
|
"accountSettingsApplied": "帐号设置已应用。",
|
||||||
"trayMenuExit": "退出"
|
"trayMenuExit": "退出",
|
||||||
|
"postQuestionUnanswered": "未解答的问题",
|
||||||
|
"postQuestionUnansweredWithReward": "未解答的问题,悬赏源点 {}",
|
||||||
|
"postQuestionAnswered": "已解答的问题",
|
||||||
|
"postQuestionAnswerTitle": "精选解答",
|
||||||
|
"postQuestionAnswerSelect": "选择解答",
|
||||||
|
"postQuestionAnswerSelected": "解答已选择,奖励已发放。",
|
||||||
|
"postVideoUpload": "上传视频"
|
||||||
}
|
}
|
||||||
|
@ -138,9 +138,11 @@
|
|||||||
"fieldPublisherBelongToRealmUnset": "未設置發佈者所屬領域",
|
"fieldPublisherBelongToRealmUnset": "未設置發佈者所屬領域",
|
||||||
"writePostTypeStory": "發動態",
|
"writePostTypeStory": "發動態",
|
||||||
"writePostTypeArticle": "寫文章",
|
"writePostTypeArticle": "寫文章",
|
||||||
|
"writePostTypeQuestion": "提問題",
|
||||||
"fieldPostPublisher": "帖子發佈者",
|
"fieldPostPublisher": "帖子發佈者",
|
||||||
"fieldPostContent": "發生什麼事了?!",
|
"fieldPostContent": "發生什麼事了?!",
|
||||||
"fieldPostTitle": "標題",
|
"fieldPostTitle": "標題",
|
||||||
|
"fieldPostQuestionReward": "回答獎勵源點",
|
||||||
"fieldPostDescription": "描述",
|
"fieldPostDescription": "描述",
|
||||||
"fieldPostTags": "標籤",
|
"fieldPostTags": "標籤",
|
||||||
"fieldPostCategories": "分類",
|
"fieldPostCategories": "分類",
|
||||||
@ -607,5 +609,12 @@
|
|||||||
"other": "{} 源點"
|
"other": "{} 源點"
|
||||||
},
|
},
|
||||||
"aiThinkingProcess": "AI 思考過程",
|
"aiThinkingProcess": "AI 思考過程",
|
||||||
"accountSettingsApplied": "帳號設置已應用。"
|
"accountSettingsApplied": "帳號設置已應用。",
|
||||||
|
"trayMenuExit": "退出",
|
||||||
|
"postQuestionUnanswered": "未解答的問題",
|
||||||
|
"postQuestionUnansweredWithReward": "未解答的問題,懸賞源點 {}",
|
||||||
|
"postQuestionAnswered": "已解答的問題",
|
||||||
|
"postQuestionAnswerTitle": "精選解答",
|
||||||
|
"postQuestionAnswerSelect": "選擇解答",
|
||||||
|
"postQuestionAnswerSelected": "解答已選擇,獎勵已發放。"
|
||||||
}
|
}
|
||||||
|
@ -138,9 +138,11 @@
|
|||||||
"fieldPublisherBelongToRealmUnset": "未設置發佈者所屬領域",
|
"fieldPublisherBelongToRealmUnset": "未設置發佈者所屬領域",
|
||||||
"writePostTypeStory": "發動態",
|
"writePostTypeStory": "發動態",
|
||||||
"writePostTypeArticle": "寫文章",
|
"writePostTypeArticle": "寫文章",
|
||||||
|
"writePostTypeQuestion": "提問題",
|
||||||
"fieldPostPublisher": "帖子發佈者",
|
"fieldPostPublisher": "帖子發佈者",
|
||||||
"fieldPostContent": "發生什麼事了?!",
|
"fieldPostContent": "發生什麼事了?!",
|
||||||
"fieldPostTitle": "標題",
|
"fieldPostTitle": "標題",
|
||||||
|
"fieldPostQuestionReward": "回答獎勵源點",
|
||||||
"fieldPostDescription": "描述",
|
"fieldPostDescription": "描述",
|
||||||
"fieldPostTags": "標籤",
|
"fieldPostTags": "標籤",
|
||||||
"fieldPostCategories": "分類",
|
"fieldPostCategories": "分類",
|
||||||
@ -607,5 +609,12 @@
|
|||||||
"other": "{} 源點"
|
"other": "{} 源點"
|
||||||
},
|
},
|
||||||
"aiThinkingProcess": "AI 思考過程",
|
"aiThinkingProcess": "AI 思考過程",
|
||||||
"accountSettingsApplied": "帳號設置已應用。"
|
"accountSettingsApplied": "帳號設置已應用。",
|
||||||
|
"trayMenuExit": "退出",
|
||||||
|
"postQuestionUnanswered": "未解答的問題",
|
||||||
|
"postQuestionUnansweredWithReward": "未解答的問題,懸賞源點 {}",
|
||||||
|
"postQuestionAnswered": "已解答的問題",
|
||||||
|
"postQuestionAnswerTitle": "精選解答",
|
||||||
|
"postQuestionAnswerSelect": "選擇解答",
|
||||||
|
"postQuestionAnswerSelected": "解答已選擇,獎勵已發放。"
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,6 @@ PODS:
|
|||||||
- Alamofire (5.10.2)
|
- Alamofire (5.10.2)
|
||||||
- connectivity_plus (0.0.1):
|
- connectivity_plus (0.0.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
- FlutterMacOS
|
|
||||||
- croppy (0.0.1):
|
- croppy (0.0.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
- device_info_plus (0.0.1):
|
- device_info_plus (0.0.1):
|
||||||
@ -180,7 +179,7 @@ PODS:
|
|||||||
- in_app_review (2.0.0):
|
- in_app_review (2.0.0):
|
||||||
- Flutter
|
- Flutter
|
||||||
- Kingfisher (8.2.0)
|
- Kingfisher (8.2.0)
|
||||||
- livekit_client (2.3.5):
|
- livekit_client (2.3.6):
|
||||||
- Flutter
|
- Flutter
|
||||||
- flutter_webrtc
|
- flutter_webrtc
|
||||||
- WebRTC-SDK (= 125.6422.06)
|
- WebRTC-SDK (= 125.6422.06)
|
||||||
@ -237,7 +236,7 @@ PODS:
|
|||||||
|
|
||||||
DEPENDENCIES:
|
DEPENDENCIES:
|
||||||
- Alamofire
|
- Alamofire
|
||||||
- connectivity_plus (from `.symlinks/plugins/connectivity_plus/darwin`)
|
- connectivity_plus (from `.symlinks/plugins/connectivity_plus/ios`)
|
||||||
- croppy (from `.symlinks/plugins/croppy/ios`)
|
- croppy (from `.symlinks/plugins/croppy/ios`)
|
||||||
- device_info_plus (from `.symlinks/plugins/device_info_plus/ios`)
|
- device_info_plus (from `.symlinks/plugins/device_info_plus/ios`)
|
||||||
- file_picker (from `.symlinks/plugins/file_picker/ios`)
|
- file_picker (from `.symlinks/plugins/file_picker/ios`)
|
||||||
@ -300,7 +299,7 @@ SPEC REPOS:
|
|||||||
|
|
||||||
EXTERNAL SOURCES:
|
EXTERNAL SOURCES:
|
||||||
connectivity_plus:
|
connectivity_plus:
|
||||||
:path: ".symlinks/plugins/connectivity_plus/darwin"
|
:path: ".symlinks/plugins/connectivity_plus/ios"
|
||||||
croppy:
|
croppy:
|
||||||
:path: ".symlinks/plugins/croppy/ios"
|
:path: ".symlinks/plugins/croppy/ios"
|
||||||
device_info_plus:
|
device_info_plus:
|
||||||
@ -374,7 +373,7 @@ EXTERNAL SOURCES:
|
|||||||
|
|
||||||
SPEC CHECKSUMS:
|
SPEC CHECKSUMS:
|
||||||
Alamofire: 7193b3b92c74a07f85569e1a6c4f4237291e7496
|
Alamofire: 7193b3b92c74a07f85569e1a6c4f4237291e7496
|
||||||
connectivity_plus: 18382e7311ba19efcaee94442b23b32507b20695
|
connectivity_plus: 2a701ffec2c0ae28a48cf7540e279787e77c447d
|
||||||
croppy: b6199bc8d56bd2e03cc11609d1c47ad9875c1321
|
croppy: b6199bc8d56bd2e03cc11609d1c47ad9875c1321
|
||||||
device_info_plus: bf2e3232933866d73fe290f2942f2156cdd10342
|
device_info_plus: bf2e3232933866d73fe290f2942f2156cdd10342
|
||||||
DKImagePickerController: 946cec48c7873164274ecc4624d19e3da4c1ef3c
|
DKImagePickerController: 946cec48c7873164274ecc4624d19e3da4c1ef3c
|
||||||
@ -404,7 +403,7 @@ SPEC CHECKSUMS:
|
|||||||
image_picker_ios: c560581cceedb403a6ff17f2f816d7fea1421fc1
|
image_picker_ios: c560581cceedb403a6ff17f2f816d7fea1421fc1
|
||||||
in_app_review: a31b5257259646ea78e0e35fc914979b0031d011
|
in_app_review: a31b5257259646ea78e0e35fc914979b0031d011
|
||||||
Kingfisher: 323e5c4ec7983aaace12af655a7b51a7f88a599d
|
Kingfisher: 323e5c4ec7983aaace12af655a7b51a7f88a599d
|
||||||
livekit_client: dcc5fd47ba69c98fc6baeb12e862c9d43807d976
|
livekit_client: 148b2cf67a09aaf475ba8e5bf1667fe10dc35f81
|
||||||
media_kit_libs_ios_video: a5fe24bc7875ccd6378a0978c13185e1344651c1
|
media_kit_libs_ios_video: a5fe24bc7875ccd6378a0978c13185e1344651c1
|
||||||
media_kit_native_event_loop: e6b2ab20cf0746eb1c33be961fcf79667304fa2a
|
media_kit_native_event_loop: e6b2ab20cf0746eb1c33be961fcf79667304fa2a
|
||||||
media_kit_video: 5da63f157170e5bf303bf85453b7ef6971218a2e
|
media_kit_video: 5da63f157170e5bf303bf85453b7ef6971218a2e
|
||||||
|
@ -71,7 +71,7 @@ class ChatMessageController extends ChangeNotifier {
|
|||||||
resp.data as Map<String, dynamic>,
|
resp.data as Map<String, dynamic>,
|
||||||
);
|
);
|
||||||
|
|
||||||
_wsSubscription = _ws.stream.stream.listen((event) {
|
_wsSubscription = _ws.pk.stream.listen((event) {
|
||||||
switch (event.method) {
|
switch (event.method) {
|
||||||
case 'events.new':
|
case 'events.new':
|
||||||
if (event.payload?['channel_id'] != channel?.id) break;
|
if (event.payload?['channel_id'] != channel?.id) break;
|
||||||
|
@ -144,6 +144,8 @@ class PostWriteController extends ChangeNotifier {
|
|||||||
static const Map<String, String> kTitleMap = {
|
static const Map<String, String> kTitleMap = {
|
||||||
'stories': 'writePostTypeStory',
|
'stories': 'writePostTypeStory',
|
||||||
'articles': 'writePostTypeArticle',
|
'articles': 'writePostTypeArticle',
|
||||||
|
'questions': 'writePostTypeQuestion',
|
||||||
|
'videos': 'writePostTypeVideo',
|
||||||
};
|
};
|
||||||
|
|
||||||
static const kAttachmentProgressWeight = 0.9;
|
static const kAttachmentProgressWeight = 0.9;
|
||||||
@ -153,6 +155,7 @@ class PostWriteController extends ChangeNotifier {
|
|||||||
final TextEditingController titleController = TextEditingController();
|
final TextEditingController titleController = TextEditingController();
|
||||||
final TextEditingController descriptionController = TextEditingController();
|
final TextEditingController descriptionController = TextEditingController();
|
||||||
final TextEditingController aliasController = TextEditingController();
|
final TextEditingController aliasController = TextEditingController();
|
||||||
|
final TextEditingController rewardController = TextEditingController();
|
||||||
|
|
||||||
bool _temporarySaveActive = false;
|
bool _temporarySaveActive = false;
|
||||||
|
|
||||||
@ -168,6 +171,7 @@ class PostWriteController extends ChangeNotifier {
|
|||||||
});
|
});
|
||||||
contentController.addListener(() {
|
contentController.addListener(() {
|
||||||
_temporaryPlanSave();
|
_temporaryPlanSave();
|
||||||
|
notifyListeners();
|
||||||
});
|
});
|
||||||
if (doLoadFromTemporary) _temporaryLoad();
|
if (doLoadFromTemporary) _temporaryLoad();
|
||||||
}
|
}
|
||||||
@ -194,6 +198,7 @@ class PostWriteController extends ChangeNotifier {
|
|||||||
PostWriteMedia? thumbnail;
|
PostWriteMedia? thumbnail;
|
||||||
List<PostWriteMedia> attachments = List.empty(growable: true);
|
List<PostWriteMedia> attachments = List.empty(growable: true);
|
||||||
DateTime? publishedAt, publishedUntil;
|
DateTime? publishedAt, publishedUntil;
|
||||||
|
SnAttachment? videoAttachment;
|
||||||
|
|
||||||
Future<void> fetchRelatedPost(
|
Future<void> fetchRelatedPost(
|
||||||
BuildContext context, {
|
BuildContext context, {
|
||||||
@ -214,6 +219,7 @@ class PostWriteController extends ChangeNotifier {
|
|||||||
descriptionController.text = post.body['description'] ?? '';
|
descriptionController.text = post.body['description'] ?? '';
|
||||||
contentController.text = post.body['content'] ?? '';
|
contentController.text = post.body['content'] ?? '';
|
||||||
aliasController.text = post.alias ?? '';
|
aliasController.text = post.alias ?? '';
|
||||||
|
rewardController.text = post.body['reward']?.toString() ?? '';
|
||||||
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);
|
||||||
@ -347,6 +353,7 @@ class PostWriteController extends ChangeNotifier {
|
|||||||
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 (thumbnail != null && thumbnail!.attachment != null) 'thumbnail': thumbnail!.attachment!.toJson(),
|
if (thumbnail != null && thumbnail!.attachment != null) 'thumbnail': thumbnail!.attachment!.toJson(),
|
||||||
'attachments':
|
'attachments':
|
||||||
attachments.where((e) => e.attachment != null).map((e) => e.attachment!.toJson()).toList(growable: true),
|
attachments.where((e) => e.attachment != null).map((e) => e.attachment!.toJson()).toList(growable: true),
|
||||||
@ -375,6 +382,7 @@ class PostWriteController extends ChangeNotifier {
|
|||||||
aliasController.text = data['alias'] ?? '';
|
aliasController.text = data['alias'] ?? '';
|
||||||
titleController.text = data['title'] ?? '';
|
titleController.text = data['title'] ?? '';
|
||||||
descriptionController.text = data['description'] ?? '';
|
descriptionController.text = data['description'] ?? '';
|
||||||
|
rewardController.text = data['reward']?.toString() ?? '';
|
||||||
if (data['thumbnail'] != null) thumbnail = PostWriteMedia(SnAttachment.fromJson(data['thumbnail']));
|
if (data['thumbnail'] != null) thumbnail = PostWriteMedia(SnAttachment.fromJson(data['thumbnail']));
|
||||||
attachments
|
attachments
|
||||||
.addAll(data['attachments'].map((ele) => PostWriteMedia(SnAttachment.fromJson(ele))).cast<PostWriteMedia>());
|
.addAll(data['attachments'].map((ele) => PostWriteMedia(SnAttachment.fromJson(ele))).cast<PostWriteMedia>());
|
||||||
@ -473,6 +481,8 @@ class PostWriteController extends ChangeNotifier {
|
|||||||
progress = kAttachmentProgressWeight;
|
progress = kAttachmentProgressWeight;
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
|
|
||||||
|
final reward = double.tryParse(rewardController.text);
|
||||||
|
|
||||||
// Posting the content
|
// Posting the content
|
||||||
try {
|
try {
|
||||||
final baseProgressVal = progress!;
|
final baseProgressVal = progress!;
|
||||||
@ -498,6 +508,8 @@ class PostWriteController extends ChangeNotifier {
|
|||||||
if (publishedUntil != null) 'published_until': 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 (videoAttachment != null) 'video': videoAttachment!.rid,
|
||||||
},
|
},
|
||||||
onSendProgress: (count, total) {
|
onSendProgress: (count, total) {
|
||||||
progress = baseProgressVal + (count / total) * (kPostingProgressWeight / 2);
|
progress = baseProgressVal + (count / total) * (kPostingProgressWeight / 2);
|
||||||
@ -624,6 +636,11 @@ class PostWriteController extends ChangeNotifier {
|
|||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void setVideoAttachment(SnAttachment? value) {
|
||||||
|
videoAttachment = value;
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
|
||||||
void reset() {
|
void reset() {
|
||||||
publishedAt = null;
|
publishedAt = null;
|
||||||
publishedUntil = null;
|
publishedUntil = null;
|
||||||
|
@ -77,7 +77,7 @@ class NotificationProvider extends ChangeNotifier {
|
|||||||
List<SnNotification> notifications = List.empty(growable: true);
|
List<SnNotification> notifications = List.empty(growable: true);
|
||||||
|
|
||||||
void listen() {
|
void listen() {
|
||||||
_ws.stream.stream.listen((event) {
|
_ws.pk.stream.listen((event) {
|
||||||
if (event.method == 'notifications.new') {
|
if (event.method == 'notifications.new') {
|
||||||
final notification = SnNotification.fromJson(event.payload!);
|
final notification = SnNotification.fromJson(event.payload!);
|
||||||
if (showingCount < 0) showingCount = 0;
|
if (showingCount < 0) showingCount = 0;
|
||||||
@ -103,10 +103,10 @@ class NotificationProvider extends ChangeNotifier {
|
|||||||
|
|
||||||
void updateTray() {
|
void updateTray() {
|
||||||
if (kIsWeb || Platform.isAndroid || Platform.isIOS) return;
|
if (kIsWeb || Platform.isAndroid || Platform.isIOS) return;
|
||||||
if (notifications.isEmpty) {
|
if (showingTrayCount == 0) {
|
||||||
trayManager.setTitle('');
|
trayManager.setTitle('');
|
||||||
} else {
|
} else {
|
||||||
trayManager.setTitle(' ${notifications.length.toString()}');
|
trayManager.setTitle(' $showingTrayCount');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -23,6 +23,9 @@ class SnPostContentProvider {
|
|||||||
if (out[i].body['thumbnail'] != null) {
|
if (out[i].body['thumbnail'] != null) {
|
||||||
rids.add(out[i].body['thumbnail']);
|
rids.add(out[i].body['thumbnail']);
|
||||||
}
|
}
|
||||||
|
if (out[i].body['video'] != null) {
|
||||||
|
rids.add(out[i].body['video']);
|
||||||
|
}
|
||||||
if (out[i].repostTo != null) {
|
if (out[i].repostTo != null) {
|
||||||
out[i] = out[i].copyWith(
|
out[i] = out[i].copyWith(
|
||||||
repostTo: await _preloadRelatedDataSingle(out[i].repostTo!),
|
repostTo: await _preloadRelatedDataSingle(out[i].repostTo!),
|
||||||
@ -36,6 +39,7 @@ class SnPostContentProvider {
|
|||||||
preload: SnPostPreload(
|
preload: SnPostPreload(
|
||||||
thumbnail: attachments.where((ele) => ele?.rid == out[i].body['thumbnail']).firstOrNull,
|
thumbnail: attachments.where((ele) => ele?.rid == out[i].body['thumbnail']).firstOrNull,
|
||||||
attachments: attachments.where((ele) => out[i].body['attachments']?.contains(ele?.rid) ?? false).toList(),
|
attachments: attachments.where((ele) => out[i].body['attachments']?.contains(ele?.rid) ?? false).toList(),
|
||||||
|
video: attachments.where((ele) => ele?.rid == out[i].body['video']).firstOrNull,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -53,6 +57,9 @@ class SnPostContentProvider {
|
|||||||
if (out.body['thumbnail'] != null) {
|
if (out.body['thumbnail'] != null) {
|
||||||
rids.add(out.body['thumbnail']);
|
rids.add(out.body['thumbnail']);
|
||||||
}
|
}
|
||||||
|
if (out.body['video'] != null) {
|
||||||
|
rids.add(out.body['video']);
|
||||||
|
}
|
||||||
if (out.repostTo != null) {
|
if (out.repostTo != null) {
|
||||||
out = out.copyWith(
|
out = out.copyWith(
|
||||||
repostTo: await _preloadRelatedDataSingle(out.repostTo!),
|
repostTo: await _preloadRelatedDataSingle(out.repostTo!),
|
||||||
@ -64,6 +71,7 @@ class SnPostContentProvider {
|
|||||||
preload: SnPostPreload(
|
preload: SnPostPreload(
|
||||||
thumbnail: attachments.where((ele) => ele?.rid == out.body['thumbnail']).firstOrNull,
|
thumbnail: attachments.where((ele) => ele?.rid == out.body['thumbnail']).firstOrNull,
|
||||||
attachments: attachments.where((ele) => out.body['attachments']?.contains(ele?.rid) ?? false).toList(),
|
attachments: attachments.where((ele) => out.body['attachments']?.contains(ele?.rid) ?? false).toList(),
|
||||||
|
video: attachments.where((ele) => ele?.rid == out.body['video']).firstOrNull,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -18,7 +18,8 @@ class WebSocketProvider extends ChangeNotifier {
|
|||||||
late final SnNetworkProvider _sn;
|
late final SnNetworkProvider _sn;
|
||||||
late final UserProvider _ua;
|
late final UserProvider _ua;
|
||||||
|
|
||||||
StreamController<WebSocketPackage> stream = StreamController.broadcast();
|
StreamController<WebSocketPackage> pk = StreamController.broadcast();
|
||||||
|
Stream<dynamic>? _wsStream;
|
||||||
|
|
||||||
WebSocketProvider(BuildContext context) {
|
WebSocketProvider(BuildContext context) {
|
||||||
_sn = context.read<SnNetworkProvider>();
|
_sn = context.read<SnNetworkProvider>();
|
||||||
@ -36,7 +37,7 @@ class WebSocketProvider extends ChangeNotifier {
|
|||||||
Completer<void>? _connectCompleter;
|
Completer<void>? _connectCompleter;
|
||||||
|
|
||||||
Future<void> connect({noRetry = false}) async {
|
Future<void> connect({noRetry = false}) async {
|
||||||
if(_connectCompleter != null) {
|
if (_connectCompleter != null) {
|
||||||
await _connectCompleter!.future;
|
await _connectCompleter!.future;
|
||||||
_connectCompleter = null;
|
_connectCompleter = null;
|
||||||
}
|
}
|
||||||
@ -59,6 +60,7 @@ class WebSocketProvider extends ChangeNotifier {
|
|||||||
try {
|
try {
|
||||||
conn = WebSocketChannel.connect(uri);
|
conn = WebSocketChannel.connect(uri);
|
||||||
await conn!.ready;
|
await conn!.ready;
|
||||||
|
_wsStream = conn!.stream.asBroadcastStream();
|
||||||
listen();
|
listen();
|
||||||
log('[WebSocket] Connected to server!');
|
log('[WebSocket] Connected to server!');
|
||||||
isConnected = true;
|
isConnected = true;
|
||||||
@ -73,7 +75,7 @@ class WebSocketProvider extends ChangeNotifier {
|
|||||||
log('Retry connecting to websocket in 3 seconds...');
|
log('Retry connecting to websocket in 3 seconds...');
|
||||||
return Future.delayed(
|
return Future.delayed(
|
||||||
const Duration(seconds: 3),
|
const Duration(seconds: 3),
|
||||||
() => connect(noRetry: true),
|
() => connect(noRetry: true),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
@ -93,11 +95,12 @@ class WebSocketProvider extends ChangeNotifier {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void listen() {
|
void listen() {
|
||||||
conn?.stream.listen(
|
if (_wsStream == null) return;
|
||||||
|
_wsStream!.listen(
|
||||||
(event) {
|
(event) {
|
||||||
final packet = WebSocketPackage.fromJson(jsonDecode(event));
|
final packet = WebSocketPackage.fromJson(jsonDecode(event));
|
||||||
log('Websocket incoming message: ${packet.method} ${packet.message}');
|
log('Websocket incoming message: ${packet.method} ${packet.message}');
|
||||||
stream.sink.add(packet);
|
pk.sink.add(packet);
|
||||||
},
|
},
|
||||||
onDone: () {
|
onDone: () {
|
||||||
isConnected = false;
|
isConnected = false;
|
||||||
|
@ -155,12 +155,16 @@ class _CallRoomScreenState extends State<CallRoomScreen> {
|
|||||||
text: TextSpan(children: [
|
text: TextSpan(children: [
|
||||||
TextSpan(
|
TextSpan(
|
||||||
text: 'call'.tr(),
|
text: 'call'.tr(),
|
||||||
style: Theme.of(context).textTheme.titleLarge!.copyWith(color: Colors.white),
|
style: Theme.of(context).textTheme.titleLarge!.copyWith(
|
||||||
|
color: Theme.of(context).appBarTheme.foregroundColor,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
const TextSpan(text: '\n'),
|
const TextSpan(text: '\n'),
|
||||||
TextSpan(
|
TextSpan(
|
||||||
text: call.lastDuration.toString(),
|
text: call.lastDuration.toString(),
|
||||||
style: Theme.of(context).textTheme.bodySmall!.copyWith(color: Colors.white),
|
style: Theme.of(context).textTheme.bodySmall!.copyWith(
|
||||||
|
color: Theme.of(context).appBarTheme.foregroundColor,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
]),
|
]),
|
||||||
),
|
),
|
||||||
|
@ -206,7 +206,7 @@ class _ChatRoomScreenState extends State<ChatRoomScreen> {
|
|||||||
});
|
});
|
||||||
|
|
||||||
final ws = context.read<WebSocketProvider>();
|
final ws = context.read<WebSocketProvider>();
|
||||||
_wsSubscription = ws.stream.stream.listen((event) {
|
_wsSubscription = ws.pk.stream.listen((event) {
|
||||||
switch (event.method) {
|
switch (event.method) {
|
||||||
case 'calls.new':
|
case 'calls.new':
|
||||||
final payload = SnChatCall.fromJson(event.payload!);
|
final payload = SnChatCall.fromJson(event.payload!);
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
import 'package:animations/animations.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';
|
||||||
@ -7,10 +6,8 @@ import 'package:go_router/go_router.dart';
|
|||||||
import 'package:material_symbols_icons/symbols.dart';
|
import 'package:material_symbols_icons/symbols.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'package:styled_widget/styled_widget.dart';
|
import 'package:styled_widget/styled_widget.dart';
|
||||||
import 'package:surface/providers/config.dart';
|
|
||||||
import 'package:surface/providers/post.dart';
|
import 'package:surface/providers/post.dart';
|
||||||
import 'package:surface/providers/sn_network.dart';
|
import 'package:surface/providers/sn_network.dart';
|
||||||
import 'package:surface/screens/post/post_detail.dart';
|
|
||||||
import 'package:surface/types/post.dart';
|
import 'package:surface/types/post.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';
|
||||||
@ -97,8 +94,6 @@ class _ExploreScreenState extends State<ExploreScreen> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final cfg = context.read<ConfigProvider>();
|
|
||||||
|
|
||||||
return AppScaffold(
|
return AppScaffold(
|
||||||
floatingActionButtonLocation: ExpandableFab.location,
|
floatingActionButtonLocation: ExpandableFab.location,
|
||||||
floatingActionButton: ExpandableFab(
|
floatingActionButton: ExpandableFab(
|
||||||
@ -166,6 +161,48 @@ class _ExploreScreenState extends State<ExploreScreen> {
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
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),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
body: RefreshIndicator(
|
body: RefreshIndicator(
|
||||||
@ -225,34 +262,15 @@ class _ExploreScreenState extends State<ExploreScreen> {
|
|||||||
onFetchData: _fetchPosts,
|
onFetchData: _fetchPosts,
|
||||||
itemBuilder: (context, idx) {
|
itemBuilder: (context, idx) {
|
||||||
return Center(
|
return Center(
|
||||||
child: OpenContainer(
|
child: OpenablePostItem(
|
||||||
closedBuilder: (_, __) => Container(
|
data: _posts[idx],
|
||||||
constraints: const BoxConstraints(maxWidth: 640),
|
maxWidth: 640,
|
||||||
child: PostItem(
|
onChanged: (data) {
|
||||||
data: _posts[idx],
|
setState(() => _posts[idx] = data);
|
||||||
maxWidth: 640,
|
},
|
||||||
onChanged: (data) {
|
onDeleted: () {
|
||||||
setState(() => _posts[idx] = data);
|
_refreshPosts();
|
||||||
},
|
},
|
||||||
onDeleted: () {
|
|
||||||
_refreshPosts();
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
openBuilder: (_, close) => PostDetailScreen(
|
|
||||||
slug: _posts[idx].id.toString(),
|
|
||||||
preload: _posts[idx],
|
|
||||||
onBack: close,
|
|
||||||
),
|
|
||||||
openColor: Colors.transparent,
|
|
||||||
openElevation: 0,
|
|
||||||
transitionType: ContainerTransitionType.fade,
|
|
||||||
closedColor: Theme.of(context).colorScheme.surfaceContainerLow.withOpacity(
|
|
||||||
cfg.prefs.getBool(kAppBackgroundStoreKey) == true ? 0.75 : 1,
|
|
||||||
),
|
|
||||||
closedShape: const RoundedRectangleBorder(
|
|
||||||
borderRadius: BorderRadius.all(Radius.circular(16)),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
@ -183,7 +183,7 @@ class _PostDetailScreenState extends State<PostDetailScreen> {
|
|||||||
if (_data != null)
|
if (_data != null)
|
||||||
PostCommentSliverList(
|
PostCommentSliverList(
|
||||||
key: _childListKey,
|
key: _childListKey,
|
||||||
parentPostId: _data!.id,
|
parentPost: _data!,
|
||||||
maxWidth: 640,
|
maxWidth: 640,
|
||||||
),
|
),
|
||||||
SliverGap(math.max(MediaQuery.of(context).padding.bottom, 16)),
|
SliverGap(math.max(MediaQuery.of(context).padding.bottom, 16)),
|
||||||
|
@ -1,32 +1,34 @@
|
|||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
import 'package:dropdown_button2/dropdown_button2.dart';
|
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/gestures.dart';
|
import 'package:flutter/gestures.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:gap/gap.dart';
|
import 'package:gap/gap.dart';
|
||||||
import 'package:go_router/go_router.dart';
|
|
||||||
import 'package:hotkey_manager/hotkey_manager.dart';
|
import 'package:hotkey_manager/hotkey_manager.dart';
|
||||||
import 'package:material_symbols_icons/symbols.dart';
|
import 'package:material_symbols_icons/symbols.dart';
|
||||||
import 'package:pasteboard/pasteboard.dart';
|
import 'package:pasteboard/pasteboard.dart';
|
||||||
|
import 'package:responsive_framework/responsive_framework.dart';
|
||||||
import 'package:styled_widget/styled_widget.dart';
|
import 'package:styled_widget/styled_widget.dart';
|
||||||
import 'package:surface/controllers/post_write_controller.dart';
|
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_network.dart';
|
import 'package:surface/providers/sn_network.dart';
|
||||||
|
import 'package:surface/types/attachment.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/attachment/attachment_item.dart';
|
||||||
import 'package:surface/widgets/loading_indicator.dart';
|
import 'package:surface/widgets/loading_indicator.dart';
|
||||||
|
import 'package:surface/widgets/markdown_content.dart';
|
||||||
import 'package:surface/widgets/navigation/app_scaffold.dart';
|
import 'package:surface/widgets/navigation/app_scaffold.dart';
|
||||||
import 'package:surface/widgets/post/post_item.dart';
|
|
||||||
import 'package:surface/widgets/post/post_media_pending_list.dart';
|
import 'package:surface/widgets/post/post_media_pending_list.dart';
|
||||||
import 'package:surface/widgets/post/post_meta_editor.dart';
|
import 'package:surface/widgets/post/post_meta_editor.dart';
|
||||||
import 'package:surface/widgets/dialog.dart';
|
import 'package:surface/widgets/dialog.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
import 'package:uuid/uuid.dart';
|
||||||
|
|
||||||
import '../../types/attachment.dart';
|
import '../../widgets/attachment/attachment_input.dart';
|
||||||
|
|
||||||
class PostEditorExtra {
|
class PostEditorExtra {
|
||||||
final String? text;
|
final String? text;
|
||||||
@ -124,6 +126,16 @@ class _PostEditorScreenState extends State<PostEditorScreen> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _showPublisherPopup() {
|
||||||
|
showModalBottomSheet(
|
||||||
|
context: context,
|
||||||
|
builder: (context) => _PostPublisherPopup(
|
||||||
|
controller: _writeController,
|
||||||
|
publishers: _publishers,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
_writeController.dispose();
|
_writeController.dispose();
|
||||||
@ -197,174 +209,50 @@ class _PostEditorScreenState extends State<PostEditorScreen> {
|
|||||||
),
|
),
|
||||||
body: Column(
|
body: Column(
|
||||||
children: [
|
children: [
|
||||||
DropdownButtonHideUnderline(
|
if (_writeController.editingPost != null)
|
||||||
child: DropdownButton2<SnPublisher>(
|
Container(
|
||||||
isExpanded: true,
|
padding: const EdgeInsets.only(top: 4, bottom: 4, left: 20, right: 20),
|
||||||
hint: Text(
|
decoration: BoxDecoration(
|
||||||
'fieldPostPublisher',
|
border: Border(
|
||||||
style: TextStyle(
|
bottom: BorderSide(
|
||||||
fontSize: 14,
|
color: Theme.of(context).dividerColor,
|
||||||
color: Theme.of(context).hintColor,
|
width: 1 / MediaQuery.of(context).devicePixelRatio,
|
||||||
),
|
|
||||||
).tr(),
|
|
||||||
items: <DropdownMenuItem<SnPublisher>>[
|
|
||||||
...(_publishers?.map(
|
|
||||||
(item) => DropdownMenuItem<SnPublisher>(
|
|
||||||
enabled: _writeController.editingPost == null,
|
|
||||||
value: item,
|
|
||||||
child: Row(
|
|
||||||
children: [
|
|
||||||
AccountImage(content: item.avatar, radius: 16),
|
|
||||||
const Gap(8),
|
|
||||||
Expanded(
|
|
||||||
child: Column(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
Text(item.nick).textStyle(Theme.of(context).textTheme.bodyMedium!),
|
|
||||||
Text('@${item.name}')
|
|
||||||
.textStyle(Theme.of(context).textTheme.bodySmall!)
|
|
||||||
.fontSize(12),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
) ??
|
|
||||||
[]),
|
|
||||||
DropdownMenuItem<SnPublisher>(
|
|
||||||
value: null,
|
|
||||||
child: Row(
|
|
||||||
children: [
|
|
||||||
CircleAvatar(
|
|
||||||
radius: 16,
|
|
||||||
backgroundColor: Colors.transparent,
|
|
||||||
foregroundColor: Theme.of(context).colorScheme.onSurface,
|
|
||||||
child: const Icon(Symbols.add),
|
|
||||||
),
|
|
||||||
const Gap(8),
|
|
||||||
Expanded(
|
|
||||||
child: Column(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
Text('publishersNew').tr().textStyle(Theme.of(context).textTheme.bodyMedium!),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
|
||||||
value: _writeController.publisher,
|
|
||||||
onChanged: (SnPublisher? value) {
|
|
||||||
if (value == null) {
|
|
||||||
GoRouter.of(context).pushNamed('accountPublisherNew').then((value) {
|
|
||||||
if (value == true) {
|
|
||||||
_publishers = null;
|
|
||||||
_fetchPublishers();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
_writeController.setPublisher(value);
|
|
||||||
final config = context.read<ConfigProvider>();
|
|
||||||
config.prefs.setInt('int_last_publisher_id', value.id);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
buttonStyleData: const ButtonStyleData(
|
|
||||||
padding: EdgeInsets.only(right: 16),
|
|
||||||
height: 48,
|
|
||||||
),
|
),
|
||||||
menuItemStyleData: const MenuItemStyleData(
|
child: Row(
|
||||||
height: 48,
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
const Icon(Icons.edit, size: 16),
|
||||||
|
const Gap(10),
|
||||||
|
Text('postEditingNotice').tr(args: ['@${_writeController.editingPost!.publisher.name}']),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
|
||||||
const Divider(height: 1),
|
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Stack(
|
child: Stack(
|
||||||
children: [
|
children: [
|
||||||
SingleChildScrollView(
|
SingleChildScrollView(
|
||||||
padding: EdgeInsets.only(bottom: 160),
|
padding: EdgeInsets.only(bottom: 160),
|
||||||
child: Column(
|
child: switch (_writeController.mode) {
|
||||||
children: [
|
'stories' => _PostStoryEditor(
|
||||||
// Replying Notice
|
controller: _writeController,
|
||||||
if (_writeController.replyingPost != null)
|
onTapPublisher: _showPublisherPopup,
|
||||||
Column(
|
|
||||||
children: [
|
|
||||||
ExpansionTile(
|
|
||||||
minTileHeight: 48,
|
|
||||||
leading: const Icon(Symbols.reply).padding(left: 4),
|
|
||||||
title: Text('postReplyingNotice')
|
|
||||||
.fontSize(15)
|
|
||||||
.tr(args: ['@${_writeController.replyingPost!.publisher.name}']),
|
|
||||||
children: <Widget>[PostItem(data: _writeController.replyingPost!)],
|
|
||||||
),
|
|
||||||
const Divider(height: 1),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
// Reposting Notice
|
|
||||||
if (_writeController.repostingPost != null)
|
|
||||||
Column(
|
|
||||||
children: [
|
|
||||||
ExpansionTile(
|
|
||||||
minTileHeight: 48,
|
|
||||||
leading: const Icon(Symbols.forward).padding(left: 4),
|
|
||||||
title: Text('postRepostingNotice')
|
|
||||||
.fontSize(15)
|
|
||||||
.tr(args: ['@${_writeController.repostingPost!.publisher.name}']),
|
|
||||||
children: <Widget>[
|
|
||||||
PostItem(
|
|
||||||
data: _writeController.repostingPost!,
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
const Divider(height: 1),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
// Editing Notice
|
|
||||||
if (_writeController.editingPost != null)
|
|
||||||
Column(
|
|
||||||
children: [
|
|
||||||
ExpansionTile(
|
|
||||||
minTileHeight: 48,
|
|
||||||
leading: const Icon(Symbols.edit_note).padding(left: 4),
|
|
||||||
title: Text('postEditingNotice')
|
|
||||||
.fontSize(15)
|
|
||||||
.tr(args: ['@${_writeController.editingPost!.publisher.name}']),
|
|
||||||
children: <Widget>[PostItem(data: _writeController.editingPost!)],
|
|
||||||
),
|
|
||||||
const Divider(height: 1),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
// Content Input Area
|
|
||||||
Container(
|
|
||||||
constraints: const BoxConstraints(maxWidth: 640),
|
|
||||||
child: TextField(
|
|
||||||
controller: _writeController.contentController,
|
|
||||||
maxLines: null,
|
|
||||||
decoration: InputDecoration(
|
|
||||||
hintText: 'fieldPostContent'.tr(),
|
|
||||||
hintStyle: TextStyle(fontSize: 14),
|
|
||||||
isCollapsed: true,
|
|
||||||
contentPadding: const EdgeInsets.symmetric(
|
|
||||||
horizontal: 16,
|
|
||||||
),
|
|
||||||
border: InputBorder.none,
|
|
||||||
),
|
|
||||||
onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
]
|
'articles' => _PostArticleEditor(
|
||||||
.expandIndexed(
|
controller: _writeController,
|
||||||
(idx, ele) => [
|
onTapPublisher: _showPublisherPopup,
|
||||||
if (idx != 0 || _writeController.isRelatedNull) const Gap(8),
|
),
|
||||||
ele,
|
'questions' => _PostQuestionEditor(
|
||||||
],
|
controller: _writeController,
|
||||||
)
|
onTapPublisher: _showPublisherPopup,
|
||||||
.toList(),
|
),
|
||||||
),
|
'videos' => _PostVideoEditor(
|
||||||
|
controller: _writeController,
|
||||||
|
onTapPublisher: _showPublisherPopup,
|
||||||
|
),
|
||||||
|
_ => const Placeholder(),
|
||||||
|
},
|
||||||
),
|
),
|
||||||
if (_writeController.attachments.isNotEmpty || _writeController.thumbnail != null)
|
if (_writeController.attachments.isNotEmpty || _writeController.thumbnail != null)
|
||||||
Positioned(
|
Positioned(
|
||||||
@ -508,3 +396,414 @@ class _PostEditorActionScrollBehavior extends MaterialScrollBehavior {
|
|||||||
PointerDeviceKind.mouse,
|
PointerDeviceKind.mouse,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class _PostPublisherPopup extends StatelessWidget {
|
||||||
|
final PostWriteController controller;
|
||||||
|
final List<SnPublisher>? publishers;
|
||||||
|
|
||||||
|
const _PostPublisherPopup({required this.controller, this.publishers});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
const Icon(Symbols.face, size: 24),
|
||||||
|
const Gap(16),
|
||||||
|
Text('accountPublishers', style: Theme.of(context).textTheme.titleLarge).tr(),
|
||||||
|
],
|
||||||
|
).padding(horizontal: 20, top: 16, bottom: 12),
|
||||||
|
Expanded(
|
||||||
|
child: ListView.builder(
|
||||||
|
itemCount: publishers?.length ?? 0,
|
||||||
|
itemBuilder: (context, idx) {
|
||||||
|
final publisher = publishers![idx];
|
||||||
|
return ListTile(
|
||||||
|
title: Text(publisher.nick),
|
||||||
|
subtitle: Text('@${publisher.name}'),
|
||||||
|
leading: AccountImage(content: publisher.avatar, radius: 18),
|
||||||
|
onTap: () {
|
||||||
|
controller.setPublisher(publisher);
|
||||||
|
Navigator.pop(context, true);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _PostStoryEditor extends StatelessWidget {
|
||||||
|
final PostWriteController controller;
|
||||||
|
final Function? onTapPublisher;
|
||||||
|
|
||||||
|
const _PostStoryEditor({required this.controller, this.onTapPublisher});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Container(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 12),
|
||||||
|
constraints: const BoxConstraints(maxWidth: 640),
|
||||||
|
child: Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Material(
|
||||||
|
elevation: 2,
|
||||||
|
borderRadius: const BorderRadius.all(Radius.circular(24)),
|
||||||
|
child: GestureDetector(
|
||||||
|
onTap: () {
|
||||||
|
onTapPublisher?.call();
|
||||||
|
},
|
||||||
|
child: AccountImage(
|
||||||
|
content: controller.publisher?.avatar,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
const Gap(6),
|
||||||
|
TextField(
|
||||||
|
controller: controller.titleController,
|
||||||
|
decoration: InputDecoration.collapsed(
|
||||||
|
hintText: 'fieldPostTitle'.tr(),
|
||||||
|
border: InputBorder.none,
|
||||||
|
),
|
||||||
|
style: Theme.of(context).textTheme.titleLarge,
|
||||||
|
onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(),
|
||||||
|
).padding(horizontal: 16),
|
||||||
|
const Gap(8),
|
||||||
|
TextField(
|
||||||
|
controller: controller.contentController,
|
||||||
|
maxLines: null,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
hintText: 'fieldPostContent'.tr(),
|
||||||
|
hintStyle: TextStyle(fontSize: 14),
|
||||||
|
isCollapsed: true,
|
||||||
|
contentPadding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 16,
|
||||||
|
),
|
||||||
|
border: InputBorder.none,
|
||||||
|
),
|
||||||
|
onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
).padding(bottom: 8),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _PostArticleEditor extends StatelessWidget {
|
||||||
|
final PostWriteController controller;
|
||||||
|
final Function? onTapPublisher;
|
||||||
|
|
||||||
|
const _PostArticleEditor({required this.controller, this.onTapPublisher});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final editorWidgets = <Widget>[
|
||||||
|
Material(
|
||||||
|
color: Theme.of(context).colorScheme.surfaceContainerHigh,
|
||||||
|
child: InkWell(
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
AccountImage(content: controller.publisher?.avatar, radius: 20),
|
||||||
|
const Gap(8),
|
||||||
|
Expanded(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(controller.publisher?.nick ?? 'loading'.tr()).bold(),
|
||||||
|
Text('@${controller.publisher?.name}'),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
).padding(horizontal: 12, vertical: 8),
|
||||||
|
onTap: () {
|
||||||
|
onTapPublisher?.call();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const Gap(16),
|
||||||
|
TextField(
|
||||||
|
controller: controller.titleController,
|
||||||
|
decoration: InputDecoration.collapsed(
|
||||||
|
hintText: 'fieldPostTitle'.tr(),
|
||||||
|
border: InputBorder.none,
|
||||||
|
),
|
||||||
|
style: Theme.of(context).textTheme.titleLarge,
|
||||||
|
onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(),
|
||||||
|
).padding(horizontal: 16),
|
||||||
|
const Gap(8),
|
||||||
|
TextField(
|
||||||
|
controller: controller.descriptionController,
|
||||||
|
decoration: InputDecoration.collapsed(
|
||||||
|
hintText: 'fieldPostDescription'.tr(),
|
||||||
|
border: InputBorder.none,
|
||||||
|
),
|
||||||
|
maxLines: null,
|
||||||
|
keyboardType: TextInputType.multiline,
|
||||||
|
style: Theme.of(context).textTheme.bodyLarge,
|
||||||
|
onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(),
|
||||||
|
).padding(horizontal: 16),
|
||||||
|
const Gap(4),
|
||||||
|
];
|
||||||
|
|
||||||
|
if (ResponsiveBreakpoints.of(context).largerThan(MOBILE)) {
|
||||||
|
return Container(
|
||||||
|
constraints: const BoxConstraints(maxWidth: 640 * 2 + 8),
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
...editorWidgets,
|
||||||
|
Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: TextField(
|
||||||
|
controller: controller.contentController,
|
||||||
|
maxLines: null,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
hintText: 'fieldPostContent'.tr(),
|
||||||
|
hintStyle: TextStyle(fontSize: 14),
|
||||||
|
isCollapsed: true,
|
||||||
|
contentPadding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 16,
|
||||||
|
),
|
||||||
|
border: InputBorder.none,
|
||||||
|
),
|
||||||
|
onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const Gap(8),
|
||||||
|
Expanded(
|
||||||
|
child: MarkdownTextContent(
|
||||||
|
content: controller.contentController.text,
|
||||||
|
).padding(horizontal: 24),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Column(
|
||||||
|
children: [
|
||||||
|
...editorWidgets,
|
||||||
|
Container(
|
||||||
|
padding: const EdgeInsets.only(top: 8),
|
||||||
|
constraints: const BoxConstraints(maxWidth: 640),
|
||||||
|
child: TextField(
|
||||||
|
controller: controller.contentController,
|
||||||
|
maxLines: null,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
hintText: 'fieldPostContent'.tr(),
|
||||||
|
hintStyle: TextStyle(fontSize: 14),
|
||||||
|
isCollapsed: true,
|
||||||
|
contentPadding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 16,
|
||||||
|
),
|
||||||
|
border: InputBorder.none,
|
||||||
|
),
|
||||||
|
onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _PostQuestionEditor extends StatelessWidget {
|
||||||
|
final PostWriteController controller;
|
||||||
|
final Function? onTapPublisher;
|
||||||
|
|
||||||
|
const _PostQuestionEditor({required this.controller, this.onTapPublisher});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Container(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 12),
|
||||||
|
constraints: const BoxConstraints(maxWidth: 640),
|
||||||
|
child: Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Material(
|
||||||
|
elevation: 1,
|
||||||
|
borderRadius: const BorderRadius.all(Radius.circular(24)),
|
||||||
|
child: GestureDetector(
|
||||||
|
onTap: () {
|
||||||
|
onTapPublisher?.call();
|
||||||
|
},
|
||||||
|
child: AccountImage(
|
||||||
|
content: controller.publisher?.avatar,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
const Gap(6),
|
||||||
|
TextField(
|
||||||
|
controller: controller.titleController,
|
||||||
|
decoration: InputDecoration.collapsed(
|
||||||
|
hintText: 'fieldPostTitle'.tr(),
|
||||||
|
border: InputBorder.none,
|
||||||
|
),
|
||||||
|
style: Theme.of(context).textTheme.titleLarge,
|
||||||
|
onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(),
|
||||||
|
).padding(horizontal: 16),
|
||||||
|
const Gap(8),
|
||||||
|
TextField(
|
||||||
|
controller: controller.rewardController,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
hintText: 'fieldPostQuestionReward'.tr(),
|
||||||
|
suffixText: 'walletCurrencyShort'.tr(),
|
||||||
|
border: InputBorder.none,
|
||||||
|
isCollapsed: true,
|
||||||
|
),
|
||||||
|
onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(),
|
||||||
|
).padding(horizontal: 16),
|
||||||
|
const Gap(8),
|
||||||
|
TextField(
|
||||||
|
controller: controller.contentController,
|
||||||
|
maxLines: null,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
hintText: 'fieldPostContent'.tr(),
|
||||||
|
hintStyle: TextStyle(fontSize: 14),
|
||||||
|
isCollapsed: true,
|
||||||
|
contentPadding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 16,
|
||||||
|
),
|
||||||
|
border: InputBorder.none,
|
||||||
|
),
|
||||||
|
onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
).padding(top: 8),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _PostVideoEditor extends StatelessWidget {
|
||||||
|
final PostWriteController controller;
|
||||||
|
final Function? onTapPublisher;
|
||||||
|
|
||||||
|
const _PostVideoEditor({required this.controller, this.onTapPublisher});
|
||||||
|
|
||||||
|
void _selectVideo(BuildContext context) async {
|
||||||
|
final video = await showDialog<SnAttachment?>(
|
||||||
|
context: context,
|
||||||
|
builder: (context) => AttachmentInputDialog(
|
||||||
|
title: 'postVideoUpload'.tr(),
|
||||||
|
pool: 'interactive',
|
||||||
|
mediaType: SnMediaType.video,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
if (!context.mounted) return;
|
||||||
|
if (video == null) return;
|
||||||
|
controller.setVideoAttachment(video);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Column(
|
||||||
|
children: [
|
||||||
|
Material(
|
||||||
|
color: Theme.of(context).colorScheme.surfaceContainerHigh,
|
||||||
|
child: InkWell(
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
AccountImage(content: controller.publisher?.avatar, radius: 20),
|
||||||
|
const Gap(8),
|
||||||
|
Expanded(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(controller.publisher?.nick ?? 'loading'.tr()).bold(),
|
||||||
|
Text('@${controller.publisher?.name}'),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
).padding(horizontal: 12, vertical: 8),
|
||||||
|
onTap: () {
|
||||||
|
onTapPublisher?.call();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const Gap(16),
|
||||||
|
TextField(
|
||||||
|
controller: controller.titleController,
|
||||||
|
decoration: InputDecoration.collapsed(
|
||||||
|
hintText: 'fieldPostTitle'.tr(),
|
||||||
|
border: InputBorder.none,
|
||||||
|
),
|
||||||
|
style: Theme.of(context).textTheme.titleLarge,
|
||||||
|
onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(),
|
||||||
|
).padding(horizontal: 16),
|
||||||
|
const Gap(8),
|
||||||
|
TextField(
|
||||||
|
controller: controller.descriptionController,
|
||||||
|
decoration: InputDecoration.collapsed(
|
||||||
|
hintText: 'fieldPostDescription'.tr(),
|
||||||
|
border: InputBorder.none,
|
||||||
|
),
|
||||||
|
maxLines: null,
|
||||||
|
keyboardType: TextInputType.multiline,
|
||||||
|
style: Theme.of(context).textTheme.bodyLarge,
|
||||||
|
onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(),
|
||||||
|
).padding(horizontal: 16),
|
||||||
|
const Gap(12),
|
||||||
|
Container(
|
||||||
|
margin: const EdgeInsets.only(left: 16, right: 16),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
borderRadius: BorderRadius.circular(16),
|
||||||
|
border: Border.all(color: Theme.of(context).dividerColor),
|
||||||
|
),
|
||||||
|
child: InkWell(
|
||||||
|
borderRadius: BorderRadius.circular(16),
|
||||||
|
child: AspectRatio(
|
||||||
|
aspectRatio: 16 / 9,
|
||||||
|
child: controller.videoAttachment == null
|
||||||
|
? Center(
|
||||||
|
child: Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
const Icon(Icons.add),
|
||||||
|
const Gap(4),
|
||||||
|
Text('postVideoUpload'.tr()),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: ClipRRect(
|
||||||
|
borderRadius: BorderRadius.circular(16),
|
||||||
|
child: AttachmentItem(
|
||||||
|
data: controller.videoAttachment!,
|
||||||
|
heroTag: const Uuid().v4(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
onTap: () {
|
||||||
|
if (controller.videoAttachment != null) return;
|
||||||
|
_selectVideo(context);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:gap/gap.dart';
|
import 'package:gap/gap.dart';
|
||||||
import 'package:go_router/go_router.dart';
|
|
||||||
import 'package:material_symbols_icons/symbols.dart';
|
import 'package:material_symbols_icons/symbols.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'package:styled_widget/styled_widget.dart';
|
import 'package:styled_widget/styled_widget.dart';
|
||||||
@ -134,7 +133,7 @@ class _PostSearchScreenState extends State<PostSearchScreen> {
|
|||||||
body: Stack(
|
body: Stack(
|
||||||
children: [
|
children: [
|
||||||
InfiniteList(
|
InfiniteList(
|
||||||
padding: const EdgeInsets.only(top: 100),
|
padding: const EdgeInsets.only(top: 100 + 8),
|
||||||
itemCount: _posts.length,
|
itemCount: _posts.length,
|
||||||
isLoading: _isBusy,
|
isLoading: _isBusy,
|
||||||
hasReachedMax: _postCount != null && _posts.length >= _postCount!,
|
hasReachedMax: _postCount != null && _posts.length >= _postCount!,
|
||||||
@ -142,27 +141,18 @@ class _PostSearchScreenState extends State<PostSearchScreen> {
|
|||||||
_fetchPosts();
|
_fetchPosts();
|
||||||
},
|
},
|
||||||
itemBuilder: (context, idx) {
|
itemBuilder: (context, idx) {
|
||||||
return GestureDetector(
|
return OpenablePostItem(
|
||||||
child: PostItem(
|
data: _posts[idx],
|
||||||
data: _posts[idx],
|
maxWidth: 640,
|
||||||
maxWidth: 640,
|
onChanged: (data) {
|
||||||
onChanged: (data) {
|
setState(() => _posts[idx] = data);
|
||||||
setState(() => _posts[idx] = data);
|
},
|
||||||
},
|
onDeleted: () {
|
||||||
onDeleted: () {
|
_refreshPosts();
|
||||||
_refreshPosts();
|
|
||||||
},
|
|
||||||
),
|
|
||||||
onTap: () {
|
|
||||||
GoRouter.of(context).pushNamed(
|
|
||||||
'postDetail',
|
|
||||||
pathParameters: {'slug': _posts[idx].id.toString()},
|
|
||||||
extra: _posts[idx],
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
separatorBuilder: (context, index) => const Divider(height: 1),
|
separatorBuilder: (_, __) => const Gap(8),
|
||||||
),
|
),
|
||||||
Positioned(
|
Positioned(
|
||||||
top: 16,
|
top: 16,
|
||||||
|
@ -287,8 +287,8 @@ class _PostPublisherScreenState extends State<PostPublisherScreen> with SingleTi
|
|||||||
Theme(
|
Theme(
|
||||||
data: Theme.of(context).copyWith(
|
data: Theme.of(context).copyWith(
|
||||||
appBarTheme: Theme.of(context).appBarTheme.copyWith(
|
appBarTheme: Theme.of(context).appBarTheme.copyWith(
|
||||||
foregroundColor: Colors.white,
|
foregroundColor: Colors.white,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
child: SliverAppBar(
|
child: SliverAppBar(
|
||||||
expandedHeight: _appBarHeight,
|
expandedHeight: _appBarHeight,
|
||||||
@ -597,25 +597,16 @@ class _PublisherPostList extends StatelessWidget {
|
|||||||
hasReachedMax: postCount != null && posts.length >= postCount!,
|
hasReachedMax: postCount != null && posts.length >= postCount!,
|
||||||
onFetchData: fetchPosts,
|
onFetchData: fetchPosts,
|
||||||
itemBuilder: (context, idx) {
|
itemBuilder: (context, idx) {
|
||||||
return GestureDetector(
|
return OpenablePostItem(
|
||||||
child: PostItem(
|
data: posts[idx],
|
||||||
data: posts[idx],
|
maxWidth: 640,
|
||||||
maxWidth: 640,
|
onChanged: (data) {
|
||||||
onChanged: (data) {
|
onChanged(idx, data);
|
||||||
onChanged(idx, data);
|
|
||||||
},
|
|
||||||
onDeleted: onDeleted,
|
|
||||||
),
|
|
||||||
onTap: () {
|
|
||||||
GoRouter.of(context).pushNamed(
|
|
||||||
'postDetail',
|
|
||||||
pathParameters: {'slug': posts[idx].id.toString()},
|
|
||||||
extra: posts[idx],
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
|
onDeleted: onDeleted,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
separatorBuilder: (context, index) => const Divider(height: 1),
|
separatorBuilder: (_, __) => const Gap(8),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -89,6 +89,7 @@ class SnPostPreload with _$SnPostPreload {
|
|||||||
const factory SnPostPreload({
|
const factory SnPostPreload({
|
||||||
required SnAttachment? thumbnail,
|
required SnAttachment? thumbnail,
|
||||||
required List<SnAttachment?>? attachments,
|
required List<SnAttachment?>? attachments,
|
||||||
|
required SnAttachment? video,
|
||||||
}) = _SnPostPreload;
|
}) = _SnPostPreload;
|
||||||
|
|
||||||
factory SnPostPreload.fromJson(Map<String, Object?> json) =>
|
factory SnPostPreload.fromJson(Map<String, Object?> json) =>
|
||||||
|
@ -1567,6 +1567,7 @@ SnPostPreload _$SnPostPreloadFromJson(Map<String, dynamic> json) {
|
|||||||
mixin _$SnPostPreload {
|
mixin _$SnPostPreload {
|
||||||
SnAttachment? get thumbnail => throw _privateConstructorUsedError;
|
SnAttachment? get thumbnail => throw _privateConstructorUsedError;
|
||||||
List<SnAttachment?>? get attachments => throw _privateConstructorUsedError;
|
List<SnAttachment?>? get attachments => throw _privateConstructorUsedError;
|
||||||
|
SnAttachment? get video => throw _privateConstructorUsedError;
|
||||||
|
|
||||||
/// Serializes this SnPostPreload to a JSON map.
|
/// Serializes this SnPostPreload to a JSON map.
|
||||||
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
|
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
|
||||||
@ -1584,9 +1585,13 @@ abstract class $SnPostPreloadCopyWith<$Res> {
|
|||||||
SnPostPreload value, $Res Function(SnPostPreload) then) =
|
SnPostPreload value, $Res Function(SnPostPreload) then) =
|
||||||
_$SnPostPreloadCopyWithImpl<$Res, SnPostPreload>;
|
_$SnPostPreloadCopyWithImpl<$Res, SnPostPreload>;
|
||||||
@useResult
|
@useResult
|
||||||
$Res call({SnAttachment? thumbnail, List<SnAttachment?>? attachments});
|
$Res call(
|
||||||
|
{SnAttachment? thumbnail,
|
||||||
|
List<SnAttachment?>? attachments,
|
||||||
|
SnAttachment? video});
|
||||||
|
|
||||||
$SnAttachmentCopyWith<$Res>? get thumbnail;
|
$SnAttachmentCopyWith<$Res>? get thumbnail;
|
||||||
|
$SnAttachmentCopyWith<$Res>? get video;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @nodoc
|
/// @nodoc
|
||||||
@ -1606,6 +1611,7 @@ class _$SnPostPreloadCopyWithImpl<$Res, $Val extends SnPostPreload>
|
|||||||
$Res call({
|
$Res call({
|
||||||
Object? thumbnail = freezed,
|
Object? thumbnail = freezed,
|
||||||
Object? attachments = freezed,
|
Object? attachments = freezed,
|
||||||
|
Object? video = freezed,
|
||||||
}) {
|
}) {
|
||||||
return _then(_value.copyWith(
|
return _then(_value.copyWith(
|
||||||
thumbnail: freezed == thumbnail
|
thumbnail: freezed == thumbnail
|
||||||
@ -1616,6 +1622,10 @@ class _$SnPostPreloadCopyWithImpl<$Res, $Val extends SnPostPreload>
|
|||||||
? _value.attachments
|
? _value.attachments
|
||||||
: attachments // ignore: cast_nullable_to_non_nullable
|
: attachments // ignore: cast_nullable_to_non_nullable
|
||||||
as List<SnAttachment?>?,
|
as List<SnAttachment?>?,
|
||||||
|
video: freezed == video
|
||||||
|
? _value.video
|
||||||
|
: video // ignore: cast_nullable_to_non_nullable
|
||||||
|
as SnAttachment?,
|
||||||
) as $Val);
|
) as $Val);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1632,6 +1642,20 @@ class _$SnPostPreloadCopyWithImpl<$Res, $Val extends SnPostPreload>
|
|||||||
return _then(_value.copyWith(thumbnail: value) as $Val);
|
return _then(_value.copyWith(thumbnail: value) as $Val);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Create a copy of SnPostPreload
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@override
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
$SnAttachmentCopyWith<$Res>? get video {
|
||||||
|
if (_value.video == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $SnAttachmentCopyWith<$Res>(_value.video!, (value) {
|
||||||
|
return _then(_value.copyWith(video: value) as $Val);
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @nodoc
|
/// @nodoc
|
||||||
@ -1642,10 +1666,15 @@ abstract class _$$SnPostPreloadImplCopyWith<$Res>
|
|||||||
__$$SnPostPreloadImplCopyWithImpl<$Res>;
|
__$$SnPostPreloadImplCopyWithImpl<$Res>;
|
||||||
@override
|
@override
|
||||||
@useResult
|
@useResult
|
||||||
$Res call({SnAttachment? thumbnail, List<SnAttachment?>? attachments});
|
$Res call(
|
||||||
|
{SnAttachment? thumbnail,
|
||||||
|
List<SnAttachment?>? attachments,
|
||||||
|
SnAttachment? video});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
$SnAttachmentCopyWith<$Res>? get thumbnail;
|
$SnAttachmentCopyWith<$Res>? get thumbnail;
|
||||||
|
@override
|
||||||
|
$SnAttachmentCopyWith<$Res>? get video;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @nodoc
|
/// @nodoc
|
||||||
@ -1663,6 +1692,7 @@ class __$$SnPostPreloadImplCopyWithImpl<$Res>
|
|||||||
$Res call({
|
$Res call({
|
||||||
Object? thumbnail = freezed,
|
Object? thumbnail = freezed,
|
||||||
Object? attachments = freezed,
|
Object? attachments = freezed,
|
||||||
|
Object? video = freezed,
|
||||||
}) {
|
}) {
|
||||||
return _then(_$SnPostPreloadImpl(
|
return _then(_$SnPostPreloadImpl(
|
||||||
thumbnail: freezed == thumbnail
|
thumbnail: freezed == thumbnail
|
||||||
@ -1673,6 +1703,10 @@ class __$$SnPostPreloadImplCopyWithImpl<$Res>
|
|||||||
? _value._attachments
|
? _value._attachments
|
||||||
: attachments // ignore: cast_nullable_to_non_nullable
|
: attachments // ignore: cast_nullable_to_non_nullable
|
||||||
as List<SnAttachment?>?,
|
as List<SnAttachment?>?,
|
||||||
|
video: freezed == video
|
||||||
|
? _value.video
|
||||||
|
: video // ignore: cast_nullable_to_non_nullable
|
||||||
|
as SnAttachment?,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1682,7 +1716,8 @@ class __$$SnPostPreloadImplCopyWithImpl<$Res>
|
|||||||
class _$SnPostPreloadImpl implements _SnPostPreload {
|
class _$SnPostPreloadImpl implements _SnPostPreload {
|
||||||
const _$SnPostPreloadImpl(
|
const _$SnPostPreloadImpl(
|
||||||
{required this.thumbnail,
|
{required this.thumbnail,
|
||||||
required final List<SnAttachment?>? attachments})
|
required final List<SnAttachment?>? attachments,
|
||||||
|
required this.video})
|
||||||
: _attachments = attachments;
|
: _attachments = attachments;
|
||||||
|
|
||||||
factory _$SnPostPreloadImpl.fromJson(Map<String, dynamic> json) =>
|
factory _$SnPostPreloadImpl.fromJson(Map<String, dynamic> json) =>
|
||||||
@ -1700,9 +1735,12 @@ class _$SnPostPreloadImpl implements _SnPostPreload {
|
|||||||
return EqualUnmodifiableListView(value);
|
return EqualUnmodifiableListView(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
final SnAttachment? video;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
return 'SnPostPreload(thumbnail: $thumbnail, attachments: $attachments)';
|
return 'SnPostPreload(thumbnail: $thumbnail, attachments: $attachments, video: $video)';
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -1713,13 +1751,14 @@ class _$SnPostPreloadImpl implements _SnPostPreload {
|
|||||||
(identical(other.thumbnail, thumbnail) ||
|
(identical(other.thumbnail, thumbnail) ||
|
||||||
other.thumbnail == thumbnail) &&
|
other.thumbnail == thumbnail) &&
|
||||||
const DeepCollectionEquality()
|
const DeepCollectionEquality()
|
||||||
.equals(other._attachments, _attachments));
|
.equals(other._attachments, _attachments) &&
|
||||||
|
(identical(other.video, video) || other.video == video));
|
||||||
}
|
}
|
||||||
|
|
||||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
@override
|
@override
|
||||||
int get hashCode => Object.hash(runtimeType, thumbnail,
|
int get hashCode => Object.hash(runtimeType, thumbnail,
|
||||||
const DeepCollectionEquality().hash(_attachments));
|
const DeepCollectionEquality().hash(_attachments), video);
|
||||||
|
|
||||||
/// Create a copy of SnPostPreload
|
/// Create a copy of SnPostPreload
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
@ -1740,7 +1779,8 @@ class _$SnPostPreloadImpl implements _SnPostPreload {
|
|||||||
abstract class _SnPostPreload implements SnPostPreload {
|
abstract class _SnPostPreload implements SnPostPreload {
|
||||||
const factory _SnPostPreload(
|
const factory _SnPostPreload(
|
||||||
{required final SnAttachment? thumbnail,
|
{required final SnAttachment? thumbnail,
|
||||||
required final List<SnAttachment?>? attachments}) = _$SnPostPreloadImpl;
|
required final List<SnAttachment?>? attachments,
|
||||||
|
required final SnAttachment? video}) = _$SnPostPreloadImpl;
|
||||||
|
|
||||||
factory _SnPostPreload.fromJson(Map<String, dynamic> json) =
|
factory _SnPostPreload.fromJson(Map<String, dynamic> json) =
|
||||||
_$SnPostPreloadImpl.fromJson;
|
_$SnPostPreloadImpl.fromJson;
|
||||||
@ -1749,6 +1789,8 @@ abstract class _SnPostPreload implements SnPostPreload {
|
|||||||
SnAttachment? get thumbnail;
|
SnAttachment? get thumbnail;
|
||||||
@override
|
@override
|
||||||
List<SnAttachment?>? get attachments;
|
List<SnAttachment?>? get attachments;
|
||||||
|
@override
|
||||||
|
SnAttachment? get video;
|
||||||
|
|
||||||
/// Create a copy of SnPostPreload
|
/// Create a copy of SnPostPreload
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@ -165,12 +165,16 @@ _$SnPostPreloadImpl _$$SnPostPreloadImplFromJson(Map<String, dynamic> json) =>
|
|||||||
? null
|
? null
|
||||||
: SnAttachment.fromJson(e as Map<String, dynamic>))
|
: SnAttachment.fromJson(e as Map<String, dynamic>))
|
||||||
.toList(),
|
.toList(),
|
||||||
|
video: json['video'] == null
|
||||||
|
? null
|
||||||
|
: SnAttachment.fromJson(json['video'] as Map<String, dynamic>),
|
||||||
);
|
);
|
||||||
|
|
||||||
Map<String, dynamic> _$$SnPostPreloadImplToJson(_$SnPostPreloadImpl instance) =>
|
Map<String, dynamic> _$$SnPostPreloadImplToJson(_$SnPostPreloadImpl instance) =>
|
||||||
<String, dynamic>{
|
<String, dynamic>{
|
||||||
'thumbnail': instance.thumbnail?.toJson(),
|
'thumbnail': instance.thumbnail?.toJson(),
|
||||||
'attachments': instance.attachments?.map((e) => e?.toJson()).toList(),
|
'attachments': instance.attachments?.map((e) => e?.toJson()).toList(),
|
||||||
|
'video': instance.video?.toJson(),
|
||||||
};
|
};
|
||||||
|
|
||||||
_$SnBodyImpl _$$SnBodyImplFromJson(Map<String, dynamic> json) => _$SnBodyImpl(
|
_$SnBodyImpl _$$SnBodyImplFromJson(Map<String, dynamic> json) => _$SnBodyImpl(
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:gap/gap.dart';
|
||||||
|
import 'package:material_symbols_icons/symbols.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'package:styled_widget/styled_widget.dart';
|
import 'package:styled_widget/styled_widget.dart';
|
||||||
import 'package:surface/providers/sn_network.dart';
|
import 'package:surface/providers/sn_network.dart';
|
||||||
@ -96,10 +98,14 @@ class _AccountSelectState extends State<AccountSelect> {
|
|||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Row(
|
||||||
widget.title,
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
style: Theme.of(context).textTheme.headlineSmall,
|
children: [
|
||||||
).padding(left: 24, right: 24, top: 16, bottom: 16),
|
const Icon(Symbols.group, size: 24),
|
||||||
|
const Gap(16),
|
||||||
|
Text(widget.title, style: Theme.of(context).textTheme.titleLarge),
|
||||||
|
],
|
||||||
|
).padding(horizontal: 20, top: 16, bottom: 12),
|
||||||
Container(
|
Container(
|
||||||
color: Theme.of(context).colorScheme.secondaryContainer,
|
color: Theme.of(context).colorScheme.secondaryContainer,
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 16),
|
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 16),
|
||||||
|
@ -6,12 +6,22 @@ import 'package:material_symbols_icons/symbols.dart';
|
|||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'package:styled_widget/styled_widget.dart';
|
import 'package:styled_widget/styled_widget.dart';
|
||||||
import 'package:surface/providers/sn_attachment.dart';
|
import 'package:surface/providers/sn_attachment.dart';
|
||||||
|
import 'package:surface/types/attachment.dart';
|
||||||
import 'package:surface/widgets/dialog.dart';
|
import 'package:surface/widgets/dialog.dart';
|
||||||
|
|
||||||
class AttachmentInputDialog extends StatefulWidget {
|
class AttachmentInputDialog extends StatefulWidget {
|
||||||
final String? title;
|
final String? title;
|
||||||
final bool? analyzeNow;
|
final bool? analyzeNow;
|
||||||
const AttachmentInputDialog({super.key, required this.title, this.analyzeNow = false});
|
final SnMediaType? mediaType;
|
||||||
|
final String pool;
|
||||||
|
|
||||||
|
const AttachmentInputDialog({
|
||||||
|
super.key,
|
||||||
|
required this.title,
|
||||||
|
required this.pool,
|
||||||
|
this.analyzeNow = false,
|
||||||
|
this.mediaType = SnMediaType.image,
|
||||||
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<AttachmentInputDialog> createState() => _AttachmentInputDialogState();
|
State<AttachmentInputDialog> createState() => _AttachmentInputDialogState();
|
||||||
@ -20,13 +30,18 @@ final bool? analyzeNow;
|
|||||||
class _AttachmentInputDialogState extends State<AttachmentInputDialog> {
|
class _AttachmentInputDialogState extends State<AttachmentInputDialog> {
|
||||||
final _randomIdController = TextEditingController();
|
final _randomIdController = TextEditingController();
|
||||||
|
|
||||||
XFile? _thumbnailFile;
|
XFile? _file;
|
||||||
|
double? _progress;
|
||||||
|
|
||||||
void _pickImage() async {
|
void _pickMedia() async {
|
||||||
final picker = ImagePicker();
|
final picker = ImagePicker();
|
||||||
final result = await picker.pickImage(source: ImageSource.gallery);
|
final result = switch (widget.mediaType) {
|
||||||
|
SnMediaType.image => await picker.pickImage(source: ImageSource.gallery),
|
||||||
|
SnMediaType.video => await picker.pickVideo(source: ImageSource.gallery),
|
||||||
|
_ => await picker.pickMedia(),
|
||||||
|
};
|
||||||
if (result == null) return;
|
if (result == null) return;
|
||||||
setState(() => _thumbnailFile = result);
|
setState(() => _file = result);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool _isBusy = false;
|
bool _isBusy = false;
|
||||||
@ -46,15 +61,20 @@ class _AttachmentInputDialogState extends State<AttachmentInputDialog> {
|
|||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
context.showErrorDialog(err);
|
context.showErrorDialog(err);
|
||||||
}
|
}
|
||||||
} else if (_thumbnailFile != null) {
|
} else if (_file != null) {
|
||||||
try {
|
try {
|
||||||
final attachment = await attach.directUploadOne(
|
final place = await attach.chunkedUploadInitialize(await _file!.length(), _file!.name, widget.pool, null);
|
||||||
(await _thumbnailFile!.readAsBytes()).buffer.asUint8List(),
|
|
||||||
_thumbnailFile!.path,
|
final attachment = await attach.chunkedUploadParts(
|
||||||
'interactive',
|
_file!,
|
||||||
null,
|
place.$1,
|
||||||
|
place.$2,
|
||||||
analyzeNow: widget.analyzeNow ?? false,
|
analyzeNow: widget.analyzeNow ?? false,
|
||||||
|
onProgress: (value) {
|
||||||
|
setState(() => _progress = value);
|
||||||
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
Navigator.pop(context, attachment);
|
Navigator.pop(context, attachment);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@ -67,7 +87,7 @@ class _AttachmentInputDialogState extends State<AttachmentInputDialog> {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return AlertDialog(
|
return AlertDialog(
|
||||||
title: Text(widget.title ?? 'attachmentInputDialog').tr(),
|
title: Text(widget.title ?? 'attachmentInputDialog'.tr()),
|
||||||
content: Column(
|
content: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
@ -86,24 +106,35 @@ class _AttachmentInputDialogState extends State<AttachmentInputDialog> {
|
|||||||
const Gap(24),
|
const Gap(24),
|
||||||
Text('attachmentInputNew').tr().fontSize(14),
|
Text('attachmentInputNew').tr().fontSize(14),
|
||||||
Card(
|
Card(
|
||||||
child: ListTile(
|
child: Column(
|
||||||
contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
children: [
|
||||||
leading: const Icon(Symbols.add_photo_alternate),
|
ListTile(
|
||||||
trailing: const Icon(Symbols.chevron_right),
|
contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
||||||
title: Text('addAttachmentFromAlbum').tr(),
|
leading: const Icon(Symbols.add_photo_alternate),
|
||||||
subtitle: _thumbnailFile == null ? Text('unset').tr() : Text('waitingForUpload').tr(),
|
trailing: const Icon(Symbols.chevron_right),
|
||||||
onTap: () {
|
title: Text('addAttachmentFromAlbum').tr(),
|
||||||
_pickImage();
|
subtitle: _file == null ? Text('unset').tr() : Text('waitingForUpload').tr(),
|
||||||
},
|
onTap: () {
|
||||||
|
_pickMedia();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
if (_isBusy)
|
||||||
|
LinearProgressIndicator(
|
||||||
|
value: _progress,
|
||||||
|
borderRadius: BorderRadius.all(Radius.circular(8)),
|
||||||
|
).padding(top: 16),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
actions: [
|
actions: [
|
||||||
TextButton(
|
TextButton(
|
||||||
onPressed: _isBusy ? null : () {
|
onPressed: _isBusy
|
||||||
Navigator.pop(context);
|
? null
|
||||||
},
|
: () {
|
||||||
|
Navigator.pop(context);
|
||||||
|
},
|
||||||
child: Text('dialogDismiss').tr(),
|
child: Text('dialogDismiss').tr(),
|
||||||
),
|
),
|
||||||
TextButton(
|
TextButton(
|
||||||
|
@ -336,6 +336,7 @@ class ChatMessageInputState extends State<ChatMessageInput> {
|
|||||||
: 'fieldChatMessage'.tr(args: [widget.controller.channel?.name ?? 'loading'.tr()]),
|
: 'fieldChatMessage'.tr(args: [widget.controller.channel?.name ?? 'loading'.tr()]),
|
||||||
border: InputBorder.none,
|
border: InputBorder.none,
|
||||||
),
|
),
|
||||||
|
textInputAction: TextInputAction.send,
|
||||||
onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(),
|
onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(),
|
||||||
onSubmitted: (_) {
|
onSubmitted: (_) {
|
||||||
if (_isBusy) return;
|
if (_isBusy) return;
|
||||||
|
@ -8,17 +8,23 @@ import 'package:styled_widget/styled_widget.dart';
|
|||||||
import 'package:surface/providers/post.dart';
|
import 'package:surface/providers/post.dart';
|
||||||
import 'package:surface/providers/userinfo.dart';
|
import 'package:surface/providers/userinfo.dart';
|
||||||
import 'package:surface/types/post.dart';
|
import 'package:surface/types/post.dart';
|
||||||
|
import 'package:surface/widgets/dialog.dart';
|
||||||
import 'package:surface/widgets/post/post_item.dart';
|
import 'package:surface/widgets/post/post_item.dart';
|
||||||
import 'package:surface/widgets/post/post_mini_editor.dart';
|
import 'package:surface/widgets/post/post_mini_editor.dart';
|
||||||
import 'package:very_good_infinite_list/very_good_infinite_list.dart';
|
import 'package:very_good_infinite_list/very_good_infinite_list.dart';
|
||||||
|
|
||||||
|
import '../../providers/sn_network.dart';
|
||||||
|
|
||||||
class PostCommentSliverList extends StatefulWidget {
|
class PostCommentSliverList extends StatefulWidget {
|
||||||
final int parentPostId;
|
final SnPost parentPost;
|
||||||
final double? maxWidth;
|
final double? maxWidth;
|
||||||
|
final Function(SnPost)? onSelectAnswer;
|
||||||
|
|
||||||
const PostCommentSliverList({
|
const PostCommentSliverList({
|
||||||
super.key,
|
super.key,
|
||||||
required this.parentPostId,
|
required this.parentPost,
|
||||||
this.maxWidth,
|
this.maxWidth,
|
||||||
|
this.onSelectAnswer,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -37,7 +43,7 @@ class PostCommentSliverListState extends State<PostCommentSliverList> {
|
|||||||
setState(() => _isBusy = true);
|
setState(() => _isBusy = true);
|
||||||
|
|
||||||
final pt = context.read<SnPostContentProvider>();
|
final pt = context.read<SnPostContentProvider>();
|
||||||
final result = await pt.listPostReplies(widget.parentPostId);
|
final result = await pt.listPostReplies(widget.parentPost.id);
|
||||||
final List<SnPost> out = result.$1;
|
final List<SnPost> out = result.$1;
|
||||||
|
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
@ -48,6 +54,21 @@ class PostCommentSliverListState extends State<PostCommentSliverList> {
|
|||||||
if (mounted) setState(() => _isBusy = false);
|
if (mounted) setState(() => _isBusy = false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> _selectAnswer(SnPost answer) async {
|
||||||
|
try {
|
||||||
|
final sn = context.read<SnNetworkProvider>();
|
||||||
|
await sn.client.put('/cgi/co/questions/${widget.parentPost.id}/answer', data: {
|
||||||
|
'publisher': answer.publisherId,
|
||||||
|
'answer_id': answer.id,
|
||||||
|
});
|
||||||
|
if (!mounted) return;
|
||||||
|
await refresh();
|
||||||
|
} catch (err) {
|
||||||
|
if (!mounted) return;
|
||||||
|
context.showErrorDialog(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> refresh() async {
|
Future<void> refresh() async {
|
||||||
_posts.clear();
|
_posts.clear();
|
||||||
_fetchPosts();
|
_fetchPosts();
|
||||||
@ -71,6 +92,7 @@ class PostCommentSliverListState extends State<PostCommentSliverList> {
|
|||||||
child: PostItem(
|
child: PostItem(
|
||||||
data: _posts[idx],
|
data: _posts[idx],
|
||||||
maxWidth: widget.maxWidth,
|
maxWidth: widget.maxWidth,
|
||||||
|
onSelectAnswer: widget.parentPost.type == 'question' ? () => _selectAnswer(_posts[idx]) : null,
|
||||||
onChanged: (data) {
|
onChanged: (data) {
|
||||||
setState(() => _posts[idx] = data);
|
setState(() => _posts[idx] = data);
|
||||||
},
|
},
|
||||||
@ -94,11 +116,12 @@ class PostCommentSliverListState extends State<PostCommentSliverList> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class PostCommentListPopup extends StatefulWidget {
|
class PostCommentListPopup extends StatefulWidget {
|
||||||
final int postId;
|
final SnPost post;
|
||||||
final int commentCount;
|
final int commentCount;
|
||||||
|
|
||||||
const PostCommentListPopup({
|
const PostCommentListPopup({
|
||||||
super.key,
|
super.key,
|
||||||
required this.postId,
|
required this.post,
|
||||||
this.commentCount = 0,
|
this.commentCount = 0,
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -122,9 +145,7 @@ class _PostCommentListPopupState extends State<PostCommentListPopup> {
|
|||||||
children: [
|
children: [
|
||||||
const Icon(Symbols.comment, size: 24),
|
const Icon(Symbols.comment, size: 24),
|
||||||
const Gap(16),
|
const Gap(16),
|
||||||
Text('postCommentsDetailed')
|
Text('postCommentsDetailed').plural(widget.commentCount).textStyle(Theme.of(context).textTheme.titleLarge!),
|
||||||
.plural(widget.commentCount)
|
|
||||||
.textStyle(Theme.of(context).textTheme.titleLarge!),
|
|
||||||
],
|
],
|
||||||
).padding(horizontal: 20, top: 16, bottom: 12),
|
).padding(horizontal: 20, top: 16, bottom: 12),
|
||||||
Expanded(
|
Expanded(
|
||||||
@ -143,7 +164,7 @@ class _PostCommentListPopupState extends State<PostCommentListPopup> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
child: PostMiniEditor(
|
child: PostMiniEditor(
|
||||||
postReplyId: widget.postId,
|
postReplyId: widget.post.id,
|
||||||
onPost: () {
|
onPost: () {
|
||||||
_childListKey.currentState!.refresh();
|
_childListKey.currentState!.refresh();
|
||||||
},
|
},
|
||||||
@ -151,8 +172,8 @@ class _PostCommentListPopupState extends State<PostCommentListPopup> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
PostCommentSliverList(
|
PostCommentSliverList(
|
||||||
|
parentPost: widget.post,
|
||||||
key: _childListKey,
|
key: _childListKey,
|
||||||
parentPostId: widget.postId,
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
import 'dart:math' as math;
|
import 'dart:math' as math;
|
||||||
|
|
||||||
|
import 'package:animations/animations.dart';
|
||||||
import 'package:dio/dio.dart';
|
import 'package:dio/dio.dart';
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:file_saver/file_saver.dart';
|
import 'package:file_saver/file_saver.dart';
|
||||||
@ -22,10 +23,12 @@ import 'package:styled_widget/styled_widget.dart';
|
|||||||
import 'package:surface/providers/config.dart';
|
import 'package:surface/providers/config.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/screens/post/post_detail.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/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/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';
|
||||||
import 'package:surface/widgets/link_preview.dart';
|
import 'package:surface/widgets/link_preview.dart';
|
||||||
@ -38,6 +41,65 @@ 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';
|
||||||
|
|
||||||
|
class OpenablePostItem extends StatelessWidget {
|
||||||
|
final SnPost data;
|
||||||
|
final bool showReactions;
|
||||||
|
final bool showComments;
|
||||||
|
final bool showMenu;
|
||||||
|
final bool showFullPost;
|
||||||
|
final double? maxWidth;
|
||||||
|
final Function(SnPost data)? onChanged;
|
||||||
|
final Function()? onDeleted;
|
||||||
|
final Function()? onSelectAnswer;
|
||||||
|
|
||||||
|
const OpenablePostItem({
|
||||||
|
super.key,
|
||||||
|
required this.data,
|
||||||
|
this.showReactions = true,
|
||||||
|
this.showComments = true,
|
||||||
|
this.showMenu = true,
|
||||||
|
this.showFullPost = false,
|
||||||
|
this.maxWidth,
|
||||||
|
this.onChanged,
|
||||||
|
this.onDeleted,
|
||||||
|
this.onSelectAnswer,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final cfg = context.read<ConfigProvider>();
|
||||||
|
|
||||||
|
return OpenContainer(
|
||||||
|
closedBuilder: (_, __) => Container(
|
||||||
|
constraints: BoxConstraints(maxWidth: maxWidth ?? double.infinity),
|
||||||
|
child: PostItem(
|
||||||
|
data: data,
|
||||||
|
maxWidth: maxWidth,
|
||||||
|
showComments: showComments,
|
||||||
|
showFullPost: showFullPost,
|
||||||
|
onChanged: onChanged,
|
||||||
|
onDeleted: onDeleted,
|
||||||
|
onSelectAnswer: onSelectAnswer,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
openBuilder: (_, close) => PostDetailScreen(
|
||||||
|
slug: data.id.toString(),
|
||||||
|
preload: data,
|
||||||
|
onBack: close,
|
||||||
|
),
|
||||||
|
openColor: Colors.transparent,
|
||||||
|
openElevation: 0,
|
||||||
|
transitionType: ContainerTransitionType.fade,
|
||||||
|
closedColor: Theme.of(context).colorScheme.surfaceContainerLow.withOpacity(
|
||||||
|
cfg.prefs.getBool(kAppBackgroundStoreKey) == true ? 0.75 : 1,
|
||||||
|
),
|
||||||
|
closedShape: const RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.all(Radius.circular(16)),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class PostItem extends StatelessWidget {
|
class PostItem extends StatelessWidget {
|
||||||
final SnPost data;
|
final SnPost data;
|
||||||
final bool showReactions;
|
final bool showReactions;
|
||||||
@ -47,6 +109,7 @@ class PostItem extends StatelessWidget {
|
|||||||
final double? maxWidth;
|
final double? maxWidth;
|
||||||
final Function(SnPost data)? onChanged;
|
final Function(SnPost data)? onChanged;
|
||||||
final Function()? onDeleted;
|
final Function()? onDeleted;
|
||||||
|
final Function()? onSelectAnswer;
|
||||||
|
|
||||||
const PostItem({
|
const PostItem({
|
||||||
super.key,
|
super.key,
|
||||||
@ -58,6 +121,7 @@ class PostItem extends StatelessWidget {
|
|||||||
this.maxWidth,
|
this.maxWidth,
|
||||||
this.onChanged,
|
this.onChanged,
|
||||||
this.onDeleted,
|
this.onDeleted,
|
||||||
|
this.onSelectAnswer,
|
||||||
});
|
});
|
||||||
|
|
||||||
void _onChanged(SnPost data) {
|
void _onChanged(SnPost data) {
|
||||||
@ -142,10 +206,12 @@ class PostItem extends StatelessWidget {
|
|||||||
isRelativeDate: !showFullPost,
|
isRelativeDate: !showFullPost,
|
||||||
onShare: () => _doShare(context),
|
onShare: () => _doShare(context),
|
||||||
onShareImage: () => _doShareViaPicture(context),
|
onShareImage: () => _doShareViaPicture(context),
|
||||||
|
onSelectAnswer: onSelectAnswer,
|
||||||
onDeleted: () {
|
onDeleted: () {
|
||||||
if (onDeleted != null) {}
|
if (onDeleted != null) {}
|
||||||
},
|
},
|
||||||
).padding(horizontal: 12, top: 8, bottom: 8),
|
).padding(horizontal: 12, top: 8, bottom: 8),
|
||||||
|
if (data.preload?.video != null) _PostVideoPlayer(data: data).padding(horizontal: 12, bottom: 8),
|
||||||
Container(
|
Container(
|
||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
margin: const EdgeInsets.only(bottom: 4, left: 12, right: 12),
|
margin: const EdgeInsets.only(bottom: 4, left: 12, right: 12),
|
||||||
@ -224,10 +290,13 @@ class PostItem extends StatelessWidget {
|
|||||||
showMenu: showMenu,
|
showMenu: showMenu,
|
||||||
onShare: () => _doShare(context),
|
onShare: () => _doShare(context),
|
||||||
onShareImage: () => _doShareViaPicture(context),
|
onShareImage: () => _doShareViaPicture(context),
|
||||||
|
onSelectAnswer: onSelectAnswer,
|
||||||
onDeleted: () {
|
onDeleted: () {
|
||||||
if (onDeleted != null) onDeleted!();
|
if (onDeleted != null) onDeleted!();
|
||||||
},
|
},
|
||||||
).padding(horizontal: 12, vertical: 8),
|
).padding(horizontal: 12, vertical: 8),
|
||||||
|
if (data.preload?.video != null) _PostVideoPlayer(data: data).padding(horizontal: 12, bottom: 8),
|
||||||
|
if (data.type == 'question') _PostQuestionHint(data: data).padding(horizontal: 16, bottom: 8),
|
||||||
if (data.body['title'] != null || data.body['description'] != null)
|
if (data.body['title'] != null || data.body['description'] != null)
|
||||||
_PostHeadline(
|
_PostHeadline(
|
||||||
data: data,
|
data: data,
|
||||||
@ -333,6 +402,7 @@ class PostShareImageWidget extends StatelessWidget {
|
|||||||
showMenu: false,
|
showMenu: false,
|
||||||
isRelativeDate: false,
|
isRelativeDate: false,
|
||||||
).padding(horizontal: 16, bottom: 8),
|
).padding(horizontal: 16, bottom: 8),
|
||||||
|
if (data.type == 'question') _PostQuestionHint(data: data).padding(horizontal: 16, bottom: 8),
|
||||||
_PostHeadline(
|
_PostHeadline(
|
||||||
data: data,
|
data: data,
|
||||||
isEnlarge: data.type == 'article',
|
isEnlarge: data.type == 'article',
|
||||||
@ -438,6 +508,30 @@ class PostShareImageWidget extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class _PostQuestionHint extends StatelessWidget {
|
||||||
|
final SnPost data;
|
||||||
|
|
||||||
|
const _PostQuestionHint({required this.data});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Row(
|
||||||
|
children: [
|
||||||
|
Icon(data.body['answer'] == null ? Symbols.help : Symbols.check_circle, size: 20),
|
||||||
|
const Gap(4),
|
||||||
|
if (data.body['answer'] == null && data.body['reward']?.toDouble() != null)
|
||||||
|
Text('postQuestionUnansweredWithReward'.tr(args: [
|
||||||
|
'${data.body['reward']}',
|
||||||
|
])).opacity(0.75)
|
||||||
|
else if (data.body['answer'] == null)
|
||||||
|
Text('postQuestionUnanswered'.tr()).opacity(0.75)
|
||||||
|
else
|
||||||
|
Text('postQuestionAnswered'.tr()).opacity(0.75),
|
||||||
|
],
|
||||||
|
).opacity(0.75);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class _PostBottomAction extends StatelessWidget {
|
class _PostBottomAction extends StatelessWidget {
|
||||||
final SnPost data;
|
final SnPost data;
|
||||||
final bool showComments;
|
final bool showComments;
|
||||||
@ -529,7 +623,7 @@ class _PostBottomAction extends StatelessWidget {
|
|||||||
context: context,
|
context: context,
|
||||||
useRootNavigator: true,
|
useRootNavigator: true,
|
||||||
builder: (context) => PostCommentListPopup(
|
builder: (context) => PostCommentListPopup(
|
||||||
postId: data.id,
|
post: data,
|
||||||
commentCount: data.metric.replyCount,
|
commentCount: data.metric.replyCount,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@ -652,6 +746,7 @@ class _PostContentHeader extends StatelessWidget {
|
|||||||
final bool showMenu;
|
final bool showMenu;
|
||||||
final Function onDeleted;
|
final Function onDeleted;
|
||||||
final Function() onShare, onShareImage;
|
final Function() onShare, onShareImage;
|
||||||
|
final Function()? onSelectAnswer;
|
||||||
|
|
||||||
const _PostContentHeader({
|
const _PostContentHeader({
|
||||||
required this.data,
|
required this.data,
|
||||||
@ -662,6 +757,7 @@ class _PostContentHeader extends StatelessWidget {
|
|||||||
required this.onDeleted,
|
required this.onDeleted,
|
||||||
required this.onShare,
|
required this.onShare,
|
||||||
required this.onShareImage,
|
required this.onShareImage,
|
||||||
|
this.onSelectAnswer,
|
||||||
});
|
});
|
||||||
|
|
||||||
Future<void> _deletePost(BuildContext context) async {
|
Future<void> _deletePost(BuildContext context) async {
|
||||||
@ -760,6 +856,20 @@ class _PostContentHeader extends StatelessWidget {
|
|||||||
visualDensity: VisualDensity(horizontal: -4, vertical: -4),
|
visualDensity: VisualDensity(horizontal: -4, vertical: -4),
|
||||||
),
|
),
|
||||||
itemBuilder: (BuildContext context) => <PopupMenuEntry>[
|
itemBuilder: (BuildContext context) => <PopupMenuEntry>[
|
||||||
|
if (isAuthor && onSelectAnswer != null)
|
||||||
|
PopupMenuItem(
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
const Icon(Symbols.check_circle),
|
||||||
|
const Gap(16),
|
||||||
|
Text('postQuestionAnswerSelect').tr(),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
onTap: () {
|
||||||
|
onSelectAnswer?.call();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
if (isAuthor && onSelectAnswer != null) PopupMenuDivider(),
|
||||||
if (isAuthor)
|
if (isAuthor)
|
||||||
PopupMenuItem(
|
PopupMenuItem(
|
||||||
child: Row(
|
child: Row(
|
||||||
@ -833,7 +943,7 @@ class _PostContentHeader extends StatelessWidget {
|
|||||||
onTap: () {
|
onTap: () {
|
||||||
showModalBottomSheet(
|
showModalBottomSheet(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (context) => _PostGetInsightSheet(postId: data.id),
|
builder: (context) => _PostGetInsightPopup(postId: data.id),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
@ -1139,8 +1249,18 @@ class _PostFeaturedComment extends StatefulWidget {
|
|||||||
|
|
||||||
class _PostFeaturedCommentState extends State<_PostFeaturedComment> {
|
class _PostFeaturedCommentState extends State<_PostFeaturedComment> {
|
||||||
SnPost? _featuredComment;
|
SnPost? _featuredComment;
|
||||||
|
bool _isAnswer = false;
|
||||||
|
|
||||||
Future<void> _fetchComments() async {
|
Future<void> _fetchComments() async {
|
||||||
|
// If this is a answered question, fetch the answer instead
|
||||||
|
if (widget.data.type == 'question' && widget.data.body['answer'] != null) {
|
||||||
|
final sn = context.read<SnNetworkProvider>();
|
||||||
|
final resp = await sn.client.get('/cgi/co/posts/${widget.data.body['answer']}');
|
||||||
|
_isAnswer = true;
|
||||||
|
setState(() => _featuredComment = SnPost.fromJson(resp.data));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
final sn = context.read<SnNetworkProvider>();
|
final sn = context.read<SnNetworkProvider>();
|
||||||
final resp = await sn.client.get('/cgi/co/posts/${widget.data.id}/replies/featured', queryParameters: {
|
final resp = await sn.client.get('/cgi/co/posts/${widget.data.id}/replies/featured', queryParameters: {
|
||||||
@ -1166,13 +1286,15 @@ class _PostFeaturedCommentState extends State<_PostFeaturedComment> {
|
|||||||
if (widget.data.metric.replyCount == 0) return const SizedBox.shrink();
|
if (widget.data.metric.replyCount == 0) return const SizedBox.shrink();
|
||||||
if (_featuredComment == null) return const SizedBox.shrink();
|
if (_featuredComment == null) return const SizedBox.shrink();
|
||||||
|
|
||||||
|
final sn = context.read<SnNetworkProvider>();
|
||||||
|
|
||||||
return AnimateWidgetExtensions(Container(
|
return AnimateWidgetExtensions(Container(
|
||||||
constraints: BoxConstraints(maxWidth: widget.maxWidth ?? double.infinity),
|
constraints: BoxConstraints(maxWidth: widget.maxWidth ?? double.infinity),
|
||||||
margin: const EdgeInsets.only(top: 8),
|
margin: const EdgeInsets.only(top: 8),
|
||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
child: Material(
|
child: Material(
|
||||||
borderRadius: const BorderRadius.all(Radius.circular(8)),
|
borderRadius: const BorderRadius.all(Radius.circular(8)),
|
||||||
color: Theme.of(context).colorScheme.surfaceContainerHigh,
|
color: _isAnswer ? Colors.green.withOpacity(0.5) : Theme.of(context).colorScheme.surfaceContainerHigh,
|
||||||
child: InkWell(
|
child: InkWell(
|
||||||
borderRadius: const BorderRadius.all(Radius.circular(8)),
|
borderRadius: const BorderRadius.all(Radius.circular(8)),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
@ -1180,7 +1302,7 @@ class _PostFeaturedCommentState extends State<_PostFeaturedComment> {
|
|||||||
context: context,
|
context: context,
|
||||||
useRootNavigator: true,
|
useRootNavigator: true,
|
||||||
builder: (context) => PostCommentListPopup(
|
builder: (context) => PostCommentListPopup(
|
||||||
postId: widget.data.id,
|
post: widget.data,
|
||||||
commentCount: widget.data.metric.replyCount,
|
commentCount: widget.data.metric.replyCount,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@ -1188,7 +1310,18 @@ class _PostFeaturedCommentState extends State<_PostFeaturedComment> {
|
|||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Text('postFeaturedComment', style: Theme.of(context).textTheme.titleMedium!.copyWith(fontSize: 16)).tr(),
|
Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
const Gap(2),
|
||||||
|
Icon(_isAnswer ? Symbols.task_alt : Symbols.prompt_suggestion, size: 20),
|
||||||
|
const Gap(10),
|
||||||
|
Text(
|
||||||
|
_isAnswer ? 'postQuestionAnswerTitle' : 'postFeaturedComment',
|
||||||
|
style: Theme.of(context).textTheme.titleMedium!.copyWith(fontSize: 15),
|
||||||
|
).tr(),
|
||||||
|
],
|
||||||
|
),
|
||||||
const Gap(4),
|
const Gap(4),
|
||||||
Row(
|
Row(
|
||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
@ -1196,7 +1329,7 @@ class _PostFeaturedCommentState extends State<_PostFeaturedComment> {
|
|||||||
CircleAvatar(
|
CircleAvatar(
|
||||||
radius: 12,
|
radius: 12,
|
||||||
backgroundImage: UniversalImage.provider(
|
backgroundImage: UniversalImage.provider(
|
||||||
_featuredComment!.publisher.avatar,
|
sn.getAttachmentUrl(_featuredComment!.publisher.avatar),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const Gap(8),
|
const Gap(8),
|
||||||
@ -1292,16 +1425,16 @@ class _PostAbuseReportDialogState extends State<_PostAbuseReportDialog> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class _PostGetInsightSheet extends StatefulWidget {
|
class _PostGetInsightPopup extends StatefulWidget {
|
||||||
final int postId;
|
final int postId;
|
||||||
|
|
||||||
const _PostGetInsightSheet({required this.postId});
|
const _PostGetInsightPopup({required this.postId});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<_PostGetInsightSheet> createState() => _PostGetInsightSheetState();
|
State<_PostGetInsightPopup> createState() => _PostGetInsightPopupState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _PostGetInsightSheetState extends State<_PostGetInsightSheet> {
|
class _PostGetInsightPopupState extends State<_PostGetInsightPopup> {
|
||||||
String? _response;
|
String? _response;
|
||||||
String? _thinkingProcess;
|
String? _thinkingProcess;
|
||||||
|
|
||||||
@ -1314,8 +1447,14 @@ class _PostGetInsightSheetState extends State<_PostGetInsightSheet> {
|
|||||||
receiveTimeout: const Duration(minutes: 10),
|
receiveTimeout: const Duration(minutes: 10),
|
||||||
));
|
));
|
||||||
final out = resp.data['response'] as String;
|
final out = resp.data['response'] as String;
|
||||||
final document = XmlDocument.parse(out);
|
|
||||||
_thinkingProcess = document.getElement('think')?.innerText.trim();
|
try {
|
||||||
|
final document = XmlDocument.parse(out);
|
||||||
|
_thinkingProcess = document.getElement('think')?.innerText.trim();
|
||||||
|
} catch (_) {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
|
||||||
RegExp cleanThinkingRegExp = RegExp(r'<think>[\s\S]*?</think>');
|
RegExp cleanThinkingRegExp = RegExp(r'<think>[\s\S]*?</think>');
|
||||||
setState(() => _response = out.replaceAll(cleanThinkingRegExp, '').trim());
|
setState(() => _response = out.replaceAll(cleanThinkingRegExp, '').trim());
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@ -1384,3 +1523,29 @@ class _PostGetInsightSheetState extends State<_PostGetInsightSheet> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class _PostVideoPlayer extends StatelessWidget {
|
||||||
|
final SnPost data;
|
||||||
|
|
||||||
|
const _PostVideoPlayer({required this.data});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
borderRadius: const BorderRadius.all(Radius.circular(8)),
|
||||||
|
border: Border.all(
|
||||||
|
color: Theme.of(context).dividerColor,
|
||||||
|
width: 1,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: AspectRatio(
|
||||||
|
aspectRatio: 16 / 9,
|
||||||
|
child: ClipRRect(
|
||||||
|
borderRadius: const BorderRadius.all(Radius.circular(8)),
|
||||||
|
child: AttachmentItem(data: data.preload!.video!, heroTag: 'post-video-${data.id}'),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -95,6 +95,7 @@ class PostMediaPendingList extends StatelessWidget {
|
|||||||
context: context,
|
context: context,
|
||||||
builder: (context) => AttachmentInputDialog(
|
builder: (context) => AttachmentInputDialog(
|
||||||
title: 'attachmentSetThumbnail'.tr(),
|
title: 'attachmentSetThumbnail'.tr(),
|
||||||
|
pool: 'interactive',
|
||||||
analyzeNow: true,
|
analyzeNow: true,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@ -292,7 +293,7 @@ class PostMediaPendingList extends StatelessWidget {
|
|||||||
constraints: const BoxConstraints(maxHeight: 120),
|
constraints: const BoxConstraints(maxHeight: 120),
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
const Gap(8),
|
const Gap(16),
|
||||||
if (thumbnail != null)
|
if (thumbnail != null)
|
||||||
ContextMenuArea(
|
ContextMenuArea(
|
||||||
contextMenu: _createContextMenu(context, -1, thumbnail!),
|
contextMenu: _createContextMenu(context, -1, thumbnail!),
|
||||||
@ -337,15 +338,10 @@ class _PostMediaPendingItem extends StatelessWidget {
|
|||||||
|
|
||||||
final sn = context.read<SnNetworkProvider>();
|
final sn = context.read<SnNetworkProvider>();
|
||||||
|
|
||||||
return Container(
|
return Material(
|
||||||
decoration: BoxDecoration(
|
elevation: 4,
|
||||||
border: Border.all(
|
color: Theme.of(context).colorScheme.surfaceContainer,
|
||||||
color: Theme.of(context).dividerColor,
|
borderRadius: BorderRadius.circular(8),
|
||||||
width: 1,
|
|
||||||
),
|
|
||||||
borderRadius: BorderRadius.circular(8),
|
|
||||||
color: Theme.of(context).colorScheme.surfaceContainer,
|
|
||||||
),
|
|
||||||
child: ClipRRect(
|
child: ClipRRect(
|
||||||
borderRadius: const BorderRadius.all(Radius.circular(8)),
|
borderRadius: const BorderRadius.all(Radius.circular(8)),
|
||||||
child: Row(
|
child: Row(
|
||||||
|
@ -19,6 +19,7 @@ const Map<int, String> kPostVisibilityLevel = {
|
|||||||
|
|
||||||
class PostMetaEditor extends StatelessWidget {
|
class PostMetaEditor extends StatelessWidget {
|
||||||
final PostWriteController controller;
|
final PostWriteController controller;
|
||||||
|
|
||||||
const PostMetaEditor({super.key, required this.controller});
|
const PostMetaEditor({super.key, required this.controller});
|
||||||
|
|
||||||
Future<DateTime?> _selectDate(
|
Future<DateTime?> _selectDate(
|
||||||
@ -87,28 +88,6 @@ class PostMetaEditor extends StatelessWidget {
|
|||||||
padding: EdgeInsets.only(bottom: MediaQuery.of(context).padding.bottom + 8),
|
padding: EdgeInsets.only(bottom: MediaQuery.of(context).padding.bottom + 8),
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
TextField(
|
|
||||||
controller: controller.titleController,
|
|
||||||
decoration: InputDecoration(
|
|
||||||
labelText: 'fieldPostTitle'.tr(),
|
|
||||||
border: UnderlineInputBorder(),
|
|
||||||
),
|
|
||||||
onTapOutside: (_) =>
|
|
||||||
FocusManager.instance.primaryFocus?.unfocus(),
|
|
||||||
).padding(horizontal: 24),
|
|
||||||
if (controller.mode == 'articles') const Gap(4),
|
|
||||||
if (controller.mode == 'articles')
|
|
||||||
TextField(
|
|
||||||
controller: controller.descriptionController,
|
|
||||||
maxLines: null,
|
|
||||||
decoration: InputDecoration(
|
|
||||||
labelText: 'fieldPostDescription'.tr(),
|
|
||||||
border: UnderlineInputBorder(),
|
|
||||||
),
|
|
||||||
onTapOutside: (_) =>
|
|
||||||
FocusManager.instance.primaryFocus?.unfocus(),
|
|
||||||
).padding(horizontal: 24),
|
|
||||||
const Gap(4),
|
|
||||||
PostTagsField(
|
PostTagsField(
|
||||||
initialTags: controller.tags,
|
initialTags: controller.tags,
|
||||||
labelText: 'fieldPostTags'.tr(),
|
labelText: 'fieldPostTags'.tr(),
|
||||||
@ -133,8 +112,7 @@ class PostMetaEditor extends StatelessWidget {
|
|||||||
helperMaxLines: 2,
|
helperMaxLines: 2,
|
||||||
border: UnderlineInputBorder(),
|
border: UnderlineInputBorder(),
|
||||||
),
|
),
|
||||||
onTapOutside: (_) =>
|
onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(),
|
||||||
FocusManager.instance.primaryFocus?.unfocus(),
|
|
||||||
).padding(horizontal: 24),
|
).padding(horizontal: 24),
|
||||||
const Gap(12),
|
const Gap(12),
|
||||||
ListTile(
|
ListTile(
|
||||||
@ -182,8 +160,7 @@ class PostMetaEditor extends StatelessWidget {
|
|||||||
leading: Icon(Symbols.person),
|
leading: Icon(Symbols.person),
|
||||||
trailing: Icon(Symbols.chevron_right),
|
trailing: Icon(Symbols.chevron_right),
|
||||||
title: Text('postVisibleUsers').tr(),
|
title: Text('postVisibleUsers').tr(),
|
||||||
subtitle: Text('postSelectedUsers')
|
subtitle: Text('postSelectedUsers').plural(controller.visibleUsers.length),
|
||||||
.plural(controller.visibleUsers.length),
|
|
||||||
onTap: () {
|
onTap: () {
|
||||||
_selectVisibleUser(context);
|
_selectVisibleUser(context);
|
||||||
},
|
},
|
||||||
@ -194,8 +171,7 @@ class PostMetaEditor extends StatelessWidget {
|
|||||||
leading: Icon(Symbols.person),
|
leading: Icon(Symbols.person),
|
||||||
trailing: Icon(Symbols.chevron_right),
|
trailing: Icon(Symbols.chevron_right),
|
||||||
title: Text('postInvisibleUsers').tr(),
|
title: Text('postInvisibleUsers').tr(),
|
||||||
subtitle: Text('postSelectedUsers')
|
subtitle: Text('postSelectedUsers').plural(controller.invisibleUsers.length),
|
||||||
.plural(controller.invisibleUsers.length),
|
|
||||||
onTap: () {
|
onTap: () {
|
||||||
_selectInvisibleUser(context);
|
_selectInvisibleUser(context);
|
||||||
},
|
},
|
||||||
@ -204,9 +180,7 @@ class PostMetaEditor extends StatelessWidget {
|
|||||||
leading: const Icon(Symbols.event_available),
|
leading: const Icon(Symbols.event_available),
|
||||||
title: Text('postPublishedAt').tr(),
|
title: Text('postPublishedAt').tr(),
|
||||||
subtitle: Text(
|
subtitle: Text(
|
||||||
controller.publishedAt != null
|
controller.publishedAt != null ? dateFormatter.format(controller.publishedAt!) : 'unset'.tr(),
|
||||||
? dateFormatter.format(controller.publishedAt!)
|
|
||||||
: 'unset'.tr(),
|
|
||||||
),
|
),
|
||||||
trailing: controller.publishedAt != null
|
trailing: controller.publishedAt != null
|
||||||
? IconButton(
|
? IconButton(
|
||||||
@ -230,9 +204,7 @@ class PostMetaEditor extends StatelessWidget {
|
|||||||
leading: const Icon(Symbols.event_busy),
|
leading: const Icon(Symbols.event_busy),
|
||||||
title: Text('postPublishedUntil').tr(),
|
title: Text('postPublishedUntil').tr(),
|
||||||
subtitle: Text(
|
subtitle: Text(
|
||||||
controller.publishedUntil != null
|
controller.publishedUntil != null ? dateFormatter.format(controller.publishedUntil!) : 'unset'.tr(),
|
||||||
? dateFormatter.format(controller.publishedUntil!)
|
|
||||||
: 'unset'.tr(),
|
|
||||||
),
|
),
|
||||||
trailing: controller.publishedUntil != null
|
trailing: controller.publishedUntil != null
|
||||||
? IconButton(
|
? IconButton(
|
||||||
|
@ -2,7 +2,6 @@ PODS:
|
|||||||
- bitsdojo_window_macos (0.0.1):
|
- bitsdojo_window_macos (0.0.1):
|
||||||
- FlutterMacOS
|
- FlutterMacOS
|
||||||
- connectivity_plus (0.0.1):
|
- connectivity_plus (0.0.1):
|
||||||
- Flutter
|
|
||||||
- FlutterMacOS
|
- FlutterMacOS
|
||||||
- croppy (0.0.1):
|
- croppy (0.0.1):
|
||||||
- FlutterMacOS
|
- FlutterMacOS
|
||||||
@ -143,7 +142,7 @@ PODS:
|
|||||||
- HotKey
|
- HotKey
|
||||||
- in_app_review (2.0.0):
|
- in_app_review (2.0.0):
|
||||||
- FlutterMacOS
|
- FlutterMacOS
|
||||||
- livekit_client (2.3.5):
|
- livekit_client (2.3.6):
|
||||||
- flutter_webrtc
|
- flutter_webrtc
|
||||||
- FlutterMacOS
|
- FlutterMacOS
|
||||||
- WebRTC-SDK (= 125.6422.06)
|
- WebRTC-SDK (= 125.6422.06)
|
||||||
@ -190,7 +189,7 @@ PODS:
|
|||||||
|
|
||||||
DEPENDENCIES:
|
DEPENDENCIES:
|
||||||
- bitsdojo_window_macos (from `Flutter/ephemeral/.symlinks/plugins/bitsdojo_window_macos/macos`)
|
- bitsdojo_window_macos (from `Flutter/ephemeral/.symlinks/plugins/bitsdojo_window_macos/macos`)
|
||||||
- connectivity_plus (from `Flutter/ephemeral/.symlinks/plugins/connectivity_plus/darwin`)
|
- connectivity_plus (from `Flutter/ephemeral/.symlinks/plugins/connectivity_plus/macos`)
|
||||||
- croppy (from `Flutter/ephemeral/.symlinks/plugins/croppy/macos`)
|
- croppy (from `Flutter/ephemeral/.symlinks/plugins/croppy/macos`)
|
||||||
- device_info_plus (from `Flutter/ephemeral/.symlinks/plugins/device_info_plus/macos`)
|
- device_info_plus (from `Flutter/ephemeral/.symlinks/plugins/device_info_plus/macos`)
|
||||||
- file_picker (from `Flutter/ephemeral/.symlinks/plugins/file_picker/macos`)
|
- file_picker (from `Flutter/ephemeral/.symlinks/plugins/file_picker/macos`)
|
||||||
@ -244,7 +243,7 @@ EXTERNAL SOURCES:
|
|||||||
bitsdojo_window_macos:
|
bitsdojo_window_macos:
|
||||||
:path: Flutter/ephemeral/.symlinks/plugins/bitsdojo_window_macos/macos
|
:path: Flutter/ephemeral/.symlinks/plugins/bitsdojo_window_macos/macos
|
||||||
connectivity_plus:
|
connectivity_plus:
|
||||||
:path: Flutter/ephemeral/.symlinks/plugins/connectivity_plus/darwin
|
:path: Flutter/ephemeral/.symlinks/plugins/connectivity_plus/macos
|
||||||
croppy:
|
croppy:
|
||||||
:path: Flutter/ephemeral/.symlinks/plugins/croppy/macos
|
:path: Flutter/ephemeral/.symlinks/plugins/croppy/macos
|
||||||
device_info_plus:
|
device_info_plus:
|
||||||
@ -308,7 +307,7 @@ EXTERNAL SOURCES:
|
|||||||
|
|
||||||
SPEC CHECKSUMS:
|
SPEC CHECKSUMS:
|
||||||
bitsdojo_window_macos: 44e3b8fe3dd463820e0321f6256c5b1c16bb6a00
|
bitsdojo_window_macos: 44e3b8fe3dd463820e0321f6256c5b1c16bb6a00
|
||||||
connectivity_plus: 18382e7311ba19efcaee94442b23b32507b20695
|
connectivity_plus: 0a976dfd033b59192912fa3c6c7b54aab5093802
|
||||||
croppy: 25a638bd7d05411d8c697f481568f261037694fc
|
croppy: 25a638bd7d05411d8c697f481568f261037694fc
|
||||||
device_info_plus: 1b14eed9bf95428983aed283a8d51cce3d8c4215
|
device_info_plus: 1b14eed9bf95428983aed283a8d51cce3d8c4215
|
||||||
file_picker: e716a70a9fe5fd9e09ebc922d7541464289443af
|
file_picker: e716a70a9fe5fd9e09ebc922d7541464289443af
|
||||||
@ -334,7 +333,7 @@ SPEC CHECKSUMS:
|
|||||||
HotKey: 400beb7caa29054ea8d864c96f5ba7e5b4852277
|
HotKey: 400beb7caa29054ea8d864c96f5ba7e5b4852277
|
||||||
hotkey_manager_macos: 1e2edb0c7ae4fe67108af44a9d3445de41404160
|
hotkey_manager_macos: 1e2edb0c7ae4fe67108af44a9d3445de41404160
|
||||||
in_app_review: a6a031b9acd03c7d103e341aa334adf2c493fb93
|
in_app_review: a6a031b9acd03c7d103e341aa334adf2c493fb93
|
||||||
livekit_client: 91c68237edede55f8891a166a28c1daec8a1e4b1
|
livekit_client: 0ad107154753a5a76802d2222c040223ad049499
|
||||||
media_kit_libs_macos_video: b3e2bbec2eef97c285f2b1baa7963c67c753fb82
|
media_kit_libs_macos_video: b3e2bbec2eef97c285f2b1baa7963c67c753fb82
|
||||||
media_kit_native_event_loop: 81fd5b45192b72f8b5b69eaf5b540f45777eb8d5
|
media_kit_native_event_loop: 81fd5b45192b72f8b5b69eaf5b540f45777eb8d5
|
||||||
media_kit_video: c75b07f14d59706c775778e4dd47dd027de8d1e5
|
media_kit_video: c75b07f14d59706c775778e4dd47dd027de8d1e5
|
||||||
|
48
pubspec.lock
48
pubspec.lock
@ -214,6 +214,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.4.3"
|
version: "0.4.3"
|
||||||
|
chalkdart:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: chalkdart
|
||||||
|
sha256: "08c910ee341fcdd1e46f20ddce59b13c1d85f5d97f2fd2f12014c46ede670e40"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.3.2"
|
||||||
characters:
|
characters:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -266,10 +274,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: connectivity_plus
|
name: connectivity_plus
|
||||||
sha256: "8a68739d3ee113e51ad35583fdf9ab82c55d09d693d3c39da1aebab87c938412"
|
sha256: "04bf81bb0b77de31557b58d052b24b3eee33f09a6e7a8c68a3e247c7df19ec27"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "6.1.2"
|
version: "6.1.3"
|
||||||
connectivity_plus_platform_interface:
|
connectivity_plus_platform_interface:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -362,10 +370,10 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: device_info_plus
|
name: device_info_plus
|
||||||
sha256: e3fc9a65820fef83035af8ee8c09004a719d5d1d54e6de978fcb0d84bbeb241a
|
sha256: "72d146c6d7098689ff5c5f66bcf593ac11efc530095385356e131070333e64da"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "11.2.2"
|
version: "11.3.0"
|
||||||
device_info_plus_platform_interface:
|
device_info_plus_platform_interface:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -490,10 +498,10 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: file_picker
|
name: file_picker
|
||||||
sha256: c9943dd7d702ab4199d199bc151a2d79c86db031a02ad84566dab58c494d2adc
|
sha256: cacfdc5abe93e64d418caa9256eef663499ad791bb688d9fd12c85a311968fba
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "8.3.1"
|
version: "8.3.2"
|
||||||
file_saver:
|
file_saver:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@ -830,10 +838,10 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: flutter_webrtc
|
name: flutter_webrtc
|
||||||
sha256: "4c76069f724f79dc6e8739c8f16c2ee00ca30a1f8e3aa75c0e830f8183278f03"
|
sha256: e917067abeef2400e6a7a03db53a6e1418551e54809f18ab80447ac323eb77e4
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.12.7"
|
version: "0.12.8"
|
||||||
freezed:
|
freezed:
|
||||||
dependency: "direct dev"
|
dependency: "direct dev"
|
||||||
description:
|
description:
|
||||||
@ -886,10 +894,10 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: go_router
|
name: go_router
|
||||||
sha256: "9b736a9fa879d8ad6df7932cbdcc58237c173ab004ef90d8377923d7ad731eaa"
|
sha256: "04539267a740931c6d4479a10d466717ca5901c6fdfd3fcda09391bbb8ebd651"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "14.7.2"
|
version: "14.8.0"
|
||||||
google_fonts:
|
google_fonts:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@ -934,10 +942,10 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: home_widget
|
name: home_widget
|
||||||
sha256: b313e3304c0429669fddf1286e1fbf61a64b873f38ba30b3eb890ef0d7560b12
|
sha256: "7430f7549d42cef2e729bd3c779de748b93f1eb78b1abfe6bca8fffd1cfce3e9"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.7.0"
|
version: "0.7.0+1"
|
||||||
hotkey_manager:
|
hotkey_manager:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@ -1190,10 +1198,10 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: livekit_client
|
name: livekit_client
|
||||||
sha256: "02b4653d903852d0ae86b15fbe4324747606dae6410fe860d0c07a11c79988de"
|
sha256: "0cfb2f48eff7a93ea8927696dc6f218aebd2fcd1fcc1b1a7b2f53ff3597fdb52"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.3.5"
|
version: "2.3.6"
|
||||||
logging:
|
logging:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -1246,10 +1254,10 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: material_symbols_icons
|
name: material_symbols_icons
|
||||||
sha256: "89aac72d25dd49303f71b3b1e70f8374791846729365b25bebc2a2531e5b86cd"
|
sha256: "1403944c2a68dbdf934fca2b2115a372e70a4a185c136ab71a9d16e2108d0b14"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "4.2801.1"
|
version: "4.2805.1"
|
||||||
media_kit:
|
media_kit:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@ -1382,10 +1390,10 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: package_info_plus
|
name: package_info_plus
|
||||||
sha256: c447a3c3e7be4addf129b8f9ab6a4bd5d166b78918223e223b61fddf4d07e254
|
sha256: "67eae327b1b0faf761964a1d2e5d323c797f3799db0e85aa232db8d9e922bc35"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "8.2.0"
|
version: "8.2.1"
|
||||||
package_info_plus_platform_interface:
|
package_info_plus_platform_interface:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -2251,10 +2259,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: webrtc_interface
|
name: webrtc_interface
|
||||||
sha256: abec3ab7956bd5ac539cf34a42fa0c82ea26675847c0966bb85160400eea9388
|
sha256: "10fc6dc0ac16f909f5e434c18902415211d759313c87261f1e4ec5b4f6a04c26"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.2.0"
|
version: "1.2.1"
|
||||||
win32:
|
win32:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -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.3.2+63
|
version: 2.3.2+65
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: ^3.5.4
|
sdk: ^3.5.4
|
||||||
|
Reference in New Issue
Block a user