Compare commits
14 Commits
Author | SHA1 | Date | |
---|---|---|---|
5ddd4fed2e | |||
48b6d5f6c1 | |||
b83b0b5efb | |||
cb24bd953d | |||
4937dee182 | |||
d612097bb1 | |||
058d668b6b | |||
8b19462c3a | |||
0a381ef09b | |||
9b84e912b2 | |||
b3254e0f2f | |||
f0a3bbe023 | |||
df81c84438 | |||
8b12395fca |
@ -5,7 +5,7 @@ meta {
|
|||||||
}
|
}
|
||||||
|
|
||||||
post {
|
post {
|
||||||
url: {{endpoint}}/cgi/id/dev/notify/1
|
url: {{endpoint}}/cgi/id/dev/notify/122
|
||||||
body: json
|
body: json
|
||||||
auth: inherit
|
auth: inherit
|
||||||
}
|
}
|
||||||
@ -15,12 +15,9 @@ body:json {
|
|||||||
"client_id": "{{third_client_id}}",
|
"client_id": "{{third_client_id}}",
|
||||||
"client_secret":"{{third_client_tk}}",
|
"client_secret":"{{third_client_tk}}",
|
||||||
"type": "general",
|
"type": "general",
|
||||||
"subject": "测试",
|
"subject": "处理该帐号 @solian 的决定",
|
||||||
"subtitle": "Alphabot です",
|
"subtitle": "违反用户协议",
|
||||||
"content": "全新通知动画",
|
"content": "您的帐号违反了我们用户协议中关于冒充我们官方的行为,至此做出停权的决定。还请见谅。该决定是最终决定,不接受上诉。",
|
||||||
"metadata": {
|
|
||||||
"image": "D2EDbcrsTugs3xk5"
|
|
||||||
},
|
|
||||||
"priority": 10
|
"priority": 10
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -27,6 +27,7 @@
|
|||||||
"screenChatNew": "New Channel",
|
"screenChatNew": "New Channel",
|
||||||
"screenRealm": "Realm",
|
"screenRealm": "Realm",
|
||||||
"screenRealmManage": "Edit Realm",
|
"screenRealmManage": "Edit Realm",
|
||||||
|
"screenRealmDiscovery": "Realm Discovery",
|
||||||
"screenRealmNew": "New Realm",
|
"screenRealmNew": "New Realm",
|
||||||
"screenNotification": "Notification",
|
"screenNotification": "Notification",
|
||||||
"screenPostSearch": "Search Posts",
|
"screenPostSearch": "Search Posts",
|
||||||
@ -619,5 +620,23 @@
|
|||||||
"postQuestionAnswered": "Answered Question",
|
"postQuestionAnswered": "Answered Question",
|
||||||
"postQuestionAnswerSelect": "Select as Answer",
|
"postQuestionAnswerSelect": "Select as Answer",
|
||||||
"postQuestionAnswerSelected": "Answer has been selected, reward has been applied.",
|
"postQuestionAnswerSelected": "Answer has been selected, reward has been applied.",
|
||||||
"postVideoUpload": "Upload Video"
|
"postVideoUpload": "Upload Video",
|
||||||
|
"realmJoin": "Join Realm",
|
||||||
|
"realmCommunityHint": "This realm is a community realm, you can freely join.",
|
||||||
|
"realmCommunityPublicChannelsHint": "The public channels in this realm",
|
||||||
|
"realmJoined": "Joined realm {}.",
|
||||||
|
"join": "Join",
|
||||||
|
"pollEditorNew": "New Poll",
|
||||||
|
"pollEditorEdit": "Edit Poll",
|
||||||
|
"pollEditorDelete": "Delete Poll",
|
||||||
|
"pollEditorDeleteDescription": "Are you sure you want to delete this poll? This operation is irreversible.",
|
||||||
|
"pollEditorUnlink": "Unlink Poll",
|
||||||
|
"pollOptionAdd": "Add Option",
|
||||||
|
"pollOptionName": "Option Name",
|
||||||
|
"pollLinkExisting": "Link existing poll",
|
||||||
|
"pollAnswered": "Answered the poll.",
|
||||||
|
"pollVotes": {
|
||||||
|
"one": "{} vote",
|
||||||
|
"other": "{} votes"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -25,6 +25,7 @@
|
|||||||
"screenChatNew": "新建聊天频道",
|
"screenChatNew": "新建聊天频道",
|
||||||
"screenRealm": "领域",
|
"screenRealm": "领域",
|
||||||
"screenRealmManage": "编辑领域",
|
"screenRealmManage": "编辑领域",
|
||||||
|
"screenRealmDiscovery": "发现领域",
|
||||||
"screenRealmNew": "新建领域",
|
"screenRealmNew": "新建领域",
|
||||||
"screenNotification": "通知",
|
"screenNotification": "通知",
|
||||||
"screenPostSearch": "搜索帖子",
|
"screenPostSearch": "搜索帖子",
|
||||||
@ -618,5 +619,23 @@
|
|||||||
"postQuestionAnswerTitle": "精选解答",
|
"postQuestionAnswerTitle": "精选解答",
|
||||||
"postQuestionAnswerSelect": "选择解答",
|
"postQuestionAnswerSelect": "选择解答",
|
||||||
"postQuestionAnswerSelected": "解答已选择,奖励已发放。",
|
"postQuestionAnswerSelected": "解答已选择,奖励已发放。",
|
||||||
"postVideoUpload": "上传视频"
|
"postVideoUpload": "上传视频",
|
||||||
|
"realmJoin": "加入领域",
|
||||||
|
"realmCommunityHint": "该领域是一个社区领域,你可以自由加入。",
|
||||||
|
"realmCommunityPublicChannelsHint": "该领域包含的公共频道",
|
||||||
|
"realmJoined": "已加入领域 {}。",
|
||||||
|
"join": "加入",
|
||||||
|
"pollEditorNew": "新投票",
|
||||||
|
"pollEditorEdit": "编辑投票",
|
||||||
|
"pollEditorDelete": "删除投票",
|
||||||
|
"pollEditorDeleteDescription": "你确定要删除这个投票吗?该操作不可撤销。",
|
||||||
|
"pollEditorUnlink": "解除链接",
|
||||||
|
"pollOptionAdd": "添加选项",
|
||||||
|
"pollOptionName": "选项名称",
|
||||||
|
"pollLinkExisting": "链接现有投票",
|
||||||
|
"pollAnswered": "答案已经反馈。",
|
||||||
|
"pollVotes": {
|
||||||
|
"one": "{} 票",
|
||||||
|
"other": "{} 票"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -25,6 +25,7 @@
|
|||||||
"screenChatNew": "新建聊天頻道",
|
"screenChatNew": "新建聊天頻道",
|
||||||
"screenRealm": "領域",
|
"screenRealm": "領域",
|
||||||
"screenRealmManage": "編輯領域",
|
"screenRealmManage": "編輯領域",
|
||||||
|
"screenRealmDiscovery": "發現領域",
|
||||||
"screenRealmNew": "新建領域",
|
"screenRealmNew": "新建領域",
|
||||||
"screenNotification": "通知",
|
"screenNotification": "通知",
|
||||||
"screenPostSearch": "搜索帖子",
|
"screenPostSearch": "搜索帖子",
|
||||||
@ -139,6 +140,7 @@
|
|||||||
"writePostTypeStory": "發動態",
|
"writePostTypeStory": "發動態",
|
||||||
"writePostTypeArticle": "寫文章",
|
"writePostTypeArticle": "寫文章",
|
||||||
"writePostTypeQuestion": "提問題",
|
"writePostTypeQuestion": "提問題",
|
||||||
|
"writePostTypeVideo": "發視頻",
|
||||||
"fieldPostPublisher": "帖子發佈者",
|
"fieldPostPublisher": "帖子發佈者",
|
||||||
"fieldPostContent": "發生什麼事了?!",
|
"fieldPostContent": "發生什麼事了?!",
|
||||||
"fieldPostTitle": "標題",
|
"fieldPostTitle": "標題",
|
||||||
@ -616,5 +618,11 @@
|
|||||||
"postQuestionAnswered": "已解答的問題",
|
"postQuestionAnswered": "已解答的問題",
|
||||||
"postQuestionAnswerTitle": "精選解答",
|
"postQuestionAnswerTitle": "精選解答",
|
||||||
"postQuestionAnswerSelect": "選擇解答",
|
"postQuestionAnswerSelect": "選擇解答",
|
||||||
"postQuestionAnswerSelected": "解答已選擇,獎勵已發放。"
|
"postQuestionAnswerSelected": "解答已選擇,獎勵已發放。",
|
||||||
|
"postVideoUpload": "上傳視頻",
|
||||||
|
"realmJoin": "加入領域",
|
||||||
|
"realmCommunityHint": "該領域是一個社區領域,你可以自由加入。",
|
||||||
|
"realmCommunityPublicChannelsHint": "該領域包含的公共頻道",
|
||||||
|
"realmJoined": "已加入領域 {}。",
|
||||||
|
"join": "加入"
|
||||||
}
|
}
|
||||||
|
@ -25,6 +25,7 @@
|
|||||||
"screenChatNew": "新建聊天頻道",
|
"screenChatNew": "新建聊天頻道",
|
||||||
"screenRealm": "領域",
|
"screenRealm": "領域",
|
||||||
"screenRealmManage": "編輯領域",
|
"screenRealmManage": "編輯領域",
|
||||||
|
"screenRealmDiscovery": "發現領域",
|
||||||
"screenRealmNew": "新建領域",
|
"screenRealmNew": "新建領域",
|
||||||
"screenNotification": "通知",
|
"screenNotification": "通知",
|
||||||
"screenPostSearch": "搜索帖子",
|
"screenPostSearch": "搜索帖子",
|
||||||
@ -139,6 +140,7 @@
|
|||||||
"writePostTypeStory": "發動態",
|
"writePostTypeStory": "發動態",
|
||||||
"writePostTypeArticle": "寫文章",
|
"writePostTypeArticle": "寫文章",
|
||||||
"writePostTypeQuestion": "提問題",
|
"writePostTypeQuestion": "提問題",
|
||||||
|
"writePostTypeVideo": "發視頻",
|
||||||
"fieldPostPublisher": "帖子發佈者",
|
"fieldPostPublisher": "帖子發佈者",
|
||||||
"fieldPostContent": "發生什麼事了?!",
|
"fieldPostContent": "發生什麼事了?!",
|
||||||
"fieldPostTitle": "標題",
|
"fieldPostTitle": "標題",
|
||||||
@ -616,5 +618,11 @@
|
|||||||
"postQuestionAnswered": "已解答的問題",
|
"postQuestionAnswered": "已解答的問題",
|
||||||
"postQuestionAnswerTitle": "精選解答",
|
"postQuestionAnswerTitle": "精選解答",
|
||||||
"postQuestionAnswerSelect": "選擇解答",
|
"postQuestionAnswerSelect": "選擇解答",
|
||||||
"postQuestionAnswerSelected": "解答已選擇,獎勵已發放。"
|
"postQuestionAnswerSelected": "解答已選擇,獎勵已發放。",
|
||||||
|
"postVideoUpload": "上傳視頻",
|
||||||
|
"realmJoin": "加入領域",
|
||||||
|
"realmCommunityHint": "該領域是一個社區領域,你可以自由加入。",
|
||||||
|
"realmCommunityPublicChannelsHint": "該領域包含的公共頻道",
|
||||||
|
"realmJoined": "已加入領域 {}。",
|
||||||
|
"join": "加入"
|
||||||
}
|
}
|
||||||
|
@ -16,6 +16,7 @@ import 'package:surface/providers/post.dart';
|
|||||||
import 'package:surface/providers/sn_attachment.dart';
|
import 'package:surface/providers/sn_attachment.dart';
|
||||||
import 'package:surface/providers/sn_network.dart';
|
import 'package:surface/providers/sn_network.dart';
|
||||||
import 'package:surface/types/attachment.dart';
|
import 'package:surface/types/attachment.dart';
|
||||||
|
import 'package:surface/types/poll.dart';
|
||||||
import 'package:surface/types/post.dart';
|
import 'package:surface/types/post.dart';
|
||||||
import 'package:surface/widgets/dialog.dart';
|
import 'package:surface/widgets/dialog.dart';
|
||||||
import 'package:surface/widgets/universal_image.dart';
|
import 'package:surface/widgets/universal_image.dart';
|
||||||
@ -199,6 +200,7 @@ class PostWriteController extends ChangeNotifier {
|
|||||||
List<PostWriteMedia> attachments = List.empty(growable: true);
|
List<PostWriteMedia> attachments = List.empty(growable: true);
|
||||||
DateTime? publishedAt, publishedUntil;
|
DateTime? publishedAt, publishedUntil;
|
||||||
SnAttachment? videoAttachment;
|
SnAttachment? videoAttachment;
|
||||||
|
SnPoll? poll;
|
||||||
|
|
||||||
Future<void> fetchRelatedPost(
|
Future<void> fetchRelatedPost(
|
||||||
BuildContext context, {
|
BuildContext context, {
|
||||||
@ -220,6 +222,7 @@ class PostWriteController extends ChangeNotifier {
|
|||||||
contentController.text = post.body['content'] ?? '';
|
contentController.text = post.body['content'] ?? '';
|
||||||
aliasController.text = post.alias ?? '';
|
aliasController.text = post.alias ?? '';
|
||||||
rewardController.text = post.body['reward']?.toString() ?? '';
|
rewardController.text = post.body['reward']?.toString() ?? '';
|
||||||
|
videoAttachment = post.preload?.video;
|
||||||
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);
|
||||||
@ -228,6 +231,7 @@ class PostWriteController extends ChangeNotifier {
|
|||||||
tags = List.from(post.tags.map((ele) => ele.alias), growable: true);
|
tags = List.from(post.tags.map((ele) => ele.alias), growable: true);
|
||||||
categories = List.from(post.categories.map((ele) => ele.alias), growable: true);
|
categories = List.from(post.categories.map((ele) => ele.alias), growable: true);
|
||||||
attachments.addAll(post.preload?.attachments?.map((ele) => PostWriteMedia(ele)) ?? []);
|
attachments.addAll(post.preload?.attachments?.map((ele) => PostWriteMedia(ele)) ?? []);
|
||||||
|
poll = post.preload?.poll;
|
||||||
|
|
||||||
if (post.preload?.thumbnail != null && (post.preload?.thumbnail?.rid.isNotEmpty ?? false)) {
|
if (post.preload?.thumbnail != null && (post.preload?.thumbnail?.rid.isNotEmpty ?? false)) {
|
||||||
thumbnail = PostWriteMedia(post.preload!.thumbnail);
|
thumbnail = PostWriteMedia(post.preload!.thumbnail);
|
||||||
@ -366,6 +370,7 @@ 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!.toJson(),
|
if (replyingPost != null) 'reply_to': replyingPost!.toJson(),
|
||||||
if (repostingPost != null) 'repost_to': repostingPost!.toJson(),
|
if (repostingPost != null) 'repost_to': repostingPost!.toJson(),
|
||||||
|
if (poll != null) 'poll': poll!.toJson(),
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@ -395,6 +400,7 @@ class PostWriteController extends ChangeNotifier {
|
|||||||
if (data['published_until'] != null) publishedUntil = DateTime.tryParse(data['published_until'])?.toLocal();
|
if (data['published_until'] != null) publishedUntil = DateTime.tryParse(data['published_until'])?.toLocal();
|
||||||
replyingPost = data['reply_to'] != null ? SnPost.fromJson(data['reply_to']) : null;
|
replyingPost = data['reply_to'] != null ? SnPost.fromJson(data['reply_to']) : null;
|
||||||
repostingPost = data['repost_to'] != null ? SnPost.fromJson(data['repost_to']) : null;
|
repostingPost = data['repost_to'] != null ? SnPost.fromJson(data['repost_to']) : null;
|
||||||
|
poll = data['poll'] != null ? SnPoll.fromJson(data['poll']) : null;
|
||||||
temporaryRestored = true;
|
temporaryRestored = true;
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
});
|
});
|
||||||
@ -510,6 +516,7 @@ class PostWriteController extends ChangeNotifier {
|
|||||||
if (repostingPost != null) 'repost_to': repostingPost!.id,
|
if (repostingPost != null) 'repost_to': repostingPost!.id,
|
||||||
if (reward != null) 'reward': reward,
|
if (reward != null) 'reward': reward,
|
||||||
if (videoAttachment != null) 'video': videoAttachment!.rid,
|
if (videoAttachment != null) 'video': videoAttachment!.rid,
|
||||||
|
if (poll != null) 'poll': poll!.id,
|
||||||
},
|
},
|
||||||
onSendProgress: (count, total) {
|
onSendProgress: (count, total) {
|
||||||
progress = baseProgressVal + (count / total) * (kPostingProgressWeight / 2);
|
progress = baseProgressVal + (count / total) * (kPostingProgressWeight / 2);
|
||||||
@ -641,6 +648,11 @@ class PostWriteController extends ChangeNotifier {
|
|||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void setPoll(SnPoll? value) {
|
||||||
|
poll = value;
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
|
||||||
void reset() {
|
void reset() {
|
||||||
publishedAt = null;
|
publishedAt = null;
|
||||||
publishedUntil = null;
|
publishedUntil = null;
|
||||||
|
@ -3,6 +3,7 @@ import 'package:provider/provider.dart';
|
|||||||
import 'package:surface/providers/sn_attachment.dart';
|
import 'package:surface/providers/sn_attachment.dart';
|
||||||
import 'package:surface/providers/sn_network.dart';
|
import 'package:surface/providers/sn_network.dart';
|
||||||
import 'package:surface/providers/user_directory.dart';
|
import 'package:surface/providers/user_directory.dart';
|
||||||
|
import 'package:surface/types/poll.dart';
|
||||||
import 'package:surface/types/post.dart';
|
import 'package:surface/types/post.dart';
|
||||||
|
|
||||||
class SnPostContentProvider {
|
class SnPostContentProvider {
|
||||||
@ -16,6 +17,11 @@ class SnPostContentProvider {
|
|||||||
_attach = context.read<SnAttachmentProvider>();
|
_attach = context.read<SnAttachmentProvider>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<SnPoll> _fetchPoll(int id) async {
|
||||||
|
final resp = await _sn.client.get('/cgi/co/polls/$id');
|
||||||
|
return SnPoll.fromJson(resp.data);
|
||||||
|
}
|
||||||
|
|
||||||
Future<List<SnPost>> _preloadRelatedDataInBatch(List<SnPost> out) async {
|
Future<List<SnPost>> _preloadRelatedDataInBatch(List<SnPost> out) async {
|
||||||
Set<String> rids = {};
|
Set<String> rids = {};
|
||||||
for (var i = 0; i < out.length; i++) {
|
for (var i = 0; i < out.length; i++) {
|
||||||
@ -35,11 +41,17 @@ class SnPostContentProvider {
|
|||||||
|
|
||||||
final attachments = await _attach.getMultiple(rids.toList());
|
final attachments = await _attach.getMultiple(rids.toList());
|
||||||
for (var i = 0; i < out.length; i++) {
|
for (var i = 0; i < out.length; i++) {
|
||||||
|
SnPoll? poll;
|
||||||
|
if (out[i].pollId != null) {
|
||||||
|
poll = await _fetchPoll(out[i].pollId!);
|
||||||
|
}
|
||||||
|
|
||||||
out[i] = out[i].copyWith(
|
out[i] = out[i].copyWith(
|
||||||
preload: SnPostPreload(
|
preload: SnPostPreload(
|
||||||
thumbnail: attachments.where((ele) => ele?.rid == out[i].body['thumbnail']).firstOrNull,
|
thumbnail: attachments.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,
|
video: attachments.where((ele) => ele?.rid == out[i].body['video']).firstOrNull,
|
||||||
|
poll: poll,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -67,11 +79,18 @@ class SnPostContentProvider {
|
|||||||
}
|
}
|
||||||
|
|
||||||
final attachments = await _attach.getMultiple(rids.toList());
|
final attachments = await _attach.getMultiple(rids.toList());
|
||||||
|
|
||||||
|
SnPoll? poll;
|
||||||
|
if (out.pollId != null) {
|
||||||
|
poll = await _fetchPoll(out.pollId!);
|
||||||
|
}
|
||||||
|
|
||||||
out = out.copyWith(
|
out = out.copyWith(
|
||||||
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,
|
video: attachments.where((ele) => ele?.rid == out.body['video']).firstOrNull,
|
||||||
|
poll: poll,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -42,22 +42,22 @@ class WebSocketProvider extends ChangeNotifier {
|
|||||||
_connectCompleter = null;
|
_connectCompleter = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
_connectCompleter = Completer<void>();
|
|
||||||
|
|
||||||
if (!_ua.isAuthorized) return;
|
if (!_ua.isAuthorized) return;
|
||||||
if (isConnected || conn != null) {
|
if (isConnected || conn != null) {
|
||||||
disconnect();
|
disconnect();
|
||||||
}
|
}
|
||||||
|
|
||||||
final atk = await _sn.getFreshAtk();
|
|
||||||
final uri = Uri.parse(
|
|
||||||
'${_sn.client.options.baseUrl.replaceFirst('http', 'ws')}/ws?tk=$atk',
|
|
||||||
);
|
|
||||||
|
|
||||||
isBusy = true;
|
|
||||||
notifyListeners();
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
_connectCompleter = Completer<void>();
|
||||||
|
|
||||||
|
final atk = await _sn.getFreshAtk();
|
||||||
|
final uri = Uri.parse(
|
||||||
|
'${_sn.client.options.baseUrl.replaceFirst('http', 'ws')}/ws?tk=$atk',
|
||||||
|
);
|
||||||
|
|
||||||
|
isBusy = true;
|
||||||
|
notifyListeners();
|
||||||
|
|
||||||
conn = WebSocketChannel.connect(uri);
|
conn = WebSocketChannel.connect(uri);
|
||||||
await conn!.ready;
|
await conn!.ready;
|
||||||
_wsStream = conn!.stream.asBroadcastStream();
|
_wsStream = conn!.stream.asBroadcastStream();
|
||||||
@ -82,6 +82,7 @@ class WebSocketProvider extends ChangeNotifier {
|
|||||||
isBusy = false;
|
isBusy = false;
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
_connectCompleter!.complete();
|
_connectCompleter!.complete();
|
||||||
|
_connectCompleter = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -31,6 +31,7 @@ import 'package:surface/screens/post/post_search.dart';
|
|||||||
import 'package:surface/screens/realm.dart';
|
import 'package:surface/screens/realm.dart';
|
||||||
import 'package:surface/screens/realm/manage.dart';
|
import 'package:surface/screens/realm/manage.dart';
|
||||||
import 'package:surface/screens/realm/realm_detail.dart';
|
import 'package:surface/screens/realm/realm_detail.dart';
|
||||||
|
import 'package:surface/screens/realm/realm_discovery.dart';
|
||||||
import 'package:surface/screens/settings.dart';
|
import 'package:surface/screens/settings.dart';
|
||||||
import 'package:surface/screens/sharing.dart';
|
import 'package:surface/screens/sharing.dart';
|
||||||
import 'package:surface/screens/wallet.dart';
|
import 'package:surface/screens/wallet.dart';
|
||||||
@ -192,11 +193,6 @@ final _appRoutes = [
|
|||||||
child: const RealmScreen(),
|
child: const RealmScreen(),
|
||||||
),
|
),
|
||||||
routes: [
|
routes: [
|
||||||
GoRoute(
|
|
||||||
path: '/:alias',
|
|
||||||
name: 'realmDetail',
|
|
||||||
builder: (context, state) => RealmDetailScreen(alias: state.pathParameters['alias']!),
|
|
||||||
),
|
|
||||||
GoRoute(
|
GoRoute(
|
||||||
path: '/manage',
|
path: '/manage',
|
||||||
name: 'realmManage',
|
name: 'realmManage',
|
||||||
@ -204,6 +200,16 @@ final _appRoutes = [
|
|||||||
editingRealmAlias: state.uri.queryParameters['editing'],
|
editingRealmAlias: state.uri.queryParameters['editing'],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
GoRoute(
|
||||||
|
path: '/discovery',
|
||||||
|
name: 'realmDiscovery',
|
||||||
|
builder: (context, state) => const RealmDiscoveryScreen(),
|
||||||
|
),
|
||||||
|
GoRoute(
|
||||||
|
path: '/:alias',
|
||||||
|
name: 'realmDetail',
|
||||||
|
builder: (context, state) => RealmDetailScreen(alias: state.pathParameters['alias']!),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
GoRoute(path: '/news', name: 'news', builder: (context, state) => const NewsScreen(), routes: [
|
GoRoute(path: '/news', name: 'news', builder: (context, state) => const NewsScreen(), routes: [
|
||||||
|
@ -178,6 +178,10 @@ class _AccountPublisherEditScreenState extends State<AccountPublisherEditScreen>
|
|||||||
final sn = context.read<SnNetworkProvider>();
|
final sn = context.read<SnNetworkProvider>();
|
||||||
|
|
||||||
return AppScaffold(
|
return AppScaffold(
|
||||||
|
appBar: AppBar(
|
||||||
|
leading: PageBackButton(),
|
||||||
|
title: Text('screenAccountPublisherEdit').tr(),
|
||||||
|
),
|
||||||
body: SingleChildScrollView(
|
body: SingleChildScrollView(
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import 'package:collection/collection.dart';
|
||||||
import 'package:dio/dio.dart';
|
import 'package:dio/dio.dart';
|
||||||
import 'package:dropdown_button2/dropdown_button2.dart';
|
import 'package:dropdown_button2/dropdown_button2.dart';
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
@ -17,6 +18,7 @@ import 'package:uuid/uuid.dart';
|
|||||||
|
|
||||||
class ChatManageScreen extends StatefulWidget {
|
class ChatManageScreen extends StatefulWidget {
|
||||||
final String? editingChannelAlias;
|
final String? editingChannelAlias;
|
||||||
|
|
||||||
const ChatManageScreen({super.key, this.editingChannelAlias});
|
const ChatManageScreen({super.key, this.editingChannelAlias});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -33,6 +35,8 @@ class _ChatManageScreenState extends State<ChatManageScreen> {
|
|||||||
List<SnRealm>? _realms;
|
List<SnRealm>? _realms;
|
||||||
SnRealm? _belongToRealm;
|
SnRealm? _belongToRealm;
|
||||||
|
|
||||||
|
SnChannel? _editingChannel;
|
||||||
|
|
||||||
Future<void> _fetchRealms() async {
|
Future<void> _fetchRealms() async {
|
||||||
setState(() => _isBusy = true);
|
setState(() => _isBusy = true);
|
||||||
try {
|
try {
|
||||||
@ -41,6 +45,9 @@ class _ChatManageScreenState extends State<ChatManageScreen> {
|
|||||||
_realms = List<SnRealm>.from(
|
_realms = List<SnRealm>.from(
|
||||||
resp.data?.map((e) => SnRealm.fromJson(e)) ?? [],
|
resp.data?.map((e) => SnRealm.fromJson(e)) ?? [],
|
||||||
);
|
);
|
||||||
|
if (_editingChannel != null) {
|
||||||
|
_belongToRealm = _realms?.firstWhereOrNull((e) => e.id == _editingChannel!.realmId);
|
||||||
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (mounted) context.showErrorDialog(err);
|
if (mounted) context.showErrorDialog(err);
|
||||||
} finally {
|
} finally {
|
||||||
@ -48,8 +55,6 @@ class _ChatManageScreenState extends State<ChatManageScreen> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
SnChannel? _editingChannel;
|
|
||||||
|
|
||||||
Future<void> _fetchChannel() async {
|
Future<void> _fetchChannel() async {
|
||||||
setState(() => _isBusy = true);
|
setState(() => _isBusy = true);
|
||||||
|
|
||||||
@ -124,9 +129,7 @@ class _ChatManageScreenState extends State<ChatManageScreen> {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return AppScaffold(
|
return AppScaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
title: widget.editingChannelAlias != null
|
title: widget.editingChannelAlias != null ? Text('screenChatManage').tr() : Text('screenChatNew').tr(),
|
||||||
? Text('screenChatManage').tr()
|
|
||||||
: Text('screenChatNew').tr(),
|
|
||||||
),
|
),
|
||||||
body: SingleChildScrollView(
|
body: SingleChildScrollView(
|
||||||
child: Column(
|
child: Column(
|
||||||
@ -138,8 +141,7 @@ class _ChatManageScreenState extends State<ChatManageScreen> {
|
|||||||
leadingPadding: const EdgeInsets.only(left: 10, right: 20),
|
leadingPadding: const EdgeInsets.only(left: 10, right: 20),
|
||||||
dividerColor: Colors.transparent,
|
dividerColor: Colors.transparent,
|
||||||
content: Text(
|
content: Text(
|
||||||
'channelEditingNotice'
|
'channelEditingNotice'.tr(args: ['#${_editingChannel!.alias}']),
|
||||||
.tr(args: ['#${_editingChannel!.alias}']),
|
|
||||||
),
|
),
|
||||||
actions: [
|
actions: [
|
||||||
TextButton(
|
TextButton(
|
||||||
@ -162,6 +164,7 @@ class _ChatManageScreenState extends State<ChatManageScreen> {
|
|||||||
items: [
|
items: [
|
||||||
...(_realms?.map(
|
...(_realms?.map(
|
||||||
(SnRealm item) => DropdownMenuItem<SnRealm>(
|
(SnRealm item) => DropdownMenuItem<SnRealm>(
|
||||||
|
enabled: _editingChannel == null || _editingChannel?.realmId == item.id,
|
||||||
value: item,
|
value: item,
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
@ -179,15 +182,12 @@ class _ChatManageScreenState extends State<ChatManageScreen> {
|
|||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Text(item.name).textStyle(Theme.of(context)
|
Text(item.name).textStyle(Theme.of(context).textTheme.bodyMedium!),
|
||||||
.textTheme
|
|
||||||
.bodyMedium!),
|
|
||||||
Text(
|
Text(
|
||||||
item.description,
|
item.description,
|
||||||
maxLines: 1,
|
maxLines: 1,
|
||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
).textStyle(
|
).textStyle(Theme.of(context).textTheme.bodySmall!),
|
||||||
Theme.of(context).textTheme.bodySmall!),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -197,14 +197,14 @@ class _ChatManageScreenState extends State<ChatManageScreen> {
|
|||||||
) ??
|
) ??
|
||||||
[]),
|
[]),
|
||||||
DropdownMenuItem<SnRealm>(
|
DropdownMenuItem<SnRealm>(
|
||||||
|
enabled: _editingChannel == null,
|
||||||
value: null,
|
value: null,
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
CircleAvatar(
|
CircleAvatar(
|
||||||
radius: 16,
|
radius: 16,
|
||||||
backgroundColor: Colors.transparent,
|
backgroundColor: Colors.transparent,
|
||||||
foregroundColor:
|
foregroundColor: Theme.of(context).colorScheme.onSurface,
|
||||||
Theme.of(context).colorScheme.onSurface,
|
|
||||||
child: const Icon(Symbols.clear),
|
child: const Icon(Symbols.clear),
|
||||||
),
|
),
|
||||||
const Gap(12),
|
const Gap(12),
|
||||||
@ -213,9 +213,7 @@ class _ChatManageScreenState extends State<ChatManageScreen> {
|
|||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Text('fieldChatBelongToRealmUnset')
|
Text('fieldChatBelongToRealmUnset').tr().textStyle(
|
||||||
.tr()
|
|
||||||
.textStyle(
|
|
||||||
Theme.of(context).textTheme.bodyMedium!,
|
Theme.of(context).textTheme.bodyMedium!,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@ -231,10 +229,10 @@ class _ChatManageScreenState extends State<ChatManageScreen> {
|
|||||||
},
|
},
|
||||||
buttonStyleData: const ButtonStyleData(
|
buttonStyleData: const ButtonStyleData(
|
||||||
padding: EdgeInsets.only(right: 16),
|
padding: EdgeInsets.only(right: 16),
|
||||||
height: 60,
|
height: 48,
|
||||||
),
|
),
|
||||||
menuItemStyleData: const MenuItemStyleData(
|
menuItemStyleData: const MenuItemStyleData(
|
||||||
height: 60,
|
height: 48,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -250,8 +248,7 @@ class _ChatManageScreenState extends State<ChatManageScreen> {
|
|||||||
helperText: 'fieldChatAliasHint'.tr(),
|
helperText: 'fieldChatAliasHint'.tr(),
|
||||||
helperMaxLines: 2,
|
helperMaxLines: 2,
|
||||||
),
|
),
|
||||||
onTapOutside: (_) =>
|
onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(),
|
||||||
FocusManager.instance.primaryFocus?.unfocus(),
|
|
||||||
),
|
),
|
||||||
const Gap(4),
|
const Gap(4),
|
||||||
TextField(
|
TextField(
|
||||||
@ -260,8 +257,7 @@ class _ChatManageScreenState extends State<ChatManageScreen> {
|
|||||||
border: const UnderlineInputBorder(),
|
border: const UnderlineInputBorder(),
|
||||||
labelText: 'fieldChatName'.tr(),
|
labelText: 'fieldChatName'.tr(),
|
||||||
),
|
),
|
||||||
onTapOutside: (_) =>
|
onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(),
|
||||||
FocusManager.instance.primaryFocus?.unfocus(),
|
|
||||||
),
|
),
|
||||||
const Gap(4),
|
const Gap(4),
|
||||||
TextField(
|
TextField(
|
||||||
@ -272,8 +268,7 @@ class _ChatManageScreenState extends State<ChatManageScreen> {
|
|||||||
border: const UnderlineInputBorder(),
|
border: const UnderlineInputBorder(),
|
||||||
labelText: 'fieldChatDescription'.tr(),
|
labelText: 'fieldChatDescription'.tr(),
|
||||||
),
|
),
|
||||||
onTapOutside: (_) =>
|
onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(),
|
||||||
FocusManager.instance.primaryFocus?.unfocus(),
|
|
||||||
),
|
),
|
||||||
const Gap(12),
|
const Gap(12),
|
||||||
Row(
|
Row(
|
||||||
|
@ -261,17 +261,15 @@ class _ExploreScreenState extends State<ExploreScreen> {
|
|||||||
hasReachedMax: _postCount != null && _posts.length >= _postCount!,
|
hasReachedMax: _postCount != null && _posts.length >= _postCount!,
|
||||||
onFetchData: _fetchPosts,
|
onFetchData: _fetchPosts,
|
||||||
itemBuilder: (context, idx) {
|
itemBuilder: (context, idx) {
|
||||||
return Center(
|
return OpenablePostItem(
|
||||||
child: OpenablePostItem(
|
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();
|
},
|
||||||
},
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
separatorBuilder: (_, __) => const Gap(8),
|
separatorBuilder: (_, __) => const Gap(8),
|
||||||
|
@ -6,7 +6,6 @@ import 'package:gap/gap.dart';
|
|||||||
import 'package:go_router/go_router.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:responsive_framework/responsive_framework.dart';
|
|
||||||
import 'package:styled_widget/styled_widget.dart';
|
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';
|
||||||
@ -17,7 +16,6 @@ import 'package:surface/widgets/navigation/app_background.dart';
|
|||||||
import 'package:surface/widgets/navigation/app_scaffold.dart';
|
import 'package:surface/widgets/navigation/app_scaffold.dart';
|
||||||
import 'package:surface/widgets/post/post_comment_list.dart';
|
import 'package:surface/widgets/post/post_comment_list.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';
|
|
||||||
|
|
||||||
class PostDetailScreen extends StatefulWidget {
|
class PostDetailScreen extends StatefulWidget {
|
||||||
final String slug;
|
final String slug;
|
||||||
@ -64,7 +62,8 @@ class _PostDetailScreenState extends State<PostDetailScreen> {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final ua = context.watch<UserProvider>();
|
final ua = context.watch<UserProvider>();
|
||||||
final devicePixelRatio = MediaQuery.of(context).devicePixelRatio;
|
|
||||||
|
final double maxWidth = _data?.type == 'video' ? double.infinity : 640;
|
||||||
|
|
||||||
return AppBackground(
|
return AppBackground(
|
||||||
isRoot: widget.onBack != null,
|
isRoot: widget.onBack != null,
|
||||||
@ -114,7 +113,7 @@ class _PostDetailScreenState extends State<PostDetailScreen> {
|
|||||||
SliverToBoxAdapter(
|
SliverToBoxAdapter(
|
||||||
child: PostItem(
|
child: PostItem(
|
||||||
data: _data!,
|
data: _data!,
|
||||||
maxWidth: 640,
|
maxWidth: maxWidth,
|
||||||
showComments: false,
|
showComments: false,
|
||||||
showFullPost: true,
|
showFullPost: true,
|
||||||
onChanged: (data) {
|
onChanged: (data) {
|
||||||
@ -125,11 +124,11 @@ class _PostDetailScreenState extends State<PostDetailScreen> {
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SliverToBoxAdapter(child: Divider(height: 1)),
|
if (_data != null && _data!.type != 'video') const SliverToBoxAdapter(child: Divider(height: 1)),
|
||||||
if (_data != null)
|
if (_data != null && _data!.type != 'video')
|
||||||
SliverToBoxAdapter(
|
SliverToBoxAdapter(
|
||||||
child: Container(
|
child: Container(
|
||||||
constraints: const BoxConstraints(maxWidth: 640),
|
constraints: BoxConstraints(maxWidth: maxWidth),
|
||||||
child: Row(
|
child: Row(
|
||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
@ -142,51 +141,30 @@ class _PostDetailScreenState extends State<PostDetailScreen> {
|
|||||||
).padding(horizontal: 20, vertical: 12).center(),
|
).padding(horizontal: 20, vertical: 12).center(),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (_data != null && ua.isAuthorized)
|
if (_data != null && ua.isAuthorized && _data!.type != 'video')
|
||||||
SliverToBoxAdapter(
|
SliverToBoxAdapter(
|
||||||
child: Container(
|
child: PostCommentQuickAction(
|
||||||
height: 240,
|
parentPost: _data!,
|
||||||
constraints: const BoxConstraints(maxWidth: 640),
|
maxWidth: maxWidth,
|
||||||
margin:
|
onPosted: () {
|
||||||
ResponsiveBreakpoints.of(context).largerThan(MOBILE) ? const EdgeInsets.all(8) : EdgeInsets.zero,
|
setState(() {
|
||||||
decoration: BoxDecoration(
|
_data = _data!.copyWith(
|
||||||
borderRadius: ResponsiveBreakpoints.of(context).largerThan(MOBILE)
|
metric: _data!.metric.copyWith(
|
||||||
? const BorderRadius.all(Radius.circular(8))
|
replyCount: _data!.metric.replyCount + 1,
|
||||||
: BorderRadius.zero,
|
),
|
||||||
border: ResponsiveBreakpoints.of(context).largerThan(MOBILE)
|
);
|
||||||
? Border.all(
|
});
|
||||||
color: Theme.of(context).dividerColor,
|
_childListKey.currentState!.refresh();
|
||||||
width: 1 / devicePixelRatio,
|
},
|
||||||
)
|
),
|
||||||
: Border.symmetric(
|
|
||||||
horizontal: BorderSide(
|
|
||||||
color: Theme.of(context).dividerColor,
|
|
||||||
width: 1 / devicePixelRatio,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
child: PostMiniEditor(
|
|
||||||
postReplyId: _data!.id,
|
|
||||||
onPost: () {
|
|
||||||
setState(() {
|
|
||||||
_data = _data!.copyWith(
|
|
||||||
metric: _data!.metric.copyWith(
|
|
||||||
replyCount: _data!.metric.replyCount + 1,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
_childListKey.currentState!.refresh();
|
|
||||||
},
|
|
||||||
),
|
|
||||||
).center(),
|
|
||||||
),
|
),
|
||||||
if (_data != null)
|
if (_data != null && _data!.type != 'video')
|
||||||
PostCommentSliverList(
|
PostCommentSliverList(
|
||||||
key: _childListKey,
|
key: _childListKey,
|
||||||
parentPost: _data!,
|
parentPost: _data!,
|
||||||
maxWidth: 640,
|
maxWidth: maxWidth,
|
||||||
),
|
),
|
||||||
SliverGap(math.max(MediaQuery.of(context).padding.bottom, 16)),
|
if (_data != null && _data!.type == 'video') SliverGap(math.max(MediaQuery.of(context).padding.bottom, 16)),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -6,7 +6,9 @@ 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:flutter_context_menu/flutter_context_menu.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';
|
||||||
@ -14,11 +16,15 @@ 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_attachment.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/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_input.dart';
|
||||||
import 'package:surface/widgets/attachment/attachment_item.dart';
|
import 'package:surface/widgets/attachment/attachment_item.dart';
|
||||||
|
import 'package:surface/widgets/attachment/pending_attachment_alt.dart';
|
||||||
|
import 'package:surface/widgets/attachment/pending_attachment_boost.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/markdown_content.dart';
|
||||||
import 'package:surface/widgets/navigation/app_scaffold.dart';
|
import 'package:surface/widgets/navigation/app_scaffold.dart';
|
||||||
@ -26,10 +32,9 @@ 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:surface/widgets/post/post_poll_editor.dart';
|
||||||
import 'package:uuid/uuid.dart';
|
import 'package:uuid/uuid.dart';
|
||||||
|
|
||||||
import '../../widgets/attachment/attachment_input.dart';
|
|
||||||
|
|
||||||
class PostEditorExtra {
|
class PostEditorExtra {
|
||||||
final String? text;
|
final String? text;
|
||||||
final String? title;
|
final String? title;
|
||||||
@ -86,8 +91,9 @@ class _PostEditorScreenState extends State<PostEditorScreen> {
|
|||||||
resp.data?.map((e) => SnPublisher.fromJson(e)) ?? [],
|
resp.data?.map((e) => SnPublisher.fromJson(e)) ?? [],
|
||||||
);
|
);
|
||||||
final beforeId = config.prefs.getInt('int_last_publisher_id');
|
final beforeId = config.prefs.getInt('int_last_publisher_id');
|
||||||
_writeController
|
_writeController.setPublisher(
|
||||||
.setPublisher(_publishers?.where((ele) => ele.id == beforeId).firstOrNull ?? _publishers?.firstOrNull);
|
_publishers?.where((ele) => ele.id == beforeId).firstOrNull ??
|
||||||
|
_publishers?.firstOrNull);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
context.showErrorDialog(err);
|
context.showErrorDialog(err);
|
||||||
@ -106,7 +112,9 @@ class _PostEditorScreenState extends State<PostEditorScreen> {
|
|||||||
|
|
||||||
final HotKey _pasteHotKey = HotKey(
|
final HotKey _pasteHotKey = HotKey(
|
||||||
key: PhysicalKeyboardKey.keyV,
|
key: PhysicalKeyboardKey.keyV,
|
||||||
modifiers: [Platform.isMacOS ? HotKeyModifier.meta : HotKeyModifier.control],
|
modifiers: [
|
||||||
|
Platform.isMacOS ? HotKeyModifier.meta : HotKeyModifier.control
|
||||||
|
],
|
||||||
scope: HotKeyScope.inapp,
|
scope: HotKeyScope.inapp,
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -136,10 +144,28 @@ class _PostEditorScreenState extends State<PostEditorScreen> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _showPollEditorDialog() async {
|
||||||
|
final poll = await showDialog<dynamic>(
|
||||||
|
context: context,
|
||||||
|
builder: (context) => PollEditorDialog(
|
||||||
|
poll: _writeController.poll,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
if (poll == null) return;
|
||||||
|
if (!mounted) return;
|
||||||
|
|
||||||
|
if (poll == false) {
|
||||||
|
_writeController.setPoll(null);
|
||||||
|
} else {
|
||||||
|
_writeController.setPoll(poll);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
_writeController.dispose();
|
_writeController.dispose();
|
||||||
if (!kIsWeb && !(Platform.isAndroid || Platform.isIOS)) hotKeyManager.unregister(_pasteHotKey);
|
if (!kIsWeb && !(Platform.isAndroid || Platform.isIOS))
|
||||||
|
hotKeyManager.unregister(_pasteHotKey);
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -163,7 +189,8 @@ class _PostEditorScreenState extends State<PostEditorScreen> {
|
|||||||
if (widget.extraProps != null) {
|
if (widget.extraProps != null) {
|
||||||
_writeController.contentController.text = widget.extraProps!.text ?? '';
|
_writeController.contentController.text = widget.extraProps!.text ?? '';
|
||||||
_writeController.titleController.text = widget.extraProps!.title ?? '';
|
_writeController.titleController.text = widget.extraProps!.title ?? '';
|
||||||
_writeController.descriptionController.text = widget.extraProps!.description ?? '';
|
_writeController.descriptionController.text =
|
||||||
|
widget.extraProps!.description ?? '';
|
||||||
_writeController.addAttachments(widget.extraProps!.attachments ?? []);
|
_writeController.addAttachments(widget.extraProps!.attachments ?? []);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -184,7 +211,9 @@ class _PostEditorScreenState extends State<PostEditorScreen> {
|
|||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
text: TextSpan(children: [
|
text: TextSpan(children: [
|
||||||
TextSpan(
|
TextSpan(
|
||||||
text: _writeController.title.isNotEmpty ? _writeController.title : 'untitled'.tr(),
|
text: _writeController.title.isNotEmpty
|
||||||
|
? _writeController.title
|
||||||
|
: 'untitled'.tr(),
|
||||||
style: Theme.of(context).textTheme.titleLarge!.copyWith(
|
style: Theme.of(context).textTheme.titleLarge!.copyWith(
|
||||||
color: Theme.of(context).appBarTheme.foregroundColor!,
|
color: Theme.of(context).appBarTheme.foregroundColor!,
|
||||||
),
|
),
|
||||||
@ -211,7 +240,8 @@ class _PostEditorScreenState extends State<PostEditorScreen> {
|
|||||||
children: [
|
children: [
|
||||||
if (_writeController.editingPost != null)
|
if (_writeController.editingPost != null)
|
||||||
Container(
|
Container(
|
||||||
padding: const EdgeInsets.only(top: 4, bottom: 4, left: 20, right: 20),
|
padding: const EdgeInsets.only(
|
||||||
|
top: 4, bottom: 4, left: 20, right: 20),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
border: Border(
|
border: Border(
|
||||||
bottom: BorderSide(
|
bottom: BorderSide(
|
||||||
@ -225,7 +255,9 @@ class _PostEditorScreenState extends State<PostEditorScreen> {
|
|||||||
children: [
|
children: [
|
||||||
const Icon(Icons.edit, size: 16),
|
const Icon(Icons.edit, size: 16),
|
||||||
const Gap(10),
|
const Gap(10),
|
||||||
Text('postEditingNotice').tr(args: ['@${_writeController.editingPost!.publisher.name}']),
|
Text('postEditingNotice').tr(args: [
|
||||||
|
'@${_writeController.editingPost!.publisher.name}'
|
||||||
|
]),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -234,7 +266,7 @@ class _PostEditorScreenState extends State<PostEditorScreen> {
|
|||||||
children: [
|
children: [
|
||||||
SingleChildScrollView(
|
SingleChildScrollView(
|
||||||
padding: EdgeInsets.only(bottom: 160),
|
padding: EdgeInsets.only(bottom: 160),
|
||||||
child: switch (_writeController.mode) {
|
child: StyledWidget(switch (_writeController.mode) {
|
||||||
'stories' => _PostStoryEditor(
|
'stories' => _PostStoryEditor(
|
||||||
controller: _writeController,
|
controller: _writeController,
|
||||||
onTapPublisher: _showPublisherPopup,
|
onTapPublisher: _showPublisherPopup,
|
||||||
@ -252,9 +284,11 @@ class _PostEditorScreenState extends State<PostEditorScreen> {
|
|||||||
onTapPublisher: _showPublisherPopup,
|
onTapPublisher: _showPublisherPopup,
|
||||||
),
|
),
|
||||||
_ => const Placeholder(),
|
_ => const Placeholder(),
|
||||||
},
|
})
|
||||||
|
.padding(top: 8),
|
||||||
),
|
),
|
||||||
if (_writeController.attachments.isNotEmpty || _writeController.thumbnail != null)
|
if (_writeController.attachments.isNotEmpty ||
|
||||||
|
_writeController.thumbnail != null)
|
||||||
Positioned(
|
Positioned(
|
||||||
bottom: 0,
|
bottom: 0,
|
||||||
left: 0,
|
left: 0,
|
||||||
@ -264,7 +298,8 @@ class _PostEditorScreenState extends State<PostEditorScreen> {
|
|||||||
attachments: _writeController.attachments,
|
attachments: _writeController.attachments,
|
||||||
isBusy: _writeController.isBusy,
|
isBusy: _writeController.isBusy,
|
||||||
onUpload: (int idx) async {
|
onUpload: (int idx) async {
|
||||||
await _writeController.uploadSingleAttachment(context, idx);
|
await _writeController.uploadSingleAttachment(
|
||||||
|
context, idx);
|
||||||
},
|
},
|
||||||
onPostSetThumbnail: (int? idx) {
|
onPostSetThumbnail: (int? idx) {
|
||||||
_writeController.setThumbnail(idx);
|
_writeController.setThumbnail(idx);
|
||||||
@ -273,10 +308,12 @@ class _PostEditorScreenState extends State<PostEditorScreen> {
|
|||||||
_writeController.contentController.text +=
|
_writeController.contentController.text +=
|
||||||
'\n';
|
'\n';
|
||||||
},
|
},
|
||||||
onUpdate: (int idx, PostWriteMedia updatedMedia) async {
|
onUpdate:
|
||||||
|
(int idx, PostWriteMedia updatedMedia) async {
|
||||||
_writeController.setIsBusy(true);
|
_writeController.setIsBusy(true);
|
||||||
try {
|
try {
|
||||||
_writeController.setAttachmentAt(idx, updatedMedia);
|
_writeController.setAttachmentAt(
|
||||||
|
idx, updatedMedia);
|
||||||
} finally {
|
} finally {
|
||||||
_writeController.setIsBusy(false);
|
_writeController.setIsBusy(false);
|
||||||
}
|
}
|
||||||
@ -289,7 +326,8 @@ class _PostEditorScreenState extends State<PostEditorScreen> {
|
|||||||
_writeController.setIsBusy(false);
|
_writeController.setIsBusy(false);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onUpdateBusy: (state) => _writeController.setIsBusy(state),
|
onUpdateBusy: (state) =>
|
||||||
|
_writeController.setIsBusy(state),
|
||||||
).padding(bottom: 8),
|
).padding(bottom: 8),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@ -300,24 +338,29 @@ class _PostEditorScreenState extends State<PostEditorScreen> {
|
|||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
LoadingIndicator(isActive: _isLoading),
|
if (_writeController.isBusy &&
|
||||||
if (_writeController.isBusy && _writeController.progress != null)
|
_writeController.progress != null)
|
||||||
TweenAnimationBuilder<double>(
|
TweenAnimationBuilder<double>(
|
||||||
tween: Tween(begin: 0, end: _writeController.progress),
|
tween: Tween(begin: 0, end: _writeController.progress),
|
||||||
duration: Duration(milliseconds: 300),
|
duration: Duration(milliseconds: 300),
|
||||||
builder: (context, value, _) => LinearProgressIndicator(value: value, minHeight: 2),
|
builder: (context, value, _) =>
|
||||||
|
LinearProgressIndicator(value: value, minHeight: 2),
|
||||||
)
|
)
|
||||||
else if (_writeController.isBusy)
|
else if (_writeController.isBusy)
|
||||||
const LinearProgressIndicator(value: null, minHeight: 2),
|
const LinearProgressIndicator(value: null, minHeight: 2),
|
||||||
|
LoadingIndicator(isActive: _isLoading),
|
||||||
|
const Gap(4),
|
||||||
Container(
|
Container(
|
||||||
child: _writeController.temporaryRestored
|
child: _writeController.temporaryRestored
|
||||||
? Container(
|
? Container(
|
||||||
padding: const EdgeInsets.only(top: 4, bottom: 4, left: 28, right: 22),
|
padding: const EdgeInsets.only(
|
||||||
|
top: 4, bottom: 4, left: 28, right: 22),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
border: Border(
|
border: Border(
|
||||||
bottom: BorderSide(
|
bottom: BorderSide(
|
||||||
color: Theme.of(context).dividerColor,
|
color: Theme.of(context).dividerColor,
|
||||||
width: 1 / MediaQuery.of(context).devicePixelRatio,
|
width: 1 /
|
||||||
|
MediaQuery.of(context).devicePixelRatio,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -326,7 +369,9 @@ class _PostEditorScreenState extends State<PostEditorScreen> {
|
|||||||
children: [
|
children: [
|
||||||
const Icon(Icons.restore, size: 20),
|
const Icon(Icons.restore, size: 20),
|
||||||
const Gap(8),
|
const Gap(8),
|
||||||
Expanded(child: Text('postLocalDraftRestored').tr()),
|
Expanded(
|
||||||
|
child:
|
||||||
|
Text('postLocalDraftRestored').tr()),
|
||||||
InkWell(
|
InkWell(
|
||||||
child: Text('dialogDismiss').tr(),
|
child: Text('dialogDismiss').tr(),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
@ -337,8 +382,10 @@ class _PostEditorScreenState extends State<PostEditorScreen> {
|
|||||||
))
|
))
|
||||||
: const SizedBox.shrink(),
|
: const SizedBox.shrink(),
|
||||||
)
|
)
|
||||||
.height(_writeController.temporaryRestored ? 32 : 0, animate: true)
|
.height(_writeController.temporaryRestored ? 32 : 0,
|
||||||
.animate(const Duration(milliseconds: 300), Curves.fastLinearToSlowEaseIn),
|
animate: true)
|
||||||
|
.animate(const Duration(milliseconds: 300),
|
||||||
|
Curves.fastLinearToSlowEaseIn),
|
||||||
Row(
|
Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
children: [
|
children: [
|
||||||
@ -356,13 +403,33 @@ class _PostEditorScreenState extends State<PostEditorScreen> {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
if (_writeController.mode == 'stories')
|
||||||
|
IconButton(
|
||||||
|
icon: Icon(Symbols.poll,
|
||||||
|
color: Theme.of(context)
|
||||||
|
.colorScheme
|
||||||
|
.primary),
|
||||||
|
style: ButtonStyle(
|
||||||
|
backgroundColor:
|
||||||
|
_writeController.poll == null
|
||||||
|
? null
|
||||||
|
: WidgetStatePropertyAll(
|
||||||
|
Theme.of(context)
|
||||||
|
.colorScheme
|
||||||
|
.surfaceContainer),
|
||||||
|
),
|
||||||
|
onPressed: () {
|
||||||
|
_showPollEditorDialog();
|
||||||
|
},
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
TextButton.icon(
|
TextButton.icon(
|
||||||
onPressed: (_writeController.isBusy || _writeController.publisher == null)
|
onPressed: (_writeController.isBusy ||
|
||||||
|
_writeController.publisher == null)
|
||||||
? null
|
? null
|
||||||
: () {
|
: () {
|
||||||
_writeController.sendPost(context).then((_) {
|
_writeController.sendPost(context).then((_) {
|
||||||
@ -378,7 +445,6 @@ class _PostEditorScreenState extends State<PostEditorScreen> {
|
|||||||
],
|
],
|
||||||
).padding(
|
).padding(
|
||||||
bottom: MediaQuery.of(context).padding.bottom + 8,
|
bottom: MediaQuery.of(context).padding.bottom + 8,
|
||||||
top: 4,
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@ -413,9 +479,21 @@ class _PostPublisherPopup extends StatelessWidget {
|
|||||||
children: [
|
children: [
|
||||||
const Icon(Symbols.face, size: 24),
|
const Icon(Symbols.face, size: 24),
|
||||||
const Gap(16),
|
const Gap(16),
|
||||||
Text('accountPublishers', style: Theme.of(context).textTheme.titleLarge).tr(),
|
Text('accountPublishers',
|
||||||
|
style: Theme.of(context).textTheme.titleLarge)
|
||||||
|
.tr(),
|
||||||
],
|
],
|
||||||
).padding(horizontal: 20, top: 16, bottom: 12),
|
).padding(horizontal: 20, top: 16, bottom: 12),
|
||||||
|
ListTile(
|
||||||
|
leading: const Icon(Symbols.add),
|
||||||
|
title: Text('publishersNew').tr(),
|
||||||
|
subtitle: Text('publisherNewSubtitle').tr(),
|
||||||
|
contentPadding: const EdgeInsets.symmetric(horizontal: 24),
|
||||||
|
onTap: () {
|
||||||
|
GoRouter.of(context).pushNamed('accountPublisherNew');
|
||||||
|
},
|
||||||
|
),
|
||||||
|
const Divider(height: 1),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: ListView.builder(
|
child: ListView.builder(
|
||||||
itemCount: publishers?.length ?? 0,
|
itemCount: publishers?.length ?? 0,
|
||||||
@ -475,7 +553,8 @@ class _PostStoryEditor extends StatelessWidget {
|
|||||||
border: InputBorder.none,
|
border: InputBorder.none,
|
||||||
),
|
),
|
||||||
style: Theme.of(context).textTheme.titleLarge,
|
style: Theme.of(context).textTheme.titleLarge,
|
||||||
onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(),
|
onTapOutside: (_) =>
|
||||||
|
FocusManager.instance.primaryFocus?.unfocus(),
|
||||||
).padding(horizontal: 16),
|
).padding(horizontal: 16),
|
||||||
const Gap(8),
|
const Gap(8),
|
||||||
TextField(
|
TextField(
|
||||||
@ -490,7 +569,8 @@ class _PostStoryEditor extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
border: InputBorder.none,
|
border: InputBorder.none,
|
||||||
),
|
),
|
||||||
onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(),
|
onTapOutside: (_) =>
|
||||||
|
FocusManager.instance.primaryFocus?.unfocus(),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@ -580,7 +660,8 @@ class _PostArticleEditor extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
border: InputBorder.none,
|
border: InputBorder.none,
|
||||||
),
|
),
|
||||||
onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(),
|
onTapOutside: (_) =>
|
||||||
|
FocusManager.instance.primaryFocus?.unfocus(),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const Gap(8),
|
const Gap(8),
|
||||||
@ -659,7 +740,8 @@ class _PostQuestionEditor extends StatelessWidget {
|
|||||||
border: InputBorder.none,
|
border: InputBorder.none,
|
||||||
),
|
),
|
||||||
style: Theme.of(context).textTheme.titleLarge,
|
style: Theme.of(context).textTheme.titleLarge,
|
||||||
onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(),
|
onTapOutside: (_) =>
|
||||||
|
FocusManager.instance.primaryFocus?.unfocus(),
|
||||||
).padding(horizontal: 16),
|
).padding(horizontal: 16),
|
||||||
const Gap(8),
|
const Gap(8),
|
||||||
TextField(
|
TextField(
|
||||||
@ -670,7 +752,8 @@ class _PostQuestionEditor extends StatelessWidget {
|
|||||||
border: InputBorder.none,
|
border: InputBorder.none,
|
||||||
isCollapsed: true,
|
isCollapsed: true,
|
||||||
),
|
),
|
||||||
onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(),
|
onTapOutside: (_) =>
|
||||||
|
FocusManager.instance.primaryFocus?.unfocus(),
|
||||||
).padding(horizontal: 16),
|
).padding(horizontal: 16),
|
||||||
const Gap(8),
|
const Gap(8),
|
||||||
TextField(
|
TextField(
|
||||||
@ -685,7 +768,8 @@ class _PostQuestionEditor extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
border: InputBorder.none,
|
border: InputBorder.none,
|
||||||
),
|
),
|
||||||
onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(),
|
onTapOutside: (_) =>
|
||||||
|
FocusManager.instance.primaryFocus?.unfocus(),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@ -716,6 +800,77 @@ class _PostVideoEditor extends StatelessWidget {
|
|||||||
controller.setVideoAttachment(video);
|
controller.setVideoAttachment(video);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _setAlt(BuildContext context) async {
|
||||||
|
if (controller.videoAttachment == null) return;
|
||||||
|
|
||||||
|
final result = await showDialog<SnAttachment?>(
|
||||||
|
context: context,
|
||||||
|
builder: (context) => PendingAttachmentAltDialog(
|
||||||
|
media: PostWriteMedia(controller.videoAttachment)),
|
||||||
|
);
|
||||||
|
if (result == null) return;
|
||||||
|
|
||||||
|
controller.setVideoAttachment(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _createBoost(BuildContext context) async {
|
||||||
|
if (controller.videoAttachment == null) return;
|
||||||
|
|
||||||
|
final result = await showDialog<SnAttachmentBoost?>(
|
||||||
|
context: context,
|
||||||
|
builder: (context) => PendingAttachmentBoostDialog(
|
||||||
|
media: PostWriteMedia(controller.videoAttachment)),
|
||||||
|
);
|
||||||
|
if (result == null) return;
|
||||||
|
|
||||||
|
final newAttach = controller.videoAttachment!.copyWith(
|
||||||
|
boosts: [...controller.videoAttachment!.boosts, result],
|
||||||
|
);
|
||||||
|
|
||||||
|
controller.setVideoAttachment(newAttach);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _setThumbnail(BuildContext context) async {
|
||||||
|
if (controller.videoAttachment == null) return;
|
||||||
|
|
||||||
|
final thumbnail = await showDialog<SnAttachment?>(
|
||||||
|
context: context,
|
||||||
|
builder: (context) => AttachmentInputDialog(
|
||||||
|
title: 'attachmentSetThumbnail'.tr(),
|
||||||
|
pool: 'interactive',
|
||||||
|
analyzeNow: true,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
if (thumbnail == null) return;
|
||||||
|
if (!context.mounted) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
final attach = context.read<SnAttachmentProvider>();
|
||||||
|
final newAttach = await attach.updateOne(
|
||||||
|
controller.videoAttachment!,
|
||||||
|
thumbnailId: thumbnail.id,
|
||||||
|
);
|
||||||
|
controller.setVideoAttachment(newAttach);
|
||||||
|
} catch (err) {
|
||||||
|
if (!context.mounted) return;
|
||||||
|
context.showErrorDialog(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _deleteAttachment(BuildContext context) async {
|
||||||
|
if (controller.videoAttachment == null) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
final sn = context.read<SnNetworkProvider>();
|
||||||
|
await sn.client
|
||||||
|
.delete('/cgi/uc/attachments/${controller.videoAttachment!.id}');
|
||||||
|
controller.setVideoAttachment(null);
|
||||||
|
} catch (err) {
|
||||||
|
if (!context.mounted) return;
|
||||||
|
context.showErrorDialog(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Column(
|
return Column(
|
||||||
@ -772,35 +927,81 @@ class _PostVideoEditor extends StatelessWidget {
|
|||||||
borderRadius: BorderRadius.circular(16),
|
borderRadius: BorderRadius.circular(16),
|
||||||
border: Border.all(color: Theme.of(context).dividerColor),
|
border: Border.all(color: Theme.of(context).dividerColor),
|
||||||
),
|
),
|
||||||
child: InkWell(
|
child: ContextMenuRegion(
|
||||||
borderRadius: BorderRadius.circular(16),
|
contextMenu: ContextMenu(
|
||||||
child: AspectRatio(
|
entries: [
|
||||||
aspectRatio: 16 / 9,
|
MenuItem(
|
||||||
child: controller.videoAttachment == null
|
label: 'attachmentSetAlt'.tr(),
|
||||||
? Center(
|
icon: Symbols.description,
|
||||||
child: Row(
|
onSelected: () {
|
||||||
mainAxisSize: MainAxisSize.min,
|
_setAlt(context);
|
||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
},
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
),
|
||||||
children: [
|
MenuItem(
|
||||||
const Icon(Icons.add),
|
label: 'attachmentBoost'.tr(),
|
||||||
const Gap(4),
|
icon: Symbols.bolt,
|
||||||
Text('postVideoUpload'.tr()),
|
onSelected: () {
|
||||||
],
|
_createBoost(context);
|
||||||
),
|
},
|
||||||
)
|
),
|
||||||
: ClipRRect(
|
MenuItem(
|
||||||
borderRadius: BorderRadius.circular(16),
|
label: 'attachmentSetThumbnail'.tr(),
|
||||||
child: AttachmentItem(
|
icon: Symbols.image,
|
||||||
data: controller.videoAttachment!,
|
onSelected: () {
|
||||||
heroTag: const Uuid().v4(),
|
_setThumbnail(context);
|
||||||
),
|
},
|
||||||
),
|
),
|
||||||
|
MenuItem(
|
||||||
|
label: 'attachmentCopyRandomId'.tr(),
|
||||||
|
icon: Symbols.content_copy,
|
||||||
|
onSelected: () {
|
||||||
|
Clipboard.setData(
|
||||||
|
ClipboardData(text: controller.videoAttachment!.rid));
|
||||||
|
},
|
||||||
|
),
|
||||||
|
MenuItem(
|
||||||
|
label: 'delete'.tr(),
|
||||||
|
icon: Symbols.delete,
|
||||||
|
onSelected: () => _deleteAttachment(context),
|
||||||
|
),
|
||||||
|
MenuItem(
|
||||||
|
label: 'unlink'.tr(),
|
||||||
|
icon: Symbols.link_off,
|
||||||
|
onSelected: () {
|
||||||
|
controller.setVideoAttachment(null);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
child: InkWell(
|
||||||
|
borderRadius: BorderRadius.circular(16),
|
||||||
|
onTap: controller.videoAttachment == null
|
||||||
|
? () => _selectVideo(context)
|
||||||
|
: null,
|
||||||
|
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);
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
@ -100,6 +100,12 @@ class _RealmScreenState extends State<RealmScreen> {
|
|||||||
leading: AutoAppBarLeading(),
|
leading: AutoAppBarLeading(),
|
||||||
title: Text('screenRealm').tr(),
|
title: Text('screenRealm').tr(),
|
||||||
actions: [
|
actions: [
|
||||||
|
IconButton(
|
||||||
|
icon: const Icon(Symbols.globe),
|
||||||
|
onPressed: () {
|
||||||
|
GoRouter.of(context).pushNamed('realmDiscovery');
|
||||||
|
},
|
||||||
|
),
|
||||||
IconButton(
|
IconButton(
|
||||||
icon: !_isCompactView ? const Icon(Symbols.view_list) : const Icon(Symbols.view_module),
|
icon: !_isCompactView ? const Icon(Symbols.view_list) : const Icon(Symbols.view_module),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
|
290
lib/screens/realm/realm_discovery.dart
Normal file
290
lib/screens/realm/realm_discovery.dart
Normal file
@ -0,0 +1,290 @@
|
|||||||
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:gap/gap.dart';
|
||||||
|
import 'package:material_symbols_icons/symbols.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
import 'package:styled_widget/styled_widget.dart';
|
||||||
|
import 'package:surface/providers/sn_network.dart';
|
||||||
|
import 'package:surface/providers/userinfo.dart';
|
||||||
|
import 'package:surface/types/chat.dart';
|
||||||
|
import 'package:surface/types/realm.dart';
|
||||||
|
import 'package:surface/widgets/account/account_image.dart';
|
||||||
|
import 'package:surface/widgets/dialog.dart';
|
||||||
|
import 'package:surface/widgets/loading_indicator.dart';
|
||||||
|
import 'package:surface/widgets/navigation/app_scaffold.dart';
|
||||||
|
import 'package:surface/widgets/universal_image.dart';
|
||||||
|
|
||||||
|
class RealmDiscoveryScreen extends StatefulWidget {
|
||||||
|
const RealmDiscoveryScreen({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<RealmDiscoveryScreen> createState() => _RealmDiscoveryScreenState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _RealmDiscoveryScreenState extends State<RealmDiscoveryScreen> {
|
||||||
|
List<SnRealm>? _realms;
|
||||||
|
bool _isBusy = false;
|
||||||
|
|
||||||
|
Future<void> _fetchRealms() async {
|
||||||
|
try {
|
||||||
|
setState(() => _isBusy = true);
|
||||||
|
final sn = context.read<SnNetworkProvider>();
|
||||||
|
final resp = await sn.client.get('/cgi/id/realms');
|
||||||
|
_realms = List<SnRealm>.from(
|
||||||
|
resp.data?.map((e) => SnRealm.fromJson(e)) ?? [],
|
||||||
|
);
|
||||||
|
} catch (err) {
|
||||||
|
if (mounted) context.showErrorDialog(err);
|
||||||
|
rethrow;
|
||||||
|
} finally {
|
||||||
|
setState(() => _isBusy = false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_fetchRealms();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final sn = context.read<SnNetworkProvider>();
|
||||||
|
|
||||||
|
return AppScaffold(
|
||||||
|
appBar: AppBar(
|
||||||
|
title: Text('screenRealmDiscovery').tr(),
|
||||||
|
),
|
||||||
|
body: Column(
|
||||||
|
children: [
|
||||||
|
LoadingIndicator(isActive: _isBusy),
|
||||||
|
Expanded(
|
||||||
|
child: RefreshIndicator(
|
||||||
|
onRefresh: _fetchRealms,
|
||||||
|
child: ListView.builder(
|
||||||
|
padding: EdgeInsets.zero,
|
||||||
|
itemCount: _realms?.length ?? 0,
|
||||||
|
itemBuilder: (context, idx) {
|
||||||
|
final realm = _realms![idx];
|
||||||
|
return Container(
|
||||||
|
constraints: BoxConstraints(maxWidth: 640),
|
||||||
|
child: Card(
|
||||||
|
margin: const EdgeInsets.all(12),
|
||||||
|
child: InkWell(
|
||||||
|
borderRadius: const BorderRadius.all(Radius.circular(8)),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
AspectRatio(
|
||||||
|
aspectRatio: 16 / 7,
|
||||||
|
child: Stack(
|
||||||
|
clipBehavior: Clip.none,
|
||||||
|
fit: StackFit.expand,
|
||||||
|
children: [
|
||||||
|
ClipRRect(
|
||||||
|
borderRadius: const BorderRadius.all(Radius.circular(8)),
|
||||||
|
child: Container(
|
||||||
|
color: Theme.of(context).colorScheme.surfaceContainer,
|
||||||
|
child: (realm.banner?.isEmpty ?? true)
|
||||||
|
? const SizedBox.shrink()
|
||||||
|
: AutoResizeUniversalImage(
|
||||||
|
sn.getAttachmentUrl(realm.banner!),
|
||||||
|
fit: BoxFit.cover,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Positioned(
|
||||||
|
bottom: -30,
|
||||||
|
left: 18,
|
||||||
|
child: AccountImage(
|
||||||
|
content: realm.avatar,
|
||||||
|
radius: 24,
|
||||||
|
fallbackWidget: const Icon(Symbols.group, size: 24),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const Gap(20 + 12),
|
||||||
|
Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(realm.name).textStyle(Theme.of(context).textTheme.titleMedium!),
|
||||||
|
Text(realm.description).textStyle(Theme.of(context).textTheme.bodySmall!),
|
||||||
|
],
|
||||||
|
).padding(horizontal: 24, bottom: 14),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
onTap: () {
|
||||||
|
showModalBottomSheet(
|
||||||
|
context: context,
|
||||||
|
builder: (context) => _RealmJoinPopup(realm: realm),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
).center();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _RealmJoinPopup extends StatefulWidget {
|
||||||
|
final SnRealm realm;
|
||||||
|
|
||||||
|
const _RealmJoinPopup({required this.realm});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<_RealmJoinPopup> createState() => _RealmJoinPopupState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _RealmJoinPopupState extends State<_RealmJoinPopup> {
|
||||||
|
final List<String> _planJoinChannels = List.empty(growable: true);
|
||||||
|
|
||||||
|
List<SnChannel>? _channels;
|
||||||
|
bool _isBusy = false;
|
||||||
|
bool _isJoining = false;
|
||||||
|
|
||||||
|
Future<void> _fetchPublicChannels() async {
|
||||||
|
try {
|
||||||
|
setState(() => _isBusy = true);
|
||||||
|
final sn = context.read<SnNetworkProvider>();
|
||||||
|
final resp = await sn.client.get('/cgi/im/channels/${widget.realm.alias}');
|
||||||
|
final out = List<SnChannel>.from(
|
||||||
|
resp.data.map((e) => SnChannel.fromJson(e)).cast<SnChannel>(),
|
||||||
|
);
|
||||||
|
setState(() => _channels = out);
|
||||||
|
} catch (err) {
|
||||||
|
if (!mounted) return;
|
||||||
|
context.showErrorDialog(err);
|
||||||
|
} finally {
|
||||||
|
setState(() => _isBusy = false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _joinRealm() async {
|
||||||
|
try {
|
||||||
|
setState(() => _isJoining = true);
|
||||||
|
final sn = context.read<SnNetworkProvider>();
|
||||||
|
final ua = context.read<UserProvider>();
|
||||||
|
await sn.client.post('/cgi/id/realms/${widget.realm.alias}/members', data: {
|
||||||
|
'related': ua.user?.name,
|
||||||
|
});
|
||||||
|
await _joinSelectedChannels();
|
||||||
|
if (!mounted) return;
|
||||||
|
context.showSnackbar('realmJoined'.tr(args: [widget.realm.name]));
|
||||||
|
Navigator.pop(context);
|
||||||
|
} catch (err) {
|
||||||
|
if (!mounted) return;
|
||||||
|
context.showErrorDialog(err);
|
||||||
|
} finally {
|
||||||
|
setState(() => _isJoining = false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _joinSelectedChannels() async {
|
||||||
|
if (_planJoinChannels.isEmpty) return;
|
||||||
|
for (final channel in _planJoinChannels) {
|
||||||
|
try {
|
||||||
|
final sn = context.read<SnNetworkProvider>();
|
||||||
|
final ua = context.read<UserProvider>();
|
||||||
|
await sn.client.post('/cgi/im/channels/${widget.realm.alias}/$channel/members', data: {
|
||||||
|
'related': ua.user?.name,
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
if (!mounted) return;
|
||||||
|
context.showErrorDialog(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_fetchPublicChannels();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
const Icon(Symbols.group_add, size: 24),
|
||||||
|
const Gap(16),
|
||||||
|
Text('realmJoin', style: Theme.of(context).textTheme.titleLarge).tr(),
|
||||||
|
],
|
||||||
|
).padding(horizontal: 20, top: 16, bottom: 12),
|
||||||
|
Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.end,
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
widget.realm.name,
|
||||||
|
style: Theme.of(context).textTheme.titleMedium,
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
widget.realm.description,
|
||||||
|
style: Theme.of(context).textTheme.bodyMedium,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
ElevatedButton(
|
||||||
|
onPressed: _isJoining ? null : () => _joinRealm(),
|
||||||
|
child: Text('join'.tr()),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
).padding(horizontal: 24, bottom: 12),
|
||||||
|
const Divider(height: 1),
|
||||||
|
LoadingIndicator(isActive: _isBusy),
|
||||||
|
Container(
|
||||||
|
width: double.infinity,
|
||||||
|
color: Theme.of(context).colorScheme.surfaceContainerHigh,
|
||||||
|
child: Text('realmCommunityPublicChannelsHint'.tr(), style: Theme.of(context).textTheme.bodyMedium)
|
||||||
|
.padding(horizontal: 24, vertical: 8),
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: ListView.builder(
|
||||||
|
itemCount: _channels?.length ?? 0,
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
final channel = _channels![index];
|
||||||
|
return CheckboxListTile(
|
||||||
|
value: _planJoinChannels.contains(channel.alias),
|
||||||
|
title: Text(channel.name),
|
||||||
|
subtitle: Text(
|
||||||
|
channel.description,
|
||||||
|
maxLines: 1,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
),
|
||||||
|
contentPadding: const EdgeInsets.symmetric(horizontal: 16),
|
||||||
|
secondary: AccountImage(
|
||||||
|
content: null,
|
||||||
|
fallbackWidget: const Icon(Symbols.chat, size: 20),
|
||||||
|
),
|
||||||
|
onChanged: (value) {
|
||||||
|
value ??= false;
|
||||||
|
if (value) {
|
||||||
|
setState(() => _planJoinChannels.add(channel.alias));
|
||||||
|
} else {
|
||||||
|
setState(() => _planJoinChannels.remove(channel.alias));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
45
lib/types/poll.dart
Normal file
45
lib/types/poll.dart
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||||
|
|
||||||
|
part 'poll.freezed.dart';
|
||||||
|
part 'poll.g.dart';
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
class SnPoll with _$SnPoll {
|
||||||
|
const factory SnPoll({
|
||||||
|
required int id,
|
||||||
|
required DateTime createdAt,
|
||||||
|
required DateTime updatedAt,
|
||||||
|
required dynamic deletedAt,
|
||||||
|
required dynamic expiredAt,
|
||||||
|
required List<SnPollOption> options,
|
||||||
|
required int accountId,
|
||||||
|
required SnPollMetric metric,
|
||||||
|
}) = _SnPoll;
|
||||||
|
|
||||||
|
factory SnPoll.fromJson(Map<String, Object?> json) => _$SnPollFromJson(json);
|
||||||
|
}
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
class SnPollMetric with _$SnPollMetric {
|
||||||
|
const factory SnPollMetric({
|
||||||
|
required int totalAnswer,
|
||||||
|
@Default({}) Map<String, int> byOptions,
|
||||||
|
@Default({}) Map<String, double> byOptionsPercentage,
|
||||||
|
}) = _SnPollMetric;
|
||||||
|
|
||||||
|
factory SnPollMetric.fromJson(Map<String, Object?> json) =>
|
||||||
|
_$SnPollMetricFromJson(json);
|
||||||
|
}
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
class SnPollOption with _$SnPollOption {
|
||||||
|
const factory SnPollOption({
|
||||||
|
required String id,
|
||||||
|
required String icon,
|
||||||
|
required String name,
|
||||||
|
required String description,
|
||||||
|
}) = _SnPollOption;
|
||||||
|
|
||||||
|
factory SnPollOption.fromJson(Map<String, Object?> json) =>
|
||||||
|
_$SnPollOptionFromJson(json);
|
||||||
|
}
|
761
lib/types/poll.freezed.dart
Normal file
761
lib/types/poll.freezed.dart
Normal file
@ -0,0 +1,761 @@
|
|||||||
|
// coverage:ignore-file
|
||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
// ignore_for_file: type=lint
|
||||||
|
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
|
||||||
|
|
||||||
|
part of 'poll.dart';
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// FreezedGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
T _$identity<T>(T value) => value;
|
||||||
|
|
||||||
|
final _privateConstructorUsedError = UnsupportedError(
|
||||||
|
'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models');
|
||||||
|
|
||||||
|
SnPoll _$SnPollFromJson(Map<String, dynamic> json) {
|
||||||
|
return _SnPoll.fromJson(json);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
mixin _$SnPoll {
|
||||||
|
int get id => throw _privateConstructorUsedError;
|
||||||
|
DateTime get createdAt => throw _privateConstructorUsedError;
|
||||||
|
DateTime get updatedAt => throw _privateConstructorUsedError;
|
||||||
|
dynamic get deletedAt => throw _privateConstructorUsedError;
|
||||||
|
dynamic get expiredAt => throw _privateConstructorUsedError;
|
||||||
|
List<SnPollOption> get options => throw _privateConstructorUsedError;
|
||||||
|
int get accountId => throw _privateConstructorUsedError;
|
||||||
|
SnPollMetric get metric => throw _privateConstructorUsedError;
|
||||||
|
|
||||||
|
/// Serializes this SnPoll to a JSON map.
|
||||||
|
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
|
||||||
|
|
||||||
|
/// Create a copy of SnPoll
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
$SnPollCopyWith<SnPoll> get copyWith => throw _privateConstructorUsedError;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
abstract class $SnPollCopyWith<$Res> {
|
||||||
|
factory $SnPollCopyWith(SnPoll value, $Res Function(SnPoll) then) =
|
||||||
|
_$SnPollCopyWithImpl<$Res, SnPoll>;
|
||||||
|
@useResult
|
||||||
|
$Res call(
|
||||||
|
{int id,
|
||||||
|
DateTime createdAt,
|
||||||
|
DateTime updatedAt,
|
||||||
|
dynamic deletedAt,
|
||||||
|
dynamic expiredAt,
|
||||||
|
List<SnPollOption> options,
|
||||||
|
int accountId,
|
||||||
|
SnPollMetric metric});
|
||||||
|
|
||||||
|
$SnPollMetricCopyWith<$Res> get metric;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
class _$SnPollCopyWithImpl<$Res, $Val extends SnPoll>
|
||||||
|
implements $SnPollCopyWith<$Res> {
|
||||||
|
_$SnPollCopyWithImpl(this._value, this._then);
|
||||||
|
|
||||||
|
// ignore: unused_field
|
||||||
|
final $Val _value;
|
||||||
|
// ignore: unused_field
|
||||||
|
final $Res Function($Val) _then;
|
||||||
|
|
||||||
|
/// Create a copy of SnPoll
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
@override
|
||||||
|
$Res call({
|
||||||
|
Object? id = null,
|
||||||
|
Object? createdAt = null,
|
||||||
|
Object? updatedAt = null,
|
||||||
|
Object? deletedAt = freezed,
|
||||||
|
Object? expiredAt = freezed,
|
||||||
|
Object? options = null,
|
||||||
|
Object? accountId = null,
|
||||||
|
Object? metric = null,
|
||||||
|
}) {
|
||||||
|
return _then(_value.copyWith(
|
||||||
|
id: null == id
|
||||||
|
? _value.id
|
||||||
|
: id // ignore: cast_nullable_to_non_nullable
|
||||||
|
as int,
|
||||||
|
createdAt: null == createdAt
|
||||||
|
? _value.createdAt
|
||||||
|
: createdAt // ignore: cast_nullable_to_non_nullable
|
||||||
|
as DateTime,
|
||||||
|
updatedAt: null == updatedAt
|
||||||
|
? _value.updatedAt
|
||||||
|
: updatedAt // ignore: cast_nullable_to_non_nullable
|
||||||
|
as DateTime,
|
||||||
|
deletedAt: freezed == deletedAt
|
||||||
|
? _value.deletedAt
|
||||||
|
: deletedAt // ignore: cast_nullable_to_non_nullable
|
||||||
|
as dynamic,
|
||||||
|
expiredAt: freezed == expiredAt
|
||||||
|
? _value.expiredAt
|
||||||
|
: expiredAt // ignore: cast_nullable_to_non_nullable
|
||||||
|
as dynamic,
|
||||||
|
options: null == options
|
||||||
|
? _value.options
|
||||||
|
: options // ignore: cast_nullable_to_non_nullable
|
||||||
|
as List<SnPollOption>,
|
||||||
|
accountId: null == accountId
|
||||||
|
? _value.accountId
|
||||||
|
: accountId // ignore: cast_nullable_to_non_nullable
|
||||||
|
as int,
|
||||||
|
metric: null == metric
|
||||||
|
? _value.metric
|
||||||
|
: metric // ignore: cast_nullable_to_non_nullable
|
||||||
|
as SnPollMetric,
|
||||||
|
) as $Val);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a copy of SnPoll
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@override
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
$SnPollMetricCopyWith<$Res> get metric {
|
||||||
|
return $SnPollMetricCopyWith<$Res>(_value.metric, (value) {
|
||||||
|
return _then(_value.copyWith(metric: value) as $Val);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
abstract class _$$SnPollImplCopyWith<$Res> implements $SnPollCopyWith<$Res> {
|
||||||
|
factory _$$SnPollImplCopyWith(
|
||||||
|
_$SnPollImpl value, $Res Function(_$SnPollImpl) then) =
|
||||||
|
__$$SnPollImplCopyWithImpl<$Res>;
|
||||||
|
@override
|
||||||
|
@useResult
|
||||||
|
$Res call(
|
||||||
|
{int id,
|
||||||
|
DateTime createdAt,
|
||||||
|
DateTime updatedAt,
|
||||||
|
dynamic deletedAt,
|
||||||
|
dynamic expiredAt,
|
||||||
|
List<SnPollOption> options,
|
||||||
|
int accountId,
|
||||||
|
SnPollMetric metric});
|
||||||
|
|
||||||
|
@override
|
||||||
|
$SnPollMetricCopyWith<$Res> get metric;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
class __$$SnPollImplCopyWithImpl<$Res>
|
||||||
|
extends _$SnPollCopyWithImpl<$Res, _$SnPollImpl>
|
||||||
|
implements _$$SnPollImplCopyWith<$Res> {
|
||||||
|
__$$SnPollImplCopyWithImpl(
|
||||||
|
_$SnPollImpl _value, $Res Function(_$SnPollImpl) _then)
|
||||||
|
: super(_value, _then);
|
||||||
|
|
||||||
|
/// Create a copy of SnPoll
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
@override
|
||||||
|
$Res call({
|
||||||
|
Object? id = null,
|
||||||
|
Object? createdAt = null,
|
||||||
|
Object? updatedAt = null,
|
||||||
|
Object? deletedAt = freezed,
|
||||||
|
Object? expiredAt = freezed,
|
||||||
|
Object? options = null,
|
||||||
|
Object? accountId = null,
|
||||||
|
Object? metric = null,
|
||||||
|
}) {
|
||||||
|
return _then(_$SnPollImpl(
|
||||||
|
id: null == id
|
||||||
|
? _value.id
|
||||||
|
: id // ignore: cast_nullable_to_non_nullable
|
||||||
|
as int,
|
||||||
|
createdAt: null == createdAt
|
||||||
|
? _value.createdAt
|
||||||
|
: createdAt // ignore: cast_nullable_to_non_nullable
|
||||||
|
as DateTime,
|
||||||
|
updatedAt: null == updatedAt
|
||||||
|
? _value.updatedAt
|
||||||
|
: updatedAt // ignore: cast_nullable_to_non_nullable
|
||||||
|
as DateTime,
|
||||||
|
deletedAt: freezed == deletedAt
|
||||||
|
? _value.deletedAt
|
||||||
|
: deletedAt // ignore: cast_nullable_to_non_nullable
|
||||||
|
as dynamic,
|
||||||
|
expiredAt: freezed == expiredAt
|
||||||
|
? _value.expiredAt
|
||||||
|
: expiredAt // ignore: cast_nullable_to_non_nullable
|
||||||
|
as dynamic,
|
||||||
|
options: null == options
|
||||||
|
? _value._options
|
||||||
|
: options // ignore: cast_nullable_to_non_nullable
|
||||||
|
as List<SnPollOption>,
|
||||||
|
accountId: null == accountId
|
||||||
|
? _value.accountId
|
||||||
|
: accountId // ignore: cast_nullable_to_non_nullable
|
||||||
|
as int,
|
||||||
|
metric: null == metric
|
||||||
|
? _value.metric
|
||||||
|
: metric // ignore: cast_nullable_to_non_nullable
|
||||||
|
as SnPollMetric,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
@JsonSerializable()
|
||||||
|
class _$SnPollImpl implements _SnPoll {
|
||||||
|
const _$SnPollImpl(
|
||||||
|
{required this.id,
|
||||||
|
required this.createdAt,
|
||||||
|
required this.updatedAt,
|
||||||
|
required this.deletedAt,
|
||||||
|
required this.expiredAt,
|
||||||
|
required final List<SnPollOption> options,
|
||||||
|
required this.accountId,
|
||||||
|
required this.metric})
|
||||||
|
: _options = options;
|
||||||
|
|
||||||
|
factory _$SnPollImpl.fromJson(Map<String, dynamic> json) =>
|
||||||
|
_$$SnPollImplFromJson(json);
|
||||||
|
|
||||||
|
@override
|
||||||
|
final int id;
|
||||||
|
@override
|
||||||
|
final DateTime createdAt;
|
||||||
|
@override
|
||||||
|
final DateTime updatedAt;
|
||||||
|
@override
|
||||||
|
final dynamic deletedAt;
|
||||||
|
@override
|
||||||
|
final dynamic expiredAt;
|
||||||
|
final List<SnPollOption> _options;
|
||||||
|
@override
|
||||||
|
List<SnPollOption> get options {
|
||||||
|
if (_options is EqualUnmodifiableListView) return _options;
|
||||||
|
// ignore: implicit_dynamic_type
|
||||||
|
return EqualUnmodifiableListView(_options);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
final int accountId;
|
||||||
|
@override
|
||||||
|
final SnPollMetric metric;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return 'SnPoll(id: $id, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt, expiredAt: $expiredAt, options: $options, accountId: $accountId, metric: $metric)';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) {
|
||||||
|
return identical(this, other) ||
|
||||||
|
(other.runtimeType == runtimeType &&
|
||||||
|
other is _$SnPollImpl &&
|
||||||
|
(identical(other.id, id) || other.id == id) &&
|
||||||
|
(identical(other.createdAt, createdAt) ||
|
||||||
|
other.createdAt == createdAt) &&
|
||||||
|
(identical(other.updatedAt, updatedAt) ||
|
||||||
|
other.updatedAt == updatedAt) &&
|
||||||
|
const DeepCollectionEquality().equals(other.deletedAt, deletedAt) &&
|
||||||
|
const DeepCollectionEquality().equals(other.expiredAt, expiredAt) &&
|
||||||
|
const DeepCollectionEquality().equals(other._options, _options) &&
|
||||||
|
(identical(other.accountId, accountId) ||
|
||||||
|
other.accountId == accountId) &&
|
||||||
|
(identical(other.metric, metric) || other.metric == metric));
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
@override
|
||||||
|
int get hashCode => Object.hash(
|
||||||
|
runtimeType,
|
||||||
|
id,
|
||||||
|
createdAt,
|
||||||
|
updatedAt,
|
||||||
|
const DeepCollectionEquality().hash(deletedAt),
|
||||||
|
const DeepCollectionEquality().hash(expiredAt),
|
||||||
|
const DeepCollectionEquality().hash(_options),
|
||||||
|
accountId,
|
||||||
|
metric);
|
||||||
|
|
||||||
|
/// Create a copy of SnPoll
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
@override
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
_$$SnPollImplCopyWith<_$SnPollImpl> get copyWith =>
|
||||||
|
__$$SnPollImplCopyWithImpl<_$SnPollImpl>(this, _$identity);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
return _$$SnPollImplToJson(
|
||||||
|
this,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class _SnPoll implements SnPoll {
|
||||||
|
const factory _SnPoll(
|
||||||
|
{required final int id,
|
||||||
|
required final DateTime createdAt,
|
||||||
|
required final DateTime updatedAt,
|
||||||
|
required final dynamic deletedAt,
|
||||||
|
required final dynamic expiredAt,
|
||||||
|
required final List<SnPollOption> options,
|
||||||
|
required final int accountId,
|
||||||
|
required final SnPollMetric metric}) = _$SnPollImpl;
|
||||||
|
|
||||||
|
factory _SnPoll.fromJson(Map<String, dynamic> json) = _$SnPollImpl.fromJson;
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get id;
|
||||||
|
@override
|
||||||
|
DateTime get createdAt;
|
||||||
|
@override
|
||||||
|
DateTime get updatedAt;
|
||||||
|
@override
|
||||||
|
dynamic get deletedAt;
|
||||||
|
@override
|
||||||
|
dynamic get expiredAt;
|
||||||
|
@override
|
||||||
|
List<SnPollOption> get options;
|
||||||
|
@override
|
||||||
|
int get accountId;
|
||||||
|
@override
|
||||||
|
SnPollMetric get metric;
|
||||||
|
|
||||||
|
/// Create a copy of SnPoll
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@override
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
_$$SnPollImplCopyWith<_$SnPollImpl> get copyWith =>
|
||||||
|
throw _privateConstructorUsedError;
|
||||||
|
}
|
||||||
|
|
||||||
|
SnPollMetric _$SnPollMetricFromJson(Map<String, dynamic> json) {
|
||||||
|
return _SnPollMetric.fromJson(json);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
mixin _$SnPollMetric {
|
||||||
|
int get totalAnswer => throw _privateConstructorUsedError;
|
||||||
|
Map<String, int> get byOptions => throw _privateConstructorUsedError;
|
||||||
|
Map<String, double> get byOptionsPercentage =>
|
||||||
|
throw _privateConstructorUsedError;
|
||||||
|
|
||||||
|
/// Serializes this SnPollMetric to a JSON map.
|
||||||
|
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
|
||||||
|
|
||||||
|
/// Create a copy of SnPollMetric
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
$SnPollMetricCopyWith<SnPollMetric> get copyWith =>
|
||||||
|
throw _privateConstructorUsedError;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
abstract class $SnPollMetricCopyWith<$Res> {
|
||||||
|
factory $SnPollMetricCopyWith(
|
||||||
|
SnPollMetric value, $Res Function(SnPollMetric) then) =
|
||||||
|
_$SnPollMetricCopyWithImpl<$Res, SnPollMetric>;
|
||||||
|
@useResult
|
||||||
|
$Res call(
|
||||||
|
{int totalAnswer,
|
||||||
|
Map<String, int> byOptions,
|
||||||
|
Map<String, double> byOptionsPercentage});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
class _$SnPollMetricCopyWithImpl<$Res, $Val extends SnPollMetric>
|
||||||
|
implements $SnPollMetricCopyWith<$Res> {
|
||||||
|
_$SnPollMetricCopyWithImpl(this._value, this._then);
|
||||||
|
|
||||||
|
// ignore: unused_field
|
||||||
|
final $Val _value;
|
||||||
|
// ignore: unused_field
|
||||||
|
final $Res Function($Val) _then;
|
||||||
|
|
||||||
|
/// Create a copy of SnPollMetric
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
@override
|
||||||
|
$Res call({
|
||||||
|
Object? totalAnswer = null,
|
||||||
|
Object? byOptions = null,
|
||||||
|
Object? byOptionsPercentage = null,
|
||||||
|
}) {
|
||||||
|
return _then(_value.copyWith(
|
||||||
|
totalAnswer: null == totalAnswer
|
||||||
|
? _value.totalAnswer
|
||||||
|
: totalAnswer // ignore: cast_nullable_to_non_nullable
|
||||||
|
as int,
|
||||||
|
byOptions: null == byOptions
|
||||||
|
? _value.byOptions
|
||||||
|
: byOptions // ignore: cast_nullable_to_non_nullable
|
||||||
|
as Map<String, int>,
|
||||||
|
byOptionsPercentage: null == byOptionsPercentage
|
||||||
|
? _value.byOptionsPercentage
|
||||||
|
: byOptionsPercentage // ignore: cast_nullable_to_non_nullable
|
||||||
|
as Map<String, double>,
|
||||||
|
) as $Val);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
abstract class _$$SnPollMetricImplCopyWith<$Res>
|
||||||
|
implements $SnPollMetricCopyWith<$Res> {
|
||||||
|
factory _$$SnPollMetricImplCopyWith(
|
||||||
|
_$SnPollMetricImpl value, $Res Function(_$SnPollMetricImpl) then) =
|
||||||
|
__$$SnPollMetricImplCopyWithImpl<$Res>;
|
||||||
|
@override
|
||||||
|
@useResult
|
||||||
|
$Res call(
|
||||||
|
{int totalAnswer,
|
||||||
|
Map<String, int> byOptions,
|
||||||
|
Map<String, double> byOptionsPercentage});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
class __$$SnPollMetricImplCopyWithImpl<$Res>
|
||||||
|
extends _$SnPollMetricCopyWithImpl<$Res, _$SnPollMetricImpl>
|
||||||
|
implements _$$SnPollMetricImplCopyWith<$Res> {
|
||||||
|
__$$SnPollMetricImplCopyWithImpl(
|
||||||
|
_$SnPollMetricImpl _value, $Res Function(_$SnPollMetricImpl) _then)
|
||||||
|
: super(_value, _then);
|
||||||
|
|
||||||
|
/// Create a copy of SnPollMetric
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
@override
|
||||||
|
$Res call({
|
||||||
|
Object? totalAnswer = null,
|
||||||
|
Object? byOptions = null,
|
||||||
|
Object? byOptionsPercentage = null,
|
||||||
|
}) {
|
||||||
|
return _then(_$SnPollMetricImpl(
|
||||||
|
totalAnswer: null == totalAnswer
|
||||||
|
? _value.totalAnswer
|
||||||
|
: totalAnswer // ignore: cast_nullable_to_non_nullable
|
||||||
|
as int,
|
||||||
|
byOptions: null == byOptions
|
||||||
|
? _value._byOptions
|
||||||
|
: byOptions // ignore: cast_nullable_to_non_nullable
|
||||||
|
as Map<String, int>,
|
||||||
|
byOptionsPercentage: null == byOptionsPercentage
|
||||||
|
? _value._byOptionsPercentage
|
||||||
|
: byOptionsPercentage // ignore: cast_nullable_to_non_nullable
|
||||||
|
as Map<String, double>,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
@JsonSerializable()
|
||||||
|
class _$SnPollMetricImpl implements _SnPollMetric {
|
||||||
|
const _$SnPollMetricImpl(
|
||||||
|
{required this.totalAnswer,
|
||||||
|
final Map<String, int> byOptions = const {},
|
||||||
|
final Map<String, double> byOptionsPercentage = const {}})
|
||||||
|
: _byOptions = byOptions,
|
||||||
|
_byOptionsPercentage = byOptionsPercentage;
|
||||||
|
|
||||||
|
factory _$SnPollMetricImpl.fromJson(Map<String, dynamic> json) =>
|
||||||
|
_$$SnPollMetricImplFromJson(json);
|
||||||
|
|
||||||
|
@override
|
||||||
|
final int totalAnswer;
|
||||||
|
final Map<String, int> _byOptions;
|
||||||
|
@override
|
||||||
|
@JsonKey()
|
||||||
|
Map<String, int> get byOptions {
|
||||||
|
if (_byOptions is EqualUnmodifiableMapView) return _byOptions;
|
||||||
|
// ignore: implicit_dynamic_type
|
||||||
|
return EqualUnmodifiableMapView(_byOptions);
|
||||||
|
}
|
||||||
|
|
||||||
|
final Map<String, double> _byOptionsPercentage;
|
||||||
|
@override
|
||||||
|
@JsonKey()
|
||||||
|
Map<String, double> get byOptionsPercentage {
|
||||||
|
if (_byOptionsPercentage is EqualUnmodifiableMapView)
|
||||||
|
return _byOptionsPercentage;
|
||||||
|
// ignore: implicit_dynamic_type
|
||||||
|
return EqualUnmodifiableMapView(_byOptionsPercentage);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return 'SnPollMetric(totalAnswer: $totalAnswer, byOptions: $byOptions, byOptionsPercentage: $byOptionsPercentage)';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) {
|
||||||
|
return identical(this, other) ||
|
||||||
|
(other.runtimeType == runtimeType &&
|
||||||
|
other is _$SnPollMetricImpl &&
|
||||||
|
(identical(other.totalAnswer, totalAnswer) ||
|
||||||
|
other.totalAnswer == totalAnswer) &&
|
||||||
|
const DeepCollectionEquality()
|
||||||
|
.equals(other._byOptions, _byOptions) &&
|
||||||
|
const DeepCollectionEquality()
|
||||||
|
.equals(other._byOptionsPercentage, _byOptionsPercentage));
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
@override
|
||||||
|
int get hashCode => Object.hash(
|
||||||
|
runtimeType,
|
||||||
|
totalAnswer,
|
||||||
|
const DeepCollectionEquality().hash(_byOptions),
|
||||||
|
const DeepCollectionEquality().hash(_byOptionsPercentage));
|
||||||
|
|
||||||
|
/// Create a copy of SnPollMetric
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
@override
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
_$$SnPollMetricImplCopyWith<_$SnPollMetricImpl> get copyWith =>
|
||||||
|
__$$SnPollMetricImplCopyWithImpl<_$SnPollMetricImpl>(this, _$identity);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
return _$$SnPollMetricImplToJson(
|
||||||
|
this,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class _SnPollMetric implements SnPollMetric {
|
||||||
|
const factory _SnPollMetric(
|
||||||
|
{required final int totalAnswer,
|
||||||
|
final Map<String, int> byOptions,
|
||||||
|
final Map<String, double> byOptionsPercentage}) = _$SnPollMetricImpl;
|
||||||
|
|
||||||
|
factory _SnPollMetric.fromJson(Map<String, dynamic> json) =
|
||||||
|
_$SnPollMetricImpl.fromJson;
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get totalAnswer;
|
||||||
|
@override
|
||||||
|
Map<String, int> get byOptions;
|
||||||
|
@override
|
||||||
|
Map<String, double> get byOptionsPercentage;
|
||||||
|
|
||||||
|
/// Create a copy of SnPollMetric
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@override
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
_$$SnPollMetricImplCopyWith<_$SnPollMetricImpl> get copyWith =>
|
||||||
|
throw _privateConstructorUsedError;
|
||||||
|
}
|
||||||
|
|
||||||
|
SnPollOption _$SnPollOptionFromJson(Map<String, dynamic> json) {
|
||||||
|
return _SnPollOption.fromJson(json);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
mixin _$SnPollOption {
|
||||||
|
String get id => throw _privateConstructorUsedError;
|
||||||
|
String get icon => throw _privateConstructorUsedError;
|
||||||
|
String get name => throw _privateConstructorUsedError;
|
||||||
|
String get description => throw _privateConstructorUsedError;
|
||||||
|
|
||||||
|
/// Serializes this SnPollOption to a JSON map.
|
||||||
|
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
|
||||||
|
|
||||||
|
/// Create a copy of SnPollOption
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
$SnPollOptionCopyWith<SnPollOption> get copyWith =>
|
||||||
|
throw _privateConstructorUsedError;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
abstract class $SnPollOptionCopyWith<$Res> {
|
||||||
|
factory $SnPollOptionCopyWith(
|
||||||
|
SnPollOption value, $Res Function(SnPollOption) then) =
|
||||||
|
_$SnPollOptionCopyWithImpl<$Res, SnPollOption>;
|
||||||
|
@useResult
|
||||||
|
$Res call({String id, String icon, String name, String description});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
class _$SnPollOptionCopyWithImpl<$Res, $Val extends SnPollOption>
|
||||||
|
implements $SnPollOptionCopyWith<$Res> {
|
||||||
|
_$SnPollOptionCopyWithImpl(this._value, this._then);
|
||||||
|
|
||||||
|
// ignore: unused_field
|
||||||
|
final $Val _value;
|
||||||
|
// ignore: unused_field
|
||||||
|
final $Res Function($Val) _then;
|
||||||
|
|
||||||
|
/// Create a copy of SnPollOption
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
@override
|
||||||
|
$Res call({
|
||||||
|
Object? id = null,
|
||||||
|
Object? icon = null,
|
||||||
|
Object? name = null,
|
||||||
|
Object? description = null,
|
||||||
|
}) {
|
||||||
|
return _then(_value.copyWith(
|
||||||
|
id: null == id
|
||||||
|
? _value.id
|
||||||
|
: id // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,
|
||||||
|
icon: null == icon
|
||||||
|
? _value.icon
|
||||||
|
: icon // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,
|
||||||
|
name: null == name
|
||||||
|
? _value.name
|
||||||
|
: name // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,
|
||||||
|
description: null == description
|
||||||
|
? _value.description
|
||||||
|
: description // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,
|
||||||
|
) as $Val);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
abstract class _$$SnPollOptionImplCopyWith<$Res>
|
||||||
|
implements $SnPollOptionCopyWith<$Res> {
|
||||||
|
factory _$$SnPollOptionImplCopyWith(
|
||||||
|
_$SnPollOptionImpl value, $Res Function(_$SnPollOptionImpl) then) =
|
||||||
|
__$$SnPollOptionImplCopyWithImpl<$Res>;
|
||||||
|
@override
|
||||||
|
@useResult
|
||||||
|
$Res call({String id, String icon, String name, String description});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
class __$$SnPollOptionImplCopyWithImpl<$Res>
|
||||||
|
extends _$SnPollOptionCopyWithImpl<$Res, _$SnPollOptionImpl>
|
||||||
|
implements _$$SnPollOptionImplCopyWith<$Res> {
|
||||||
|
__$$SnPollOptionImplCopyWithImpl(
|
||||||
|
_$SnPollOptionImpl _value, $Res Function(_$SnPollOptionImpl) _then)
|
||||||
|
: super(_value, _then);
|
||||||
|
|
||||||
|
/// Create a copy of SnPollOption
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
@override
|
||||||
|
$Res call({
|
||||||
|
Object? id = null,
|
||||||
|
Object? icon = null,
|
||||||
|
Object? name = null,
|
||||||
|
Object? description = null,
|
||||||
|
}) {
|
||||||
|
return _then(_$SnPollOptionImpl(
|
||||||
|
id: null == id
|
||||||
|
? _value.id
|
||||||
|
: id // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,
|
||||||
|
icon: null == icon
|
||||||
|
? _value.icon
|
||||||
|
: icon // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,
|
||||||
|
name: null == name
|
||||||
|
? _value.name
|
||||||
|
: name // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,
|
||||||
|
description: null == description
|
||||||
|
? _value.description
|
||||||
|
: description // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
@JsonSerializable()
|
||||||
|
class _$SnPollOptionImpl implements _SnPollOption {
|
||||||
|
const _$SnPollOptionImpl(
|
||||||
|
{required this.id,
|
||||||
|
required this.icon,
|
||||||
|
required this.name,
|
||||||
|
required this.description});
|
||||||
|
|
||||||
|
factory _$SnPollOptionImpl.fromJson(Map<String, dynamic> json) =>
|
||||||
|
_$$SnPollOptionImplFromJson(json);
|
||||||
|
|
||||||
|
@override
|
||||||
|
final String id;
|
||||||
|
@override
|
||||||
|
final String icon;
|
||||||
|
@override
|
||||||
|
final String name;
|
||||||
|
@override
|
||||||
|
final String description;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return 'SnPollOption(id: $id, icon: $icon, name: $name, description: $description)';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) {
|
||||||
|
return identical(this, other) ||
|
||||||
|
(other.runtimeType == runtimeType &&
|
||||||
|
other is _$SnPollOptionImpl &&
|
||||||
|
(identical(other.id, id) || other.id == id) &&
|
||||||
|
(identical(other.icon, icon) || other.icon == icon) &&
|
||||||
|
(identical(other.name, name) || other.name == name) &&
|
||||||
|
(identical(other.description, description) ||
|
||||||
|
other.description == description));
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
@override
|
||||||
|
int get hashCode => Object.hash(runtimeType, id, icon, name, description);
|
||||||
|
|
||||||
|
/// Create a copy of SnPollOption
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
@override
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
_$$SnPollOptionImplCopyWith<_$SnPollOptionImpl> get copyWith =>
|
||||||
|
__$$SnPollOptionImplCopyWithImpl<_$SnPollOptionImpl>(this, _$identity);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
return _$$SnPollOptionImplToJson(
|
||||||
|
this,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class _SnPollOption implements SnPollOption {
|
||||||
|
const factory _SnPollOption(
|
||||||
|
{required final String id,
|
||||||
|
required final String icon,
|
||||||
|
required final String name,
|
||||||
|
required final String description}) = _$SnPollOptionImpl;
|
||||||
|
|
||||||
|
factory _SnPollOption.fromJson(Map<String, dynamic> json) =
|
||||||
|
_$SnPollOptionImpl.fromJson;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get id;
|
||||||
|
@override
|
||||||
|
String get icon;
|
||||||
|
@override
|
||||||
|
String get name;
|
||||||
|
@override
|
||||||
|
String get description;
|
||||||
|
|
||||||
|
/// Create a copy of SnPollOption
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@override
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
_$$SnPollOptionImplCopyWith<_$SnPollOptionImpl> get copyWith =>
|
||||||
|
throw _privateConstructorUsedError;
|
||||||
|
}
|
69
lib/types/poll.g.dart
Normal file
69
lib/types/poll.g.dart
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
|
||||||
|
part of 'poll.dart';
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// JsonSerializableGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
_$SnPollImpl _$$SnPollImplFromJson(Map<String, dynamic> json) => _$SnPollImpl(
|
||||||
|
id: (json['id'] as num).toInt(),
|
||||||
|
createdAt: DateTime.parse(json['created_at'] as String),
|
||||||
|
updatedAt: DateTime.parse(json['updated_at'] as String),
|
||||||
|
deletedAt: json['deleted_at'],
|
||||||
|
expiredAt: json['expired_at'],
|
||||||
|
options: (json['options'] as List<dynamic>)
|
||||||
|
.map((e) => SnPollOption.fromJson(e as Map<String, dynamic>))
|
||||||
|
.toList(),
|
||||||
|
accountId: (json['account_id'] as num).toInt(),
|
||||||
|
metric: SnPollMetric.fromJson(json['metric'] as Map<String, dynamic>),
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<String, dynamic> _$$SnPollImplToJson(_$SnPollImpl instance) =>
|
||||||
|
<String, dynamic>{
|
||||||
|
'id': instance.id,
|
||||||
|
'created_at': instance.createdAt.toIso8601String(),
|
||||||
|
'updated_at': instance.updatedAt.toIso8601String(),
|
||||||
|
'deleted_at': instance.deletedAt,
|
||||||
|
'expired_at': instance.expiredAt,
|
||||||
|
'options': instance.options.map((e) => e.toJson()).toList(),
|
||||||
|
'account_id': instance.accountId,
|
||||||
|
'metric': instance.metric.toJson(),
|
||||||
|
};
|
||||||
|
|
||||||
|
_$SnPollMetricImpl _$$SnPollMetricImplFromJson(Map<String, dynamic> json) =>
|
||||||
|
_$SnPollMetricImpl(
|
||||||
|
totalAnswer: (json['total_answer'] as num).toInt(),
|
||||||
|
byOptions: (json['by_options'] as Map<String, dynamic>?)?.map(
|
||||||
|
(k, e) => MapEntry(k, (e as num).toInt()),
|
||||||
|
) ??
|
||||||
|
const {},
|
||||||
|
byOptionsPercentage:
|
||||||
|
(json['by_options_percentage'] as Map<String, dynamic>?)?.map(
|
||||||
|
(k, e) => MapEntry(k, (e as num).toDouble()),
|
||||||
|
) ??
|
||||||
|
const {},
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<String, dynamic> _$$SnPollMetricImplToJson(_$SnPollMetricImpl instance) =>
|
||||||
|
<String, dynamic>{
|
||||||
|
'total_answer': instance.totalAnswer,
|
||||||
|
'by_options': instance.byOptions,
|
||||||
|
'by_options_percentage': instance.byOptionsPercentage,
|
||||||
|
};
|
||||||
|
|
||||||
|
_$SnPollOptionImpl _$$SnPollOptionImplFromJson(Map<String, dynamic> json) =>
|
||||||
|
_$SnPollOptionImpl(
|
||||||
|
id: json['id'] as String,
|
||||||
|
icon: json['icon'] as String,
|
||||||
|
name: json['name'] as String,
|
||||||
|
description: json['description'] as String,
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<String, dynamic> _$$SnPollOptionImplToJson(_$SnPollOptionImpl instance) =>
|
||||||
|
<String, dynamic>{
|
||||||
|
'id': instance.id,
|
||||||
|
'icon': instance.icon,
|
||||||
|
'name': instance.name,
|
||||||
|
'description': instance.description,
|
||||||
|
};
|
@ -1,5 +1,6 @@
|
|||||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||||
import 'package:surface/types/attachment.dart';
|
import 'package:surface/types/attachment.dart';
|
||||||
|
import 'package:surface/types/poll.dart';
|
||||||
|
|
||||||
part 'post.freezed.dart';
|
part 'post.freezed.dart';
|
||||||
part 'post.g.dart';
|
part 'post.g.dart';
|
||||||
@ -37,6 +38,7 @@ class SnPost with _$SnPost {
|
|||||||
required int totalUpvote,
|
required int totalUpvote,
|
||||||
required int totalDownvote,
|
required int totalDownvote,
|
||||||
required int publisherId,
|
required int publisherId,
|
||||||
|
required int? pollId,
|
||||||
required SnPublisher publisher,
|
required SnPublisher publisher,
|
||||||
required SnMetric metric,
|
required SnMetric metric,
|
||||||
SnPostPreload? preload,
|
SnPostPreload? preload,
|
||||||
@ -90,6 +92,7 @@ class SnPostPreload with _$SnPostPreload {
|
|||||||
required SnAttachment? thumbnail,
|
required SnAttachment? thumbnail,
|
||||||
required List<SnAttachment?>? attachments,
|
required List<SnAttachment?>? attachments,
|
||||||
required SnAttachment? video,
|
required SnAttachment? video,
|
||||||
|
required SnPoll? poll,
|
||||||
}) = _SnPostPreload;
|
}) = _SnPostPreload;
|
||||||
|
|
||||||
factory SnPostPreload.fromJson(Map<String, Object?> json) =>
|
factory SnPostPreload.fromJson(Map<String, Object?> json) =>
|
||||||
|
@ -48,6 +48,7 @@ mixin _$SnPost {
|
|||||||
int get totalUpvote => throw _privateConstructorUsedError;
|
int get totalUpvote => throw _privateConstructorUsedError;
|
||||||
int get totalDownvote => throw _privateConstructorUsedError;
|
int get totalDownvote => throw _privateConstructorUsedError;
|
||||||
int get publisherId => throw _privateConstructorUsedError;
|
int get publisherId => throw _privateConstructorUsedError;
|
||||||
|
int? get pollId => throw _privateConstructorUsedError;
|
||||||
SnPublisher get publisher => throw _privateConstructorUsedError;
|
SnPublisher get publisher => throw _privateConstructorUsedError;
|
||||||
SnMetric get metric => throw _privateConstructorUsedError;
|
SnMetric get metric => throw _privateConstructorUsedError;
|
||||||
SnPostPreload? get preload => throw _privateConstructorUsedError;
|
SnPostPreload? get preload => throw _privateConstructorUsedError;
|
||||||
@ -95,6 +96,7 @@ abstract class $SnPostCopyWith<$Res> {
|
|||||||
int totalUpvote,
|
int totalUpvote,
|
||||||
int totalDownvote,
|
int totalDownvote,
|
||||||
int publisherId,
|
int publisherId,
|
||||||
|
int? pollId,
|
||||||
SnPublisher publisher,
|
SnPublisher publisher,
|
||||||
SnMetric metric,
|
SnMetric metric,
|
||||||
SnPostPreload? preload});
|
SnPostPreload? preload});
|
||||||
@ -149,6 +151,7 @@ class _$SnPostCopyWithImpl<$Res, $Val extends SnPost>
|
|||||||
Object? totalUpvote = null,
|
Object? totalUpvote = null,
|
||||||
Object? totalDownvote = null,
|
Object? totalDownvote = null,
|
||||||
Object? publisherId = null,
|
Object? publisherId = null,
|
||||||
|
Object? pollId = freezed,
|
||||||
Object? publisher = null,
|
Object? publisher = null,
|
||||||
Object? metric = null,
|
Object? metric = null,
|
||||||
Object? preload = freezed,
|
Object? preload = freezed,
|
||||||
@ -266,6 +269,10 @@ class _$SnPostCopyWithImpl<$Res, $Val extends SnPost>
|
|||||||
? _value.publisherId
|
? _value.publisherId
|
||||||
: publisherId // ignore: cast_nullable_to_non_nullable
|
: publisherId // ignore: cast_nullable_to_non_nullable
|
||||||
as int,
|
as int,
|
||||||
|
pollId: freezed == pollId
|
||||||
|
? _value.pollId
|
||||||
|
: pollId // ignore: cast_nullable_to_non_nullable
|
||||||
|
as int?,
|
||||||
publisher: null == publisher
|
publisher: null == publisher
|
||||||
? _value.publisher
|
? _value.publisher
|
||||||
: publisher // ignore: cast_nullable_to_non_nullable
|
: publisher // ignore: cast_nullable_to_non_nullable
|
||||||
@ -380,6 +387,7 @@ abstract class _$$SnPostImplCopyWith<$Res> implements $SnPostCopyWith<$Res> {
|
|||||||
int totalUpvote,
|
int totalUpvote,
|
||||||
int totalDownvote,
|
int totalDownvote,
|
||||||
int publisherId,
|
int publisherId,
|
||||||
|
int? pollId,
|
||||||
SnPublisher publisher,
|
SnPublisher publisher,
|
||||||
SnMetric metric,
|
SnMetric metric,
|
||||||
SnPostPreload? preload});
|
SnPostPreload? preload});
|
||||||
@ -437,6 +445,7 @@ class __$$SnPostImplCopyWithImpl<$Res>
|
|||||||
Object? totalUpvote = null,
|
Object? totalUpvote = null,
|
||||||
Object? totalDownvote = null,
|
Object? totalDownvote = null,
|
||||||
Object? publisherId = null,
|
Object? publisherId = null,
|
||||||
|
Object? pollId = freezed,
|
||||||
Object? publisher = null,
|
Object? publisher = null,
|
||||||
Object? metric = null,
|
Object? metric = null,
|
||||||
Object? preload = freezed,
|
Object? preload = freezed,
|
||||||
@ -554,6 +563,10 @@ class __$$SnPostImplCopyWithImpl<$Res>
|
|||||||
? _value.publisherId
|
? _value.publisherId
|
||||||
: publisherId // ignore: cast_nullable_to_non_nullable
|
: publisherId // ignore: cast_nullable_to_non_nullable
|
||||||
as int,
|
as int,
|
||||||
|
pollId: freezed == pollId
|
||||||
|
? _value.pollId
|
||||||
|
: pollId // ignore: cast_nullable_to_non_nullable
|
||||||
|
as int?,
|
||||||
publisher: null == publisher
|
publisher: null == publisher
|
||||||
? _value.publisher
|
? _value.publisher
|
||||||
: publisher // ignore: cast_nullable_to_non_nullable
|
: publisher // ignore: cast_nullable_to_non_nullable
|
||||||
@ -602,6 +615,7 @@ class _$SnPostImpl extends _SnPost {
|
|||||||
required this.totalUpvote,
|
required this.totalUpvote,
|
||||||
required this.totalDownvote,
|
required this.totalDownvote,
|
||||||
required this.publisherId,
|
required this.publisherId,
|
||||||
|
required this.pollId,
|
||||||
required this.publisher,
|
required this.publisher,
|
||||||
required this.metric,
|
required this.metric,
|
||||||
this.preload})
|
this.preload})
|
||||||
@ -719,6 +733,8 @@ class _$SnPostImpl extends _SnPost {
|
|||||||
@override
|
@override
|
||||||
final int publisherId;
|
final int publisherId;
|
||||||
@override
|
@override
|
||||||
|
final int? pollId;
|
||||||
|
@override
|
||||||
final SnPublisher publisher;
|
final SnPublisher publisher;
|
||||||
@override
|
@override
|
||||||
final SnMetric metric;
|
final SnMetric metric;
|
||||||
@ -727,7 +743,7 @@ class _$SnPostImpl extends _SnPost {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
return 'SnPost(id: $id, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt, type: $type, body: $body, language: $language, alias: $alias, aliasPrefix: $aliasPrefix, tags: $tags, categories: $categories, replies: $replies, replyId: $replyId, repostId: $repostId, replyTo: $replyTo, repostTo: $repostTo, visibleUsersList: $visibleUsersList, invisibleUsersList: $invisibleUsersList, visibility: $visibility, editedAt: $editedAt, pinnedAt: $pinnedAt, lockedAt: $lockedAt, isDraft: $isDraft, publishedAt: $publishedAt, publishedUntil: $publishedUntil, totalUpvote: $totalUpvote, totalDownvote: $totalDownvote, publisherId: $publisherId, publisher: $publisher, metric: $metric, preload: $preload)';
|
return 'SnPost(id: $id, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt, type: $type, body: $body, language: $language, alias: $alias, aliasPrefix: $aliasPrefix, tags: $tags, categories: $categories, replies: $replies, replyId: $replyId, repostId: $repostId, replyTo: $replyTo, repostTo: $repostTo, visibleUsersList: $visibleUsersList, invisibleUsersList: $invisibleUsersList, visibility: $visibility, editedAt: $editedAt, pinnedAt: $pinnedAt, lockedAt: $lockedAt, isDraft: $isDraft, publishedAt: $publishedAt, publishedUntil: $publishedUntil, totalUpvote: $totalUpvote, totalDownvote: $totalDownvote, publisherId: $publisherId, pollId: $pollId, publisher: $publisher, metric: $metric, preload: $preload)';
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -782,6 +798,7 @@ class _$SnPostImpl extends _SnPost {
|
|||||||
other.totalDownvote == totalDownvote) &&
|
other.totalDownvote == totalDownvote) &&
|
||||||
(identical(other.publisherId, publisherId) ||
|
(identical(other.publisherId, publisherId) ||
|
||||||
other.publisherId == publisherId) &&
|
other.publisherId == publisherId) &&
|
||||||
|
(identical(other.pollId, pollId) || other.pollId == pollId) &&
|
||||||
(identical(other.publisher, publisher) ||
|
(identical(other.publisher, publisher) ||
|
||||||
other.publisher == publisher) &&
|
other.publisher == publisher) &&
|
||||||
(identical(other.metric, metric) || other.metric == metric) &&
|
(identical(other.metric, metric) || other.metric == metric) &&
|
||||||
@ -820,6 +837,7 @@ class _$SnPostImpl extends _SnPost {
|
|||||||
totalUpvote,
|
totalUpvote,
|
||||||
totalDownvote,
|
totalDownvote,
|
||||||
publisherId,
|
publisherId,
|
||||||
|
pollId,
|
||||||
publisher,
|
publisher,
|
||||||
metric,
|
metric,
|
||||||
preload
|
preload
|
||||||
@ -871,6 +889,7 @@ abstract class _SnPost extends SnPost {
|
|||||||
required final int totalUpvote,
|
required final int totalUpvote,
|
||||||
required final int totalDownvote,
|
required final int totalDownvote,
|
||||||
required final int publisherId,
|
required final int publisherId,
|
||||||
|
required final int? pollId,
|
||||||
required final SnPublisher publisher,
|
required final SnPublisher publisher,
|
||||||
required final SnMetric metric,
|
required final SnMetric metric,
|
||||||
final SnPostPreload? preload}) = _$SnPostImpl;
|
final SnPostPreload? preload}) = _$SnPostImpl;
|
||||||
@ -935,6 +954,8 @@ abstract class _SnPost extends SnPost {
|
|||||||
@override
|
@override
|
||||||
int get publisherId;
|
int get publisherId;
|
||||||
@override
|
@override
|
||||||
|
int? get pollId;
|
||||||
|
@override
|
||||||
SnPublisher get publisher;
|
SnPublisher get publisher;
|
||||||
@override
|
@override
|
||||||
SnMetric get metric;
|
SnMetric get metric;
|
||||||
@ -1568,6 +1589,7 @@ 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;
|
SnAttachment? get video => throw _privateConstructorUsedError;
|
||||||
|
SnPoll? get poll => 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;
|
||||||
@ -1588,10 +1610,12 @@ abstract class $SnPostPreloadCopyWith<$Res> {
|
|||||||
$Res call(
|
$Res call(
|
||||||
{SnAttachment? thumbnail,
|
{SnAttachment? thumbnail,
|
||||||
List<SnAttachment?>? attachments,
|
List<SnAttachment?>? attachments,
|
||||||
SnAttachment? video});
|
SnAttachment? video,
|
||||||
|
SnPoll? poll});
|
||||||
|
|
||||||
$SnAttachmentCopyWith<$Res>? get thumbnail;
|
$SnAttachmentCopyWith<$Res>? get thumbnail;
|
||||||
$SnAttachmentCopyWith<$Res>? get video;
|
$SnAttachmentCopyWith<$Res>? get video;
|
||||||
|
$SnPollCopyWith<$Res>? get poll;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @nodoc
|
/// @nodoc
|
||||||
@ -1612,6 +1636,7 @@ class _$SnPostPreloadCopyWithImpl<$Res, $Val extends SnPostPreload>
|
|||||||
Object? thumbnail = freezed,
|
Object? thumbnail = freezed,
|
||||||
Object? attachments = freezed,
|
Object? attachments = freezed,
|
||||||
Object? video = freezed,
|
Object? video = freezed,
|
||||||
|
Object? poll = freezed,
|
||||||
}) {
|
}) {
|
||||||
return _then(_value.copyWith(
|
return _then(_value.copyWith(
|
||||||
thumbnail: freezed == thumbnail
|
thumbnail: freezed == thumbnail
|
||||||
@ -1626,6 +1651,10 @@ class _$SnPostPreloadCopyWithImpl<$Res, $Val extends SnPostPreload>
|
|||||||
? _value.video
|
? _value.video
|
||||||
: video // ignore: cast_nullable_to_non_nullable
|
: video // ignore: cast_nullable_to_non_nullable
|
||||||
as SnAttachment?,
|
as SnAttachment?,
|
||||||
|
poll: freezed == poll
|
||||||
|
? _value.poll
|
||||||
|
: poll // ignore: cast_nullable_to_non_nullable
|
||||||
|
as SnPoll?,
|
||||||
) as $Val);
|
) as $Val);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1656,6 +1685,20 @@ class _$SnPostPreloadCopyWithImpl<$Res, $Val extends SnPostPreload>
|
|||||||
return _then(_value.copyWith(video: value) as $Val);
|
return _then(_value.copyWith(video: value) as $Val);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Create a copy of SnPostPreload
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@override
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
$SnPollCopyWith<$Res>? get poll {
|
||||||
|
if (_value.poll == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $SnPollCopyWith<$Res>(_value.poll!, (value) {
|
||||||
|
return _then(_value.copyWith(poll: value) as $Val);
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @nodoc
|
/// @nodoc
|
||||||
@ -1669,12 +1712,15 @@ abstract class _$$SnPostPreloadImplCopyWith<$Res>
|
|||||||
$Res call(
|
$Res call(
|
||||||
{SnAttachment? thumbnail,
|
{SnAttachment? thumbnail,
|
||||||
List<SnAttachment?>? attachments,
|
List<SnAttachment?>? attachments,
|
||||||
SnAttachment? video});
|
SnAttachment? video,
|
||||||
|
SnPoll? poll});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
$SnAttachmentCopyWith<$Res>? get thumbnail;
|
$SnAttachmentCopyWith<$Res>? get thumbnail;
|
||||||
@override
|
@override
|
||||||
$SnAttachmentCopyWith<$Res>? get video;
|
$SnAttachmentCopyWith<$Res>? get video;
|
||||||
|
@override
|
||||||
|
$SnPollCopyWith<$Res>? get poll;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @nodoc
|
/// @nodoc
|
||||||
@ -1693,6 +1739,7 @@ class __$$SnPostPreloadImplCopyWithImpl<$Res>
|
|||||||
Object? thumbnail = freezed,
|
Object? thumbnail = freezed,
|
||||||
Object? attachments = freezed,
|
Object? attachments = freezed,
|
||||||
Object? video = freezed,
|
Object? video = freezed,
|
||||||
|
Object? poll = freezed,
|
||||||
}) {
|
}) {
|
||||||
return _then(_$SnPostPreloadImpl(
|
return _then(_$SnPostPreloadImpl(
|
||||||
thumbnail: freezed == thumbnail
|
thumbnail: freezed == thumbnail
|
||||||
@ -1707,6 +1754,10 @@ class __$$SnPostPreloadImplCopyWithImpl<$Res>
|
|||||||
? _value.video
|
? _value.video
|
||||||
: video // ignore: cast_nullable_to_non_nullable
|
: video // ignore: cast_nullable_to_non_nullable
|
||||||
as SnAttachment?,
|
as SnAttachment?,
|
||||||
|
poll: freezed == poll
|
||||||
|
? _value.poll
|
||||||
|
: poll // ignore: cast_nullable_to_non_nullable
|
||||||
|
as SnPoll?,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1717,7 +1768,8 @@ 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})
|
required this.video,
|
||||||
|
required this.poll})
|
||||||
: _attachments = attachments;
|
: _attachments = attachments;
|
||||||
|
|
||||||
factory _$SnPostPreloadImpl.fromJson(Map<String, dynamic> json) =>
|
factory _$SnPostPreloadImpl.fromJson(Map<String, dynamic> json) =>
|
||||||
@ -1737,10 +1789,12 @@ class _$SnPostPreloadImpl implements _SnPostPreload {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
final SnAttachment? video;
|
final SnAttachment? video;
|
||||||
|
@override
|
||||||
|
final SnPoll? poll;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
return 'SnPostPreload(thumbnail: $thumbnail, attachments: $attachments, video: $video)';
|
return 'SnPostPreload(thumbnail: $thumbnail, attachments: $attachments, video: $video, poll: $poll)';
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -1752,13 +1806,14 @@ class _$SnPostPreloadImpl implements _SnPostPreload {
|
|||||||
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));
|
(identical(other.video, video) || other.video == video) &&
|
||||||
|
(identical(other.poll, poll) || other.poll == poll));
|
||||||
}
|
}
|
||||||
|
|
||||||
@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), video);
|
const DeepCollectionEquality().hash(_attachments), video, poll);
|
||||||
|
|
||||||
/// 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.
|
||||||
@ -1780,7 +1835,8 @@ abstract class _SnPostPreload implements SnPostPreload {
|
|||||||
const factory _SnPostPreload(
|
const factory _SnPostPreload(
|
||||||
{required final SnAttachment? thumbnail,
|
{required final SnAttachment? thumbnail,
|
||||||
required final List<SnAttachment?>? attachments,
|
required final List<SnAttachment?>? attachments,
|
||||||
required final SnAttachment? video}) = _$SnPostPreloadImpl;
|
required final SnAttachment? video,
|
||||||
|
required final SnPoll? poll}) = _$SnPostPreloadImpl;
|
||||||
|
|
||||||
factory _SnPostPreload.fromJson(Map<String, dynamic> json) =
|
factory _SnPostPreload.fromJson(Map<String, dynamic> json) =
|
||||||
_$SnPostPreloadImpl.fromJson;
|
_$SnPostPreloadImpl.fromJson;
|
||||||
@ -1791,6 +1847,8 @@ abstract class _SnPostPreload implements SnPostPreload {
|
|||||||
List<SnAttachment?>? get attachments;
|
List<SnAttachment?>? get attachments;
|
||||||
@override
|
@override
|
||||||
SnAttachment? get video;
|
SnAttachment? get video;
|
||||||
|
@override
|
||||||
|
SnPoll? get poll;
|
||||||
|
|
||||||
/// 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.
|
||||||
|
@ -63,6 +63,7 @@ _$SnPostImpl _$$SnPostImplFromJson(Map<String, dynamic> json) => _$SnPostImpl(
|
|||||||
totalUpvote: (json['total_upvote'] as num).toInt(),
|
totalUpvote: (json['total_upvote'] as num).toInt(),
|
||||||
totalDownvote: (json['total_downvote'] as num).toInt(),
|
totalDownvote: (json['total_downvote'] as num).toInt(),
|
||||||
publisherId: (json['publisher_id'] as num).toInt(),
|
publisherId: (json['publisher_id'] as num).toInt(),
|
||||||
|
pollId: (json['poll_id'] as num?)?.toInt(),
|
||||||
publisher:
|
publisher:
|
||||||
SnPublisher.fromJson(json['publisher'] as Map<String, dynamic>),
|
SnPublisher.fromJson(json['publisher'] as Map<String, dynamic>),
|
||||||
metric: SnMetric.fromJson(json['metric'] as Map<String, dynamic>),
|
metric: SnMetric.fromJson(json['metric'] as Map<String, dynamic>),
|
||||||
@ -101,6 +102,7 @@ Map<String, dynamic> _$$SnPostImplToJson(_$SnPostImpl instance) =>
|
|||||||
'total_upvote': instance.totalUpvote,
|
'total_upvote': instance.totalUpvote,
|
||||||
'total_downvote': instance.totalDownvote,
|
'total_downvote': instance.totalDownvote,
|
||||||
'publisher_id': instance.publisherId,
|
'publisher_id': instance.publisherId,
|
||||||
|
'poll_id': instance.pollId,
|
||||||
'publisher': instance.publisher.toJson(),
|
'publisher': instance.publisher.toJson(),
|
||||||
'metric': instance.metric.toJson(),
|
'metric': instance.metric.toJson(),
|
||||||
'preload': instance.preload?.toJson(),
|
'preload': instance.preload?.toJson(),
|
||||||
@ -168,6 +170,9 @@ _$SnPostPreloadImpl _$$SnPostPreloadImplFromJson(Map<String, dynamic> json) =>
|
|||||||
video: json['video'] == null
|
video: json['video'] == null
|
||||||
? null
|
? null
|
||||||
: SnAttachment.fromJson(json['video'] as Map<String, dynamic>),
|
: SnAttachment.fromJson(json['video'] as Map<String, dynamic>),
|
||||||
|
poll: json['poll'] == null
|
||||||
|
? null
|
||||||
|
: SnPoll.fromJson(json['poll'] as Map<String, dynamic>),
|
||||||
);
|
);
|
||||||
|
|
||||||
Map<String, dynamic> _$$SnPostPreloadImplToJson(_$SnPostPreloadImpl instance) =>
|
Map<String, dynamic> _$$SnPostPreloadImplToJson(_$SnPostPreloadImpl instance) =>
|
||||||
@ -175,6 +180,7 @@ Map<String, dynamic> _$$SnPostPreloadImplToJson(_$SnPostPreloadImpl instance) =>
|
|||||||
'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(),
|
'video': instance.video?.toJson(),
|
||||||
|
'poll': instance.poll?.toJson(),
|
||||||
};
|
};
|
||||||
|
|
||||||
_$SnBodyImpl _$$SnBodyImplFromJson(Map<String, dynamic> json) => _$SnBodyImpl(
|
_$SnBodyImpl _$$SnBodyImplFromJson(Map<String, dynamic> json) => _$SnBodyImpl(
|
||||||
|
@ -6,6 +6,7 @@ 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';
|
||||||
import 'package:surface/providers/user_directory.dart';
|
import 'package:surface/providers/user_directory.dart';
|
||||||
|
import 'package:surface/providers/userinfo.dart';
|
||||||
import 'package:surface/types/account.dart';
|
import 'package:surface/types/account.dart';
|
||||||
import 'package:surface/widgets/account/account_image.dart';
|
import 'package:surface/widgets/account/account_image.dart';
|
||||||
|
|
||||||
@ -49,10 +50,19 @@ class _AccountSelectState extends State<AccountSelect> {
|
|||||||
Future<void> _getFriends() async {
|
Future<void> _getFriends() async {
|
||||||
final sn = context.read<SnNetworkProvider>();
|
final sn = context.read<SnNetworkProvider>();
|
||||||
final resp = await sn.client.get('/cgi/id/users/me/relations?status=1');
|
final resp = await sn.client.get('/cgi/id/users/me/relations?status=1');
|
||||||
|
if (!mounted) return;
|
||||||
|
final ua = context.read<UserProvider>();
|
||||||
|
|
||||||
setState(() {
|
setState(() {
|
||||||
_relativeUsers.addAll(
|
_relativeUsers.addAll(
|
||||||
resp.data?.map((e) => SnRelationship.fromJson(e)) ?? [],
|
resp.data?.map((e) {
|
||||||
|
final rel = SnRelationship.fromJson(e);
|
||||||
|
if (rel.relatedId == ua.user?.id) {
|
||||||
|
return rel.account!;
|
||||||
|
} else {
|
||||||
|
return rel.related!;
|
||||||
|
}
|
||||||
|
}).cast<SnAccount>(),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -123,13 +133,9 @@ class _AccountSelectState extends State<AccountSelect> {
|
|||||||
),
|
),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: ListView.builder(
|
child: ListView.builder(
|
||||||
itemCount: _pendingUsers.isEmpty
|
itemCount: _pendingUsers.isEmpty ? _relativeUsers.length : _pendingUsers.length,
|
||||||
? _relativeUsers.length
|
|
||||||
: _pendingUsers.length,
|
|
||||||
itemBuilder: (context, index) {
|
itemBuilder: (context, index) {
|
||||||
var user = _pendingUsers.isEmpty
|
var user = _pendingUsers.isEmpty ? _relativeUsers[index] : _pendingUsers[index];
|
||||||
? _relativeUsers[index]
|
|
||||||
: _pendingUsers[index];
|
|
||||||
return ListTile(
|
return ListTile(
|
||||||
title: Text(user.nick),
|
title: Text(user.nick),
|
||||||
subtitle: Text(user.name),
|
subtitle: Text(user.name),
|
||||||
@ -148,8 +154,7 @@ class _AccountSelectState extends State<AccountSelect> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
setState(() {
|
setState(() {
|
||||||
final idx = _selectedUsers
|
final idx = _selectedUsers.indexWhere((x) => x.id == user.id);
|
||||||
.indexWhere((x) => x.id == user.id);
|
|
||||||
if (idx != -1) {
|
if (idx != -1) {
|
||||||
_selectedUsers.removeAt(idx);
|
_selectedUsers.removeAt(idx);
|
||||||
} else {
|
} else {
|
||||||
|
@ -203,6 +203,7 @@ class ChatMessageInputState extends State<ChatMessageInput> {
|
|||||||
void dispose() {
|
void dispose() {
|
||||||
_contentController.dispose();
|
_contentController.dispose();
|
||||||
_focusNode.dispose();
|
_focusNode.dispose();
|
||||||
|
_dismissEmojiPicker();
|
||||||
if (!kIsWeb && !(Platform.isAndroid || Platform.isIOS)) hotKeyManager.unregister(_pasteHotKey);
|
if (!kIsWeb && !(Platform.isAndroid || Platform.isIOS)) hotKeyManager.unregister(_pasteHotKey);
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
@ -351,10 +352,9 @@ class ChatMessageInputState extends State<ChatMessageInput> {
|
|||||||
Symbols.mood,
|
Symbols.mood,
|
||||||
color: Theme.of(context).colorScheme.primary,
|
color: Theme.of(context).colorScheme.primary,
|
||||||
),
|
),
|
||||||
visualDensity: const VisualDensity(
|
visualDensity: const VisualDensity(horizontal: -4, vertical: -4),
|
||||||
horizontal: -4,
|
padding: EdgeInsets.zero,
|
||||||
vertical: -4,
|
constraints: const BoxConstraints(),
|
||||||
),
|
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
_showEmojiPicker(context);
|
_showEmojiPicker(context);
|
||||||
},
|
},
|
||||||
@ -372,10 +372,9 @@ class ChatMessageInputState extends State<ChatMessageInput> {
|
|||||||
Symbols.send,
|
Symbols.send,
|
||||||
color: Theme.of(context).colorScheme.primary,
|
color: Theme.of(context).colorScheme.primary,
|
||||||
),
|
),
|
||||||
visualDensity: const VisualDensity(
|
visualDensity: const VisualDensity(horizontal: -4, vertical: -4),
|
||||||
horizontal: -4,
|
padding: EdgeInsets.zero,
|
||||||
vertical: -4,
|
constraints: const BoxConstraints(),
|
||||||
),
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
@ -58,6 +58,7 @@ class AppScaffold extends StatelessWidget {
|
|||||||
backgroundColor: Theme.of(context).scaffoldBackgroundColor,
|
backgroundColor: Theme.of(context).scaffoldBackgroundColor,
|
||||||
body: SizedBox.expand(
|
body: SizedBox.expand(
|
||||||
child: AppBackground(
|
child: AppBackground(
|
||||||
|
isRoot: true,
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
IgnorePointer(child: SizedBox(height: appBar != null ? appBarHeight + safeTop : 0)),
|
IgnorePointer(child: SizedBox(height: appBar != null ? appBarHeight + safeTop : 0)),
|
||||||
|
@ -4,6 +4,7 @@ import 'package:gap/gap.dart';
|
|||||||
import 'package:go_router/go_router.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:responsive_framework/responsive_framework.dart';
|
||||||
import 'package:styled_widget/styled_widget.dart';
|
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';
|
||||||
@ -15,6 +16,47 @@ import 'package:very_good_infinite_list/very_good_infinite_list.dart';
|
|||||||
|
|
||||||
import '../../providers/sn_network.dart';
|
import '../../providers/sn_network.dart';
|
||||||
|
|
||||||
|
class PostCommentQuickAction extends StatelessWidget {
|
||||||
|
final double? maxWidth;
|
||||||
|
final SnPost parentPost;
|
||||||
|
final Function? onPosted;
|
||||||
|
|
||||||
|
const PostCommentQuickAction({super.key, this.maxWidth, required this.parentPost, this.onPosted});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final devicePixelRatio = MediaQuery.of(context).devicePixelRatio;
|
||||||
|
|
||||||
|
return Container(
|
||||||
|
height: 240,
|
||||||
|
constraints: BoxConstraints(maxWidth: maxWidth ?? double.infinity),
|
||||||
|
margin: ResponsiveBreakpoints.of(context).largerThan(MOBILE) ? const EdgeInsets.symmetric(vertical: 8) : EdgeInsets.zero,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
borderRadius: ResponsiveBreakpoints.of(context).largerThan(MOBILE)
|
||||||
|
? const BorderRadius.all(Radius.circular(8))
|
||||||
|
: BorderRadius.zero,
|
||||||
|
border: ResponsiveBreakpoints.of(context).largerThan(MOBILE)
|
||||||
|
? Border.all(
|
||||||
|
color: Theme.of(context).dividerColor,
|
||||||
|
width: 1 / devicePixelRatio,
|
||||||
|
)
|
||||||
|
: Border.symmetric(
|
||||||
|
horizontal: BorderSide(
|
||||||
|
color: Theme.of(context).dividerColor,
|
||||||
|
width: 1 / devicePixelRatio,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: PostMiniEditor(
|
||||||
|
postReplyId: parentPost.id,
|
||||||
|
onPost: () {
|
||||||
|
onPosted?.call();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class PostCommentSliverList extends StatefulWidget {
|
class PostCommentSliverList extends StatefulWidget {
|
||||||
final SnPost parentPost;
|
final SnPost parentPost;
|
||||||
final double? maxWidth;
|
final double? maxWidth;
|
||||||
@ -71,6 +113,7 @@ class PostCommentSliverListState extends State<PostCommentSliverList> {
|
|||||||
|
|
||||||
Future<void> refresh() async {
|
Future<void> refresh() async {
|
||||||
_posts.clear();
|
_posts.clear();
|
||||||
|
_postCount = null;
|
||||||
_fetchPosts();
|
_fetchPosts();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -36,6 +36,7 @@ import 'package:surface/widgets/markdown_content.dart';
|
|||||||
import 'package:gap/gap.dart';
|
import 'package:gap/gap.dart';
|
||||||
import 'package:surface/widgets/post/post_comment_list.dart';
|
import 'package:surface/widgets/post/post_comment_list.dart';
|
||||||
import 'package:surface/widgets/post/post_meta_editor.dart';
|
import 'package:surface/widgets/post/post_meta_editor.dart';
|
||||||
|
import 'package:surface/widgets/post/post_poll.dart';
|
||||||
import 'package:surface/widgets/post/post_reaction.dart';
|
import 'package:surface/widgets/post/post_reaction.dart';
|
||||||
import 'package:surface/widgets/post/publisher_popover.dart';
|
import 'package:surface/widgets/post/publisher_popover.dart';
|
||||||
import 'package:surface/widgets/universal_image.dart';
|
import 'package:surface/widgets/universal_image.dart';
|
||||||
@ -69,32 +70,34 @@ class OpenablePostItem extends StatelessWidget {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final cfg = context.read<ConfigProvider>();
|
final cfg = context.read<ConfigProvider>();
|
||||||
|
|
||||||
return OpenContainer(
|
return Center(
|
||||||
closedBuilder: (_, __) => Container(
|
child: OpenContainer(
|
||||||
constraints: BoxConstraints(maxWidth: maxWidth ?? double.infinity),
|
closedBuilder: (_, __) => Container(
|
||||||
child: PostItem(
|
constraints: BoxConstraints(maxWidth: maxWidth ?? double.infinity),
|
||||||
data: data,
|
child: PostItem(
|
||||||
maxWidth: maxWidth,
|
data: data,
|
||||||
showComments: showComments,
|
maxWidth: maxWidth,
|
||||||
showFullPost: showFullPost,
|
showComments: showComments,
|
||||||
onChanged: onChanged,
|
showFullPost: showFullPost,
|
||||||
onDeleted: onDeleted,
|
onChanged: onChanged,
|
||||||
onSelectAnswer: onSelectAnswer,
|
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)),
|
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)),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -193,6 +196,57 @@ class PostItem extends StatelessWidget {
|
|||||||
final ua = context.read<UserProvider>();
|
final ua = context.read<UserProvider>();
|
||||||
final isAuthor = ua.isAuthorized && data.publisher.accountId == ua.user?.id;
|
final isAuthor = ua.isAuthorized && data.publisher.accountId == ua.user?.id;
|
||||||
|
|
||||||
|
// Video full view
|
||||||
|
if (showFullPost && data.type == 'video' && ResponsiveBreakpoints.of(context).largerThan(TABLET)) {
|
||||||
|
return Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
const Gap(16),
|
||||||
|
Expanded(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
_PostContentHeader(
|
||||||
|
data: data,
|
||||||
|
isAuthor: isAuthor,
|
||||||
|
isRelativeDate: !showFullPost,
|
||||||
|
onShare: () => _doShare(context),
|
||||||
|
onShareImage: () => _doShareViaPicture(context),
|
||||||
|
onSelectAnswer: onSelectAnswer,
|
||||||
|
onDeleted: () {
|
||||||
|
if (onDeleted != null) {}
|
||||||
|
},
|
||||||
|
).padding(bottom: 8),
|
||||||
|
if (data.preload?.video != null) _PostVideoPlayer(data: data).padding(bottom: 8),
|
||||||
|
_PostHeadline(data: data).padding(horizontal: 4, bottom: 8),
|
||||||
|
_PostFeaturedComment(data: data),
|
||||||
|
_PostBottomAction(
|
||||||
|
data: data,
|
||||||
|
showComments: true,
|
||||||
|
showReactions: showReactions,
|
||||||
|
onShare: () => _doShare(context),
|
||||||
|
onShareImage: () => _doShareViaPicture(context),
|
||||||
|
onChanged: _onChanged,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const Gap(4),
|
||||||
|
SizedBox(
|
||||||
|
width: 340,
|
||||||
|
child: CustomScrollView(
|
||||||
|
shrinkWrap: true,
|
||||||
|
slivers: [
|
||||||
|
PostCommentSliverList(
|
||||||
|
parentPost: data,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// Article headline preview
|
// Article headline preview
|
||||||
if (!showFullPost && data.type == 'article') {
|
if (!showFullPost && data.type == 'article') {
|
||||||
return Container(
|
return Container(
|
||||||
@ -336,6 +390,7 @@ class PostItem extends StatelessWidget {
|
|||||||
fit: showFullPost ? BoxFit.cover : BoxFit.contain,
|
fit: showFullPost ? BoxFit.cover : BoxFit.contain,
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 12),
|
padding: const EdgeInsets.symmetric(horizontal: 12),
|
||||||
),
|
),
|
||||||
|
if (data.preload?.poll != null) PostPoll(poll: data.preload!.poll!).padding(horizontal: 12, vertical: 4),
|
||||||
if (data.body['content'] != null && (cfg.prefs.getBool(kAppExpandPostLink) ?? true))
|
if (data.body['content'] != null && (cfg.prefs.getBool(kAppExpandPostLink) ?? true))
|
||||||
LinkPreviewWidget(
|
LinkPreviewWidget(
|
||||||
text: data.body['content'],
|
text: data.body['content'],
|
||||||
|
138
lib/widgets/post/post_poll.dart
Normal file
138
lib/widgets/post/post_poll.dart
Normal file
@ -0,0 +1,138 @@
|
|||||||
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
|
import 'package:material_symbols_icons/symbols.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
import 'package:styled_widget/styled_widget.dart';
|
||||||
|
import 'package:surface/providers/sn_network.dart';
|
||||||
|
import 'package:surface/providers/userinfo.dart';
|
||||||
|
import 'package:surface/types/poll.dart';
|
||||||
|
import 'package:surface/widgets/dialog.dart';
|
||||||
|
|
||||||
|
class PostPoll extends StatefulWidget {
|
||||||
|
final SnPoll poll;
|
||||||
|
|
||||||
|
const PostPoll({super.key, required this.poll});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<PostPoll> createState() => _PostPollState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _PostPollState extends State<PostPoll> {
|
||||||
|
bool _isBusy = false;
|
||||||
|
late SnPoll _poll;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
_poll = widget.poll;
|
||||||
|
_fetchAnswer();
|
||||||
|
super.initState();
|
||||||
|
}
|
||||||
|
|
||||||
|
String? _answeredChoice;
|
||||||
|
|
||||||
|
Future<void> _refreshPoll() async {
|
||||||
|
final sn = context.read<SnNetworkProvider>();
|
||||||
|
final resp = await sn.client.get('/cgi/co/polls/${widget.poll.id}');
|
||||||
|
if (!mounted) return;
|
||||||
|
setState(() => _poll = SnPoll.fromJson(resp.data!));
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _fetchAnswer() async {
|
||||||
|
final ua = context.read<UserProvider>();
|
||||||
|
if (!ua.isAuthorized) return;
|
||||||
|
try {
|
||||||
|
setState(() => _isBusy = true);
|
||||||
|
final sn = context.read<SnNetworkProvider>();
|
||||||
|
final resp =
|
||||||
|
await sn.client.get('/cgi/co/polls/${widget.poll.id}/answer');
|
||||||
|
_answeredChoice = resp.data?['answer'];
|
||||||
|
if (!mounted) return;
|
||||||
|
setState(() {});
|
||||||
|
} catch (err) {
|
||||||
|
if (!mounted) return;
|
||||||
|
// ignore because it may not found
|
||||||
|
} finally {
|
||||||
|
setState(() => _isBusy = false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _voteForOption(SnPollOption option) async {
|
||||||
|
final ua = context.read<UserProvider>();
|
||||||
|
if (!ua.isAuthorized) return;
|
||||||
|
try {
|
||||||
|
setState(() => _isBusy = true);
|
||||||
|
final sn = context.read<SnNetworkProvider>();
|
||||||
|
await sn.client.post('/cgi/co/polls/${widget.poll.id}/answer', data: {
|
||||||
|
'answer': option.id,
|
||||||
|
});
|
||||||
|
if (!mounted) return;
|
||||||
|
HapticFeedback.heavyImpact();
|
||||||
|
_answeredChoice = option.id;
|
||||||
|
_refreshPoll();
|
||||||
|
} catch (err) {
|
||||||
|
if (!mounted) return;
|
||||||
|
context.showErrorDialog(err);
|
||||||
|
} finally {
|
||||||
|
setState(() => _isBusy = false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Card(
|
||||||
|
margin: EdgeInsets.zero,
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
for (final option in _poll.options)
|
||||||
|
Stack(
|
||||||
|
children: [
|
||||||
|
ClipRRect(
|
||||||
|
borderRadius: const BorderRadius.all(Radius.circular(8)),
|
||||||
|
child: Container(
|
||||||
|
height: 60,
|
||||||
|
width: MediaQuery.of(context).size.width *
|
||||||
|
(_poll.metric.byOptionsPercentage[option.id] ?? 0)
|
||||||
|
.toDouble(),
|
||||||
|
color: Theme.of(context).colorScheme.surfaceContainerHigh,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
ListTile(
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
),
|
||||||
|
minTileHeight: 60,
|
||||||
|
leading: _answeredChoice == option.id
|
||||||
|
? const Icon(Symbols.circle, fill: 1)
|
||||||
|
: const Icon(Symbols.circle),
|
||||||
|
title: Text(option.name),
|
||||||
|
subtitle: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
'pollVotes'
|
||||||
|
.plural(_poll.metric.byOptions[option.id] ?? 0),
|
||||||
|
),
|
||||||
|
Text(' · ').padding(horizontal: 4),
|
||||||
|
Text(
|
||||||
|
'${((_poll.metric.byOptionsPercentage[option.id] ?? 0).toDouble() * 100).toStringAsFixed(2)}%',
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
if (option.description.isNotEmpty)
|
||||||
|
Text(option.description),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
onTap: _isBusy ? null : () => _voteForOption(option),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
201
lib/widgets/post/post_poll_editor.dart
Normal file
201
lib/widgets/post/post_poll_editor.dart
Normal file
@ -0,0 +1,201 @@
|
|||||||
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:material_symbols_icons/symbols.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
import 'package:surface/providers/sn_network.dart';
|
||||||
|
import 'package:surface/types/poll.dart';
|
||||||
|
import 'package:surface/widgets/dialog.dart';
|
||||||
|
import 'package:uuid/uuid.dart';
|
||||||
|
|
||||||
|
class PollEditorDialog extends StatefulWidget {
|
||||||
|
final SnPoll? poll;
|
||||||
|
|
||||||
|
const PollEditorDialog({super.key, this.poll});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<PollEditorDialog> createState() => _PollEditorDialogState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _PollEditorDialogState extends State<PollEditorDialog> {
|
||||||
|
final TextEditingController _linkController = TextEditingController();
|
||||||
|
final List<SnPollOption> _pollOptions = List.empty(growable: true);
|
||||||
|
|
||||||
|
bool _isBusy = false;
|
||||||
|
|
||||||
|
Future<void> _fetchPoll() async {
|
||||||
|
if (_linkController.text.isEmpty) return;
|
||||||
|
try {
|
||||||
|
setState(() => _isBusy = true);
|
||||||
|
final sn = context.read<SnNetworkProvider>();
|
||||||
|
final resp = await sn.client.get('/cgi/co/polls/${_linkController.text}');
|
||||||
|
final out = SnPoll.fromJson(resp.data);
|
||||||
|
if (!mounted) return;
|
||||||
|
Navigator.pop(context, out);
|
||||||
|
} catch (err) {
|
||||||
|
if (!mounted) return;
|
||||||
|
context.showErrorDialog(err);
|
||||||
|
} finally {
|
||||||
|
setState(() => _isBusy = false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _applyPost() async {
|
||||||
|
try {
|
||||||
|
setState(() => _isBusy = true);
|
||||||
|
final sn = context.read<SnNetworkProvider>();
|
||||||
|
final resp = widget.poll == null
|
||||||
|
? await sn.client.post('/cgi/co/polls', data: {
|
||||||
|
'options': _pollOptions.where((ele) => ele.name.isNotEmpty).toList(),
|
||||||
|
})
|
||||||
|
: await sn.client.put('/cgi/co/polls/${widget.poll!.id}', data: {
|
||||||
|
'options': _pollOptions.where((ele) => ele.name.isNotEmpty).toList(),
|
||||||
|
});
|
||||||
|
final out = SnPoll.fromJson(resp.data);
|
||||||
|
if (!mounted) return;
|
||||||
|
Navigator.pop(context, out);
|
||||||
|
} catch (err) {
|
||||||
|
if (!mounted) return;
|
||||||
|
context.showErrorDialog(err);
|
||||||
|
} finally {
|
||||||
|
setState(() => _isBusy = false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _deletePoll() async {
|
||||||
|
final confirm = await context.showConfirmDialog(
|
||||||
|
'pollEditorDelete'.tr(),
|
||||||
|
'pollEditorDeleteDescription'.tr(),
|
||||||
|
);
|
||||||
|
if (!confirm) return;
|
||||||
|
if (!mounted) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
setState(() => _isBusy = true);
|
||||||
|
final sn = context.read<SnNetworkProvider>();
|
||||||
|
await sn.client.delete('/cgi/co/polls/${widget.poll!.id}');
|
||||||
|
if (!mounted) return;
|
||||||
|
Navigator.pop(context, false);
|
||||||
|
} catch (err) {
|
||||||
|
if (!mounted) return;
|
||||||
|
context.showErrorDialog(err);
|
||||||
|
} finally {
|
||||||
|
setState(() => _isBusy = false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_pollOptions.addAll(widget.poll?.options ?? []);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_linkController.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return AlertDialog(
|
||||||
|
title: Text(widget.poll == null ? 'pollEditorNew' : 'pollEditorEdit').tr(),
|
||||||
|
content: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
spacing: 16,
|
||||||
|
children: [
|
||||||
|
if (widget.poll == null)
|
||||||
|
TextField(
|
||||||
|
controller: _linkController,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
isDense: true,
|
||||||
|
labelText: 'pollLinkExisting'.tr(),
|
||||||
|
prefixText: '#',
|
||||||
|
suffixIcon: IconButton(
|
||||||
|
visualDensity: const VisualDensity(horizontal: -4, vertical: -4),
|
||||||
|
constraints: const BoxConstraints(),
|
||||||
|
padding: EdgeInsets.zero,
|
||||||
|
onPressed: _isBusy ? null : () => _fetchPoll(),
|
||||||
|
icon: const Icon(Icons.keyboard_arrow_right),
|
||||||
|
),
|
||||||
|
border: const OutlineInputBorder(),
|
||||||
|
),
|
||||||
|
onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(),
|
||||||
|
),
|
||||||
|
Card(
|
||||||
|
margin: EdgeInsets.zero,
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
for (int i = 0; i < _pollOptions.length; i++)
|
||||||
|
ListTile(
|
||||||
|
leading: const Icon(Symbols.circle),
|
||||||
|
title: TextFormField(
|
||||||
|
decoration: InputDecoration.collapsed(
|
||||||
|
hintText: 'pollOptionName'.tr(),
|
||||||
|
),
|
||||||
|
onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(),
|
||||||
|
initialValue: _pollOptions[i].name,
|
||||||
|
onChanged: (value) {
|
||||||
|
// Looks like we don't need set state here cuz it got internal updated.
|
||||||
|
_pollOptions[i] = _pollOptions[i].copyWith(name: value);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
trailing: IconButton(
|
||||||
|
visualDensity: const VisualDensity(horizontal: -4, vertical: -4),
|
||||||
|
padding: EdgeInsets.zero,
|
||||||
|
constraints: const BoxConstraints(),
|
||||||
|
onPressed: () {
|
||||||
|
setState(() => _pollOptions.removeAt(i));
|
||||||
|
},
|
||||||
|
icon: const Icon(Icons.close),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
ListTile(
|
||||||
|
leading: const Icon(Symbols.add),
|
||||||
|
title: Text('pollOptionAdd').tr(),
|
||||||
|
onTap: () {
|
||||||
|
setState(
|
||||||
|
() => _pollOptions.add(
|
||||||
|
SnPollOption(id: const Uuid().v4(), icon: '', name: '', description: ''),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (widget.poll != null)
|
||||||
|
Card(
|
||||||
|
margin: EdgeInsets.zero,
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
ListTile(
|
||||||
|
leading: const Icon(Symbols.delete),
|
||||||
|
trailing: const Icon(Symbols.chevron_right),
|
||||||
|
title: Text('pollEditorDelete').tr(),
|
||||||
|
onTap: _isBusy ? null : () => _deletePoll(),
|
||||||
|
),
|
||||||
|
ListTile(
|
||||||
|
leading: const Icon(Symbols.link_off),
|
||||||
|
trailing: const Icon(Symbols.chevron_right),
|
||||||
|
title: Text('pollEditorUnlink').tr(),
|
||||||
|
onTap: _isBusy ? null : () => Navigator.pop(context, false),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
actions: [
|
||||||
|
TextButton(
|
||||||
|
onPressed: _isBusy ? null : () => Navigator.pop(context),
|
||||||
|
child: Text('cancel'.tr()),
|
||||||
|
),
|
||||||
|
TextButton(
|
||||||
|
onPressed: _isBusy ? null : () => _applyPost(),
|
||||||
|
child: Text('dialogConfirm'.tr()),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
44
pubspec.lock
44
pubspec.lock
@ -138,26 +138,26 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: build_daemon
|
name: build_daemon
|
||||||
sha256: "294a2edaf4814a378725bfe6358210196f5ea37af89ecd81bfa32960113d4948"
|
sha256: "8e928697a82be082206edb0b9c99c5a4ad6bc31c9e9b8b2f291ae65cd4a25daa"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "4.0.3"
|
version: "4.0.4"
|
||||||
build_resolvers:
|
build_resolvers:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: build_resolvers
|
name: build_resolvers
|
||||||
sha256: "99d3980049739a985cf9b21f30881f46db3ebc62c5b8d5e60e27440876b1ba1e"
|
sha256: b9e4fda21d846e192628e7a4f6deda6888c36b5b69ba02ff291a01fd529140f0
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.4.3"
|
version: "2.4.4"
|
||||||
build_runner:
|
build_runner:
|
||||||
dependency: "direct dev"
|
dependency: "direct dev"
|
||||||
description:
|
description:
|
||||||
name: build_runner
|
name: build_runner
|
||||||
sha256: "74691599a5bc750dc96a6b4bfd48f7d9d66453eab04c7f4063134800d6a5c573"
|
sha256: "058fe9dce1de7d69c4b84fada934df3e0153dd000758c4d65964d0166779aa99"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.4.14"
|
version: "2.4.15"
|
||||||
build_runner_core:
|
build_runner_core:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -354,10 +354,10 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: dart_webrtc
|
name: dart_webrtc
|
||||||
sha256: e65506edb452148220efab53d8d2f8bb9d827bd8bcd53cf3a3e6df70b27f3d86
|
sha256: "3b3ff59c66cbc1577ed0f28d7005b5163555208fb1697a42207424ab8baa27c5"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.4.10"
|
version: "1.5.0"
|
||||||
dbus:
|
dbus:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -498,10 +498,10 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: file_picker
|
name: file_picker
|
||||||
sha256: cacfdc5abe93e64d418caa9256eef663499ad791bb688d9fd12c85a311968fba
|
sha256: ab13ae8ef5580a411c458d6207b6774a6c237d77ac37011b13994879f68a8810
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "8.3.2"
|
version: "8.3.7"
|
||||||
file_saver:
|
file_saver:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@ -772,10 +772,10 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: flutter_markdown
|
name: flutter_markdown
|
||||||
sha256: b3ff1ef5fb3924ee02b4d38b974ffae3969d50603e68787684ee9dd45f6f144a
|
sha256: e7bbc718adc9476aa14cfddc1ef048d2e21e4e8f18311aaac723266db9f9e7b5
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.7.6+1"
|
version: "0.7.6+2"
|
||||||
flutter_native_splash:
|
flutter_native_splash:
|
||||||
dependency: "direct dev"
|
dependency: "direct dev"
|
||||||
description:
|
description:
|
||||||
@ -838,10 +838,10 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: flutter_webrtc
|
name: flutter_webrtc
|
||||||
sha256: e917067abeef2400e6a7a03db53a6e1418551e54809f18ab80447ac323eb77e4
|
sha256: "572df3de6c828e571db4b75b4a96a15c2f34fa3d420a84438f44a3158b22e81a"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.12.8"
|
version: "0.12.9"
|
||||||
freezed:
|
freezed:
|
||||||
dependency: "direct dev"
|
dependency: "direct dev"
|
||||||
description:
|
description:
|
||||||
@ -1766,18 +1766,18 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: shared_preferences
|
name: shared_preferences
|
||||||
sha256: "688ee90fbfb6989c980254a56cb26ebe9bb30a3a2dff439a78894211f73de67a"
|
sha256: "846849e3e9b68f3ef4b60c60cf4b3e02e9321bc7f4d8c4692cf87ffa82fc8a3a"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.5.1"
|
version: "2.5.2"
|
||||||
shared_preferences_android:
|
shared_preferences_android:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: shared_preferences_android
|
name: shared_preferences_android
|
||||||
sha256: "650584dcc0a39856f369782874e562efd002a9c94aec032412c9eb81419cce1f"
|
sha256: ea86be7b7114f9e94fddfbb52649e59a03d6627ccd2387ebddcd6624719e9f16
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.4.4"
|
version: "2.4.5"
|
||||||
shared_preferences_foundation:
|
shared_preferences_foundation:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -1830,10 +1830,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: shelf_web_socket
|
name: shelf_web_socket
|
||||||
sha256: cc36c297b52866d203dbf9332263c94becc2fe0ceaa9681d07b6ef9807023b67
|
sha256: "3632775c8e90d6c9712f883e633716432a27758216dfb61bd86a8321c0580925"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.0.1"
|
version: "3.0.0"
|
||||||
shortid:
|
shortid:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -2187,10 +2187,10 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: video_compress
|
name: video_compress
|
||||||
sha256: "5b42d89f3970c956bad7a86c29682b0892c11a4ddf95ae6e29897ee28788e377"
|
sha256: "31bc5cdb9a02ba666456e5e1907393c28e6e0e972980d7d8d619a7beda0d4f20"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.1.3"
|
version: "3.1.4"
|
||||||
vm_service:
|
vm_service:
|
||||||
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+65
|
version: 2.3.2+68
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: ^3.5.4
|
sdk: ^3.5.4
|
||||||
|
Reference in New Issue
Block a user