Compare commits
8 Commits
Author | SHA1 | Date | |
---|---|---|---|
5ddd4fed2e | |||
48b6d5f6c1 | |||
b83b0b5efb | |||
cb24bd953d | |||
4937dee182 | |||
d612097bb1 | |||
058d668b6b | |||
8b19462c3a |
@ -5,7 +5,7 @@ meta {
|
||||
}
|
||||
|
||||
post {
|
||||
url: {{endpoint}}/cgi/id/dev/notify/1
|
||||
url: {{endpoint}}/cgi/id/dev/notify/122
|
||||
body: json
|
||||
auth: inherit
|
||||
}
|
||||
@ -15,12 +15,9 @@ body:json {
|
||||
"client_id": "{{third_client_id}}",
|
||||
"client_secret":"{{third_client_tk}}",
|
||||
"type": "general",
|
||||
"subject": "测试",
|
||||
"subtitle": "Alphabot です",
|
||||
"content": "全新通知动画",
|
||||
"metadata": {
|
||||
"image": "D2EDbcrsTugs3xk5"
|
||||
},
|
||||
"subject": "处理该帐号 @solian 的决定",
|
||||
"subtitle": "违反用户协议",
|
||||
"content": "您的帐号违反了我们用户协议中关于冒充我们官方的行为,至此做出停权的决定。还请见谅。该决定是最终决定,不接受上诉。",
|
||||
"priority": 10
|
||||
}
|
||||
}
|
||||
|
@ -625,5 +625,18 @@
|
||||
"realmCommunityHint": "This realm is a community realm, you can freely join.",
|
||||
"realmCommunityPublicChannelsHint": "The public channels in this realm",
|
||||
"realmJoined": "Joined realm {}.",
|
||||
"join": "Join"
|
||||
"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"
|
||||
}
|
||||
}
|
||||
|
@ -624,5 +624,18 @@
|
||||
"realmCommunityHint": "该领域是一个社区领域,你可以自由加入。",
|
||||
"realmCommunityPublicChannelsHint": "该领域包含的公共频道",
|
||||
"realmJoined": "已加入领域 {}。",
|
||||
"join": "加入"
|
||||
"join": "加入",
|
||||
"pollEditorNew": "新投票",
|
||||
"pollEditorEdit": "编辑投票",
|
||||
"pollEditorDelete": "删除投票",
|
||||
"pollEditorDeleteDescription": "你确定要删除这个投票吗?该操作不可撤销。",
|
||||
"pollEditorUnlink": "解除链接",
|
||||
"pollOptionAdd": "添加选项",
|
||||
"pollOptionName": "选项名称",
|
||||
"pollLinkExisting": "链接现有投票",
|
||||
"pollAnswered": "答案已经反馈。",
|
||||
"pollVotes": {
|
||||
"one": "{} 票",
|
||||
"other": "{} 票"
|
||||
}
|
||||
}
|
||||
|
@ -16,6 +16,7 @@ import 'package:surface/providers/post.dart';
|
||||
import 'package:surface/providers/sn_attachment.dart';
|
||||
import 'package:surface/providers/sn_network.dart';
|
||||
import 'package:surface/types/attachment.dart';
|
||||
import 'package:surface/types/poll.dart';
|
||||
import 'package:surface/types/post.dart';
|
||||
import 'package:surface/widgets/dialog.dart';
|
||||
import 'package:surface/widgets/universal_image.dart';
|
||||
@ -199,6 +200,7 @@ class PostWriteController extends ChangeNotifier {
|
||||
List<PostWriteMedia> attachments = List.empty(growable: true);
|
||||
DateTime? publishedAt, publishedUntil;
|
||||
SnAttachment? videoAttachment;
|
||||
SnPoll? poll;
|
||||
|
||||
Future<void> fetchRelatedPost(
|
||||
BuildContext context, {
|
||||
@ -229,6 +231,7 @@ class PostWriteController extends ChangeNotifier {
|
||||
tags = List.from(post.tags.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)) ?? []);
|
||||
poll = post.preload?.poll;
|
||||
|
||||
if (post.preload?.thumbnail != null && (post.preload?.thumbnail?.rid.isNotEmpty ?? false)) {
|
||||
thumbnail = PostWriteMedia(post.preload!.thumbnail);
|
||||
@ -367,6 +370,7 @@ class PostWriteController extends ChangeNotifier {
|
||||
if (publishedUntil != null) 'published_until': publishedAt!.toUtc().toIso8601String(),
|
||||
if (replyingPost != null) 'reply_to': replyingPost!.toJson(),
|
||||
if (repostingPost != null) 'repost_to': repostingPost!.toJson(),
|
||||
if (poll != null) 'poll': poll!.toJson(),
|
||||
}),
|
||||
);
|
||||
});
|
||||
@ -396,6 +400,7 @@ class PostWriteController extends ChangeNotifier {
|
||||
if (data['published_until'] != null) publishedUntil = DateTime.tryParse(data['published_until'])?.toLocal();
|
||||
replyingPost = data['reply_to'] != null ? SnPost.fromJson(data['reply_to']) : null;
|
||||
repostingPost = data['repost_to'] != null ? SnPost.fromJson(data['repost_to']) : null;
|
||||
poll = data['poll'] != null ? SnPoll.fromJson(data['poll']) : null;
|
||||
temporaryRestored = true;
|
||||
notifyListeners();
|
||||
});
|
||||
@ -511,6 +516,7 @@ class PostWriteController extends ChangeNotifier {
|
||||
if (repostingPost != null) 'repost_to': repostingPost!.id,
|
||||
if (reward != null) 'reward': reward,
|
||||
if (videoAttachment != null) 'video': videoAttachment!.rid,
|
||||
if (poll != null) 'poll': poll!.id,
|
||||
},
|
||||
onSendProgress: (count, total) {
|
||||
progress = baseProgressVal + (count / total) * (kPostingProgressWeight / 2);
|
||||
@ -642,6 +648,11 @@ class PostWriteController extends ChangeNotifier {
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
void setPoll(SnPoll? value) {
|
||||
poll = value;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
void reset() {
|
||||
publishedAt = null;
|
||||
publishedUntil = null;
|
||||
|
@ -3,6 +3,7 @@ import 'package:provider/provider.dart';
|
||||
import 'package:surface/providers/sn_attachment.dart';
|
||||
import 'package:surface/providers/sn_network.dart';
|
||||
import 'package:surface/providers/user_directory.dart';
|
||||
import 'package:surface/types/poll.dart';
|
||||
import 'package:surface/types/post.dart';
|
||||
|
||||
class SnPostContentProvider {
|
||||
@ -16,6 +17,11 @@ class SnPostContentProvider {
|
||||
_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 {
|
||||
Set<String> rids = {};
|
||||
for (var i = 0; i < out.length; i++) {
|
||||
@ -35,11 +41,17 @@ class SnPostContentProvider {
|
||||
|
||||
final attachments = await _attach.getMultiple(rids.toList());
|
||||
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(
|
||||
preload: SnPostPreload(
|
||||
thumbnail: attachments.where((ele) => ele?.rid == out[i].body['thumbnail']).firstOrNull,
|
||||
attachments: attachments.where((ele) => out[i].body['attachments']?.contains(ele?.rid) ?? false).toList(),
|
||||
video: attachments.where((ele) => ele?.rid == out[i].body['video']).firstOrNull,
|
||||
poll: poll,
|
||||
),
|
||||
);
|
||||
}
|
||||
@ -67,11 +79,18 @@ class SnPostContentProvider {
|
||||
}
|
||||
|
||||
final attachments = await _attach.getMultiple(rids.toList());
|
||||
|
||||
SnPoll? poll;
|
||||
if (out.pollId != null) {
|
||||
poll = await _fetchPoll(out.pollId!);
|
||||
}
|
||||
|
||||
out = out.copyWith(
|
||||
preload: SnPostPreload(
|
||||
thumbnail: attachments.where((ele) => ele?.rid == out.body['thumbnail']).firstOrNull,
|
||||
attachments: attachments.where((ele) => out.body['attachments']?.contains(ele?.rid) ?? false).toList(),
|
||||
video: attachments.where((ele) => ele?.rid == out.body['video']).firstOrNull,
|
||||
poll: poll,
|
||||
),
|
||||
);
|
||||
|
||||
|
@ -42,22 +42,22 @@ class WebSocketProvider extends ChangeNotifier {
|
||||
_connectCompleter = null;
|
||||
}
|
||||
|
||||
_connectCompleter = Completer<void>();
|
||||
|
||||
if (!_ua.isAuthorized) return;
|
||||
if (isConnected || conn != null) {
|
||||
disconnect();
|
||||
}
|
||||
|
||||
final atk = await _sn.getFreshAtk();
|
||||
final uri = Uri.parse(
|
||||
'${_sn.client.options.baseUrl.replaceFirst('http', 'ws')}/ws?tk=$atk',
|
||||
);
|
||||
|
||||
isBusy = true;
|
||||
notifyListeners();
|
||||
|
||||
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);
|
||||
await conn!.ready;
|
||||
_wsStream = conn!.stream.asBroadcastStream();
|
||||
@ -82,6 +82,7 @@ class WebSocketProvider extends ChangeNotifier {
|
||||
isBusy = false;
|
||||
notifyListeners();
|
||||
_connectCompleter!.complete();
|
||||
_connectCompleter = null;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -178,6 +178,10 @@ class _AccountPublisherEditScreenState extends State<AccountPublisherEditScreen>
|
||||
final sn = context.read<SnNetworkProvider>();
|
||||
|
||||
return AppScaffold(
|
||||
appBar: AppBar(
|
||||
leading: PageBackButton(),
|
||||
title: Text('screenAccountPublisherEdit').tr(),
|
||||
),
|
||||
body: SingleChildScrollView(
|
||||
child: Column(
|
||||
children: [
|
||||
|
@ -6,7 +6,6 @@ import 'package:gap/gap.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:material_symbols_icons/symbols.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:responsive_framework/responsive_framework.dart';
|
||||
import 'package:styled_widget/styled_widget.dart';
|
||||
import 'package:surface/providers/post.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/post/post_comment_list.dart';
|
||||
import 'package:surface/widgets/post/post_item.dart';
|
||||
import 'package:surface/widgets/post/post_mini_editor.dart';
|
||||
|
||||
class PostDetailScreen extends StatefulWidget {
|
||||
final String slug;
|
||||
@ -64,7 +62,8 @@ class _PostDetailScreenState extends State<PostDetailScreen> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final ua = context.watch<UserProvider>();
|
||||
final devicePixelRatio = MediaQuery.of(context).devicePixelRatio;
|
||||
|
||||
final double maxWidth = _data?.type == 'video' ? double.infinity : 640;
|
||||
|
||||
return AppBackground(
|
||||
isRoot: widget.onBack != null,
|
||||
@ -114,7 +113,7 @@ class _PostDetailScreenState extends State<PostDetailScreen> {
|
||||
SliverToBoxAdapter(
|
||||
child: PostItem(
|
||||
data: _data!,
|
||||
maxWidth: 640,
|
||||
maxWidth: maxWidth,
|
||||
showComments: false,
|
||||
showFullPost: true,
|
||||
onChanged: (data) {
|
||||
@ -125,11 +124,11 @@ class _PostDetailScreenState extends State<PostDetailScreen> {
|
||||
},
|
||||
),
|
||||
),
|
||||
const SliverToBoxAdapter(child: Divider(height: 1)),
|
||||
if (_data != null)
|
||||
if (_data != null && _data!.type != 'video') const SliverToBoxAdapter(child: Divider(height: 1)),
|
||||
if (_data != null && _data!.type != 'video')
|
||||
SliverToBoxAdapter(
|
||||
child: Container(
|
||||
constraints: const BoxConstraints(maxWidth: 640),
|
||||
constraints: BoxConstraints(maxWidth: maxWidth),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
@ -142,51 +141,30 @@ class _PostDetailScreenState extends State<PostDetailScreen> {
|
||||
).padding(horizontal: 20, vertical: 12).center(),
|
||||
),
|
||||
),
|
||||
if (_data != null && ua.isAuthorized)
|
||||
if (_data != null && ua.isAuthorized && _data!.type != 'video')
|
||||
SliverToBoxAdapter(
|
||||
child: Container(
|
||||
height: 240,
|
||||
constraints: const BoxConstraints(maxWidth: 640),
|
||||
margin:
|
||||
ResponsiveBreakpoints.of(context).largerThan(MOBILE) ? const EdgeInsets.all(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: _data!.id,
|
||||
onPost: () {
|
||||
setState(() {
|
||||
_data = _data!.copyWith(
|
||||
metric: _data!.metric.copyWith(
|
||||
replyCount: _data!.metric.replyCount + 1,
|
||||
),
|
||||
);
|
||||
});
|
||||
_childListKey.currentState!.refresh();
|
||||
},
|
||||
),
|
||||
).center(),
|
||||
child: PostCommentQuickAction(
|
||||
parentPost: _data!,
|
||||
maxWidth: maxWidth,
|
||||
onPosted: () {
|
||||
setState(() {
|
||||
_data = _data!.copyWith(
|
||||
metric: _data!.metric.copyWith(
|
||||
replyCount: _data!.metric.replyCount + 1,
|
||||
),
|
||||
);
|
||||
});
|
||||
_childListKey.currentState!.refresh();
|
||||
},
|
||||
),
|
||||
),
|
||||
if (_data != null)
|
||||
if (_data != null && _data!.type != 'video')
|
||||
PostCommentSliverList(
|
||||
key: _childListKey,
|
||||
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)),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
@ -8,6 +8,7 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_context_menu/flutter_context_menu.dart';
|
||||
import 'package:gap/gap.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:hotkey_manager/hotkey_manager.dart';
|
||||
import 'package:material_symbols_icons/symbols.dart';
|
||||
import 'package:pasteboard/pasteboard.dart';
|
||||
@ -20,6 +21,7 @@ import 'package:surface/providers/sn_network.dart';
|
||||
import 'package:surface/types/attachment.dart';
|
||||
import 'package:surface/types/post.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/pending_attachment_alt.dart';
|
||||
import 'package:surface/widgets/attachment/pending_attachment_boost.dart';
|
||||
@ -30,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/dialog.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:surface/widgets/post/post_poll_editor.dart';
|
||||
import 'package:uuid/uuid.dart';
|
||||
|
||||
import '../../widgets/attachment/attachment_input.dart';
|
||||
|
||||
class PostEditorExtra {
|
||||
final String? text;
|
||||
final String? title;
|
||||
@ -90,8 +91,9 @@ class _PostEditorScreenState extends State<PostEditorScreen> {
|
||||
resp.data?.map((e) => SnPublisher.fromJson(e)) ?? [],
|
||||
);
|
||||
final beforeId = config.prefs.getInt('int_last_publisher_id');
|
||||
_writeController
|
||||
.setPublisher(_publishers?.where((ele) => ele.id == beforeId).firstOrNull ?? _publishers?.firstOrNull);
|
||||
_writeController.setPublisher(
|
||||
_publishers?.where((ele) => ele.id == beforeId).firstOrNull ??
|
||||
_publishers?.firstOrNull);
|
||||
} catch (err) {
|
||||
if (!mounted) return;
|
||||
context.showErrorDialog(err);
|
||||
@ -110,7 +112,9 @@ class _PostEditorScreenState extends State<PostEditorScreen> {
|
||||
|
||||
final HotKey _pasteHotKey = HotKey(
|
||||
key: PhysicalKeyboardKey.keyV,
|
||||
modifiers: [Platform.isMacOS ? HotKeyModifier.meta : HotKeyModifier.control],
|
||||
modifiers: [
|
||||
Platform.isMacOS ? HotKeyModifier.meta : HotKeyModifier.control
|
||||
],
|
||||
scope: HotKeyScope.inapp,
|
||||
);
|
||||
|
||||
@ -140,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
|
||||
void dispose() {
|
||||
_writeController.dispose();
|
||||
if (!kIsWeb && !(Platform.isAndroid || Platform.isIOS)) hotKeyManager.unregister(_pasteHotKey);
|
||||
if (!kIsWeb && !(Platform.isAndroid || Platform.isIOS))
|
||||
hotKeyManager.unregister(_pasteHotKey);
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@ -167,7 +189,8 @@ class _PostEditorScreenState extends State<PostEditorScreen> {
|
||||
if (widget.extraProps != null) {
|
||||
_writeController.contentController.text = widget.extraProps!.text ?? '';
|
||||
_writeController.titleController.text = widget.extraProps!.title ?? '';
|
||||
_writeController.descriptionController.text = widget.extraProps!.description ?? '';
|
||||
_writeController.descriptionController.text =
|
||||
widget.extraProps!.description ?? '';
|
||||
_writeController.addAttachments(widget.extraProps!.attachments ?? []);
|
||||
}
|
||||
}
|
||||
@ -188,7 +211,9 @@ class _PostEditorScreenState extends State<PostEditorScreen> {
|
||||
textAlign: TextAlign.center,
|
||||
text: TextSpan(children: [
|
||||
TextSpan(
|
||||
text: _writeController.title.isNotEmpty ? _writeController.title : 'untitled'.tr(),
|
||||
text: _writeController.title.isNotEmpty
|
||||
? _writeController.title
|
||||
: 'untitled'.tr(),
|
||||
style: Theme.of(context).textTheme.titleLarge!.copyWith(
|
||||
color: Theme.of(context).appBarTheme.foregroundColor!,
|
||||
),
|
||||
@ -215,7 +240,8 @@ class _PostEditorScreenState extends State<PostEditorScreen> {
|
||||
children: [
|
||||
if (_writeController.editingPost != null)
|
||||
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(
|
||||
border: Border(
|
||||
bottom: BorderSide(
|
||||
@ -229,7 +255,9 @@ class _PostEditorScreenState extends State<PostEditorScreen> {
|
||||
children: [
|
||||
const Icon(Icons.edit, size: 16),
|
||||
const Gap(10),
|
||||
Text('postEditingNotice').tr(args: ['@${_writeController.editingPost!.publisher.name}']),
|
||||
Text('postEditingNotice').tr(args: [
|
||||
'@${_writeController.editingPost!.publisher.name}'
|
||||
]),
|
||||
],
|
||||
),
|
||||
),
|
||||
@ -238,7 +266,7 @@ class _PostEditorScreenState extends State<PostEditorScreen> {
|
||||
children: [
|
||||
SingleChildScrollView(
|
||||
padding: EdgeInsets.only(bottom: 160),
|
||||
child: switch (_writeController.mode) {
|
||||
child: StyledWidget(switch (_writeController.mode) {
|
||||
'stories' => _PostStoryEditor(
|
||||
controller: _writeController,
|
||||
onTapPublisher: _showPublisherPopup,
|
||||
@ -256,9 +284,11 @@ class _PostEditorScreenState extends State<PostEditorScreen> {
|
||||
onTapPublisher: _showPublisherPopup,
|
||||
),
|
||||
_ => const Placeholder(),
|
||||
},
|
||||
})
|
||||
.padding(top: 8),
|
||||
),
|
||||
if (_writeController.attachments.isNotEmpty || _writeController.thumbnail != null)
|
||||
if (_writeController.attachments.isNotEmpty ||
|
||||
_writeController.thumbnail != null)
|
||||
Positioned(
|
||||
bottom: 0,
|
||||
left: 0,
|
||||
@ -268,7 +298,8 @@ class _PostEditorScreenState extends State<PostEditorScreen> {
|
||||
attachments: _writeController.attachments,
|
||||
isBusy: _writeController.isBusy,
|
||||
onUpload: (int idx) async {
|
||||
await _writeController.uploadSingleAttachment(context, idx);
|
||||
await _writeController.uploadSingleAttachment(
|
||||
context, idx);
|
||||
},
|
||||
onPostSetThumbnail: (int? idx) {
|
||||
_writeController.setThumbnail(idx);
|
||||
@ -277,10 +308,12 @@ class _PostEditorScreenState extends State<PostEditorScreen> {
|
||||
_writeController.contentController.text +=
|
||||
'\n';
|
||||
},
|
||||
onUpdate: (int idx, PostWriteMedia updatedMedia) async {
|
||||
onUpdate:
|
||||
(int idx, PostWriteMedia updatedMedia) async {
|
||||
_writeController.setIsBusy(true);
|
||||
try {
|
||||
_writeController.setAttachmentAt(idx, updatedMedia);
|
||||
_writeController.setAttachmentAt(
|
||||
idx, updatedMedia);
|
||||
} finally {
|
||||
_writeController.setIsBusy(false);
|
||||
}
|
||||
@ -293,7 +326,8 @@ class _PostEditorScreenState extends State<PostEditorScreen> {
|
||||
_writeController.setIsBusy(false);
|
||||
}
|
||||
},
|
||||
onUpdateBusy: (state) => _writeController.setIsBusy(state),
|
||||
onUpdateBusy: (state) =>
|
||||
_writeController.setIsBusy(state),
|
||||
).padding(bottom: 8),
|
||||
),
|
||||
],
|
||||
@ -304,24 +338,29 @@ class _PostEditorScreenState extends State<PostEditorScreen> {
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
LoadingIndicator(isActive: _isLoading),
|
||||
if (_writeController.isBusy && _writeController.progress != null)
|
||||
if (_writeController.isBusy &&
|
||||
_writeController.progress != null)
|
||||
TweenAnimationBuilder<double>(
|
||||
tween: Tween(begin: 0, end: _writeController.progress),
|
||||
duration: Duration(milliseconds: 300),
|
||||
builder: (context, value, _) => LinearProgressIndicator(value: value, minHeight: 2),
|
||||
builder: (context, value, _) =>
|
||||
LinearProgressIndicator(value: value, minHeight: 2),
|
||||
)
|
||||
else if (_writeController.isBusy)
|
||||
const LinearProgressIndicator(value: null, minHeight: 2),
|
||||
LoadingIndicator(isActive: _isLoading),
|
||||
const Gap(4),
|
||||
Container(
|
||||
child: _writeController.temporaryRestored
|
||||
? 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(
|
||||
border: Border(
|
||||
bottom: BorderSide(
|
||||
color: Theme.of(context).dividerColor,
|
||||
width: 1 / MediaQuery.of(context).devicePixelRatio,
|
||||
width: 1 /
|
||||
MediaQuery.of(context).devicePixelRatio,
|
||||
),
|
||||
),
|
||||
),
|
||||
@ -330,7 +369,9 @@ class _PostEditorScreenState extends State<PostEditorScreen> {
|
||||
children: [
|
||||
const Icon(Icons.restore, size: 20),
|
||||
const Gap(8),
|
||||
Expanded(child: Text('postLocalDraftRestored').tr()),
|
||||
Expanded(
|
||||
child:
|
||||
Text('postLocalDraftRestored').tr()),
|
||||
InkWell(
|
||||
child: Text('dialogDismiss').tr(),
|
||||
onTap: () {
|
||||
@ -341,8 +382,10 @@ class _PostEditorScreenState extends State<PostEditorScreen> {
|
||||
))
|
||||
: const SizedBox.shrink(),
|
||||
)
|
||||
.height(_writeController.temporaryRestored ? 32 : 0, animate: true)
|
||||
.animate(const Duration(milliseconds: 300), Curves.fastLinearToSlowEaseIn),
|
||||
.height(_writeController.temporaryRestored ? 32 : 0,
|
||||
animate: true)
|
||||
.animate(const Duration(milliseconds: 300),
|
||||
Curves.fastLinearToSlowEaseIn),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
@ -360,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(
|
||||
onPressed: (_writeController.isBusy || _writeController.publisher == null)
|
||||
onPressed: (_writeController.isBusy ||
|
||||
_writeController.publisher == null)
|
||||
? null
|
||||
: () {
|
||||
_writeController.sendPost(context).then((_) {
|
||||
@ -382,7 +445,6 @@ class _PostEditorScreenState extends State<PostEditorScreen> {
|
||||
],
|
||||
).padding(
|
||||
bottom: MediaQuery.of(context).padding.bottom + 8,
|
||||
top: 4,
|
||||
),
|
||||
),
|
||||
],
|
||||
@ -417,9 +479,21 @@ class _PostPublisherPopup extends StatelessWidget {
|
||||
children: [
|
||||
const Icon(Symbols.face, size: 24),
|
||||
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),
|
||||
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(
|
||||
child: ListView.builder(
|
||||
itemCount: publishers?.length ?? 0,
|
||||
@ -479,7 +553,8 @@ class _PostStoryEditor extends StatelessWidget {
|
||||
border: InputBorder.none,
|
||||
),
|
||||
style: Theme.of(context).textTheme.titleLarge,
|
||||
onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(),
|
||||
onTapOutside: (_) =>
|
||||
FocusManager.instance.primaryFocus?.unfocus(),
|
||||
).padding(horizontal: 16),
|
||||
const Gap(8),
|
||||
TextField(
|
||||
@ -494,7 +569,8 @@ class _PostStoryEditor extends StatelessWidget {
|
||||
),
|
||||
border: InputBorder.none,
|
||||
),
|
||||
onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(),
|
||||
onTapOutside: (_) =>
|
||||
FocusManager.instance.primaryFocus?.unfocus(),
|
||||
),
|
||||
],
|
||||
),
|
||||
@ -584,7 +660,8 @@ class _PostArticleEditor extends StatelessWidget {
|
||||
),
|
||||
border: InputBorder.none,
|
||||
),
|
||||
onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(),
|
||||
onTapOutside: (_) =>
|
||||
FocusManager.instance.primaryFocus?.unfocus(),
|
||||
),
|
||||
),
|
||||
const Gap(8),
|
||||
@ -663,7 +740,8 @@ class _PostQuestionEditor extends StatelessWidget {
|
||||
border: InputBorder.none,
|
||||
),
|
||||
style: Theme.of(context).textTheme.titleLarge,
|
||||
onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(),
|
||||
onTapOutside: (_) =>
|
||||
FocusManager.instance.primaryFocus?.unfocus(),
|
||||
).padding(horizontal: 16),
|
||||
const Gap(8),
|
||||
TextField(
|
||||
@ -674,7 +752,8 @@ class _PostQuestionEditor extends StatelessWidget {
|
||||
border: InputBorder.none,
|
||||
isCollapsed: true,
|
||||
),
|
||||
onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(),
|
||||
onTapOutside: (_) =>
|
||||
FocusManager.instance.primaryFocus?.unfocus(),
|
||||
).padding(horizontal: 16),
|
||||
const Gap(8),
|
||||
TextField(
|
||||
@ -689,7 +768,8 @@ class _PostQuestionEditor extends StatelessWidget {
|
||||
),
|
||||
border: InputBorder.none,
|
||||
),
|
||||
onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(),
|
||||
onTapOutside: (_) =>
|
||||
FocusManager.instance.primaryFocus?.unfocus(),
|
||||
),
|
||||
],
|
||||
),
|
||||
@ -725,7 +805,8 @@ class _PostVideoEditor extends StatelessWidget {
|
||||
|
||||
final result = await showDialog<SnAttachment?>(
|
||||
context: context,
|
||||
builder: (context) => PendingAttachmentAltDialog(media: PostWriteMedia(controller.videoAttachment)),
|
||||
builder: (context) => PendingAttachmentAltDialog(
|
||||
media: PostWriteMedia(controller.videoAttachment)),
|
||||
);
|
||||
if (result == null) return;
|
||||
|
||||
@ -737,7 +818,8 @@ class _PostVideoEditor extends StatelessWidget {
|
||||
|
||||
final result = await showDialog<SnAttachmentBoost?>(
|
||||
context: context,
|
||||
builder: (context) => PendingAttachmentBoostDialog(media: PostWriteMedia(controller.videoAttachment)),
|
||||
builder: (context) => PendingAttachmentBoostDialog(
|
||||
media: PostWriteMedia(controller.videoAttachment)),
|
||||
);
|
||||
if (result == null) return;
|
||||
|
||||
@ -780,7 +862,8 @@ class _PostVideoEditor extends StatelessWidget {
|
||||
|
||||
try {
|
||||
final sn = context.read<SnNetworkProvider>();
|
||||
await sn.client.delete('/cgi/uc/attachments/${controller.videoAttachment!.id}');
|
||||
await sn.client
|
||||
.delete('/cgi/uc/attachments/${controller.videoAttachment!.id}');
|
||||
controller.setVideoAttachment(null);
|
||||
} catch (err) {
|
||||
if (!context.mounted) return;
|
||||
@ -872,7 +955,8 @@ class _PostVideoEditor extends StatelessWidget {
|
||||
label: 'attachmentCopyRandomId'.tr(),
|
||||
icon: Symbols.content_copy,
|
||||
onSelected: () {
|
||||
Clipboard.setData(ClipboardData(text: controller.videoAttachment!.rid));
|
||||
Clipboard.setData(
|
||||
ClipboardData(text: controller.videoAttachment!.rid));
|
||||
},
|
||||
),
|
||||
MenuItem(
|
||||
@ -891,7 +975,9 @@ class _PostVideoEditor extends StatelessWidget {
|
||||
),
|
||||
child: InkWell(
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
onTap: controller.videoAttachment != null ? () => _selectVideo(context) : null,
|
||||
onTap: controller.videoAttachment == null
|
||||
? () => _selectVideo(context)
|
||||
: null,
|
||||
child: AspectRatio(
|
||||
aspectRatio: 16 / 9,
|
||||
child: controller.videoAttachment == null
|
||||
|
@ -1,7 +1,6 @@
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:gap/gap.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:material_symbols_icons/symbols.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:styled_widget/styled_widget.dart';
|
||||
@ -261,7 +260,7 @@ class _RealmJoinPopupState extends State<_RealmJoinPopup> {
|
||||
itemBuilder: (context, index) {
|
||||
final channel = _channels![index];
|
||||
return CheckboxListTile(
|
||||
value: _planJoinChannels.contains(channel.alias) ?? false,
|
||||
value: _planJoinChannels.contains(channel.alias),
|
||||
title: Text(channel.name),
|
||||
subtitle: Text(
|
||||
channel.description,
|
||||
|
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:surface/types/attachment.dart';
|
||||
import 'package:surface/types/poll.dart';
|
||||
|
||||
part 'post.freezed.dart';
|
||||
part 'post.g.dart';
|
||||
@ -37,6 +38,7 @@ class SnPost with _$SnPost {
|
||||
required int totalUpvote,
|
||||
required int totalDownvote,
|
||||
required int publisherId,
|
||||
required int? pollId,
|
||||
required SnPublisher publisher,
|
||||
required SnMetric metric,
|
||||
SnPostPreload? preload,
|
||||
@ -90,6 +92,7 @@ class SnPostPreload with _$SnPostPreload {
|
||||
required SnAttachment? thumbnail,
|
||||
required List<SnAttachment?>? attachments,
|
||||
required SnAttachment? video,
|
||||
required SnPoll? poll,
|
||||
}) = _SnPostPreload;
|
||||
|
||||
factory SnPostPreload.fromJson(Map<String, Object?> json) =>
|
||||
|
@ -48,6 +48,7 @@ mixin _$SnPost {
|
||||
int get totalUpvote => throw _privateConstructorUsedError;
|
||||
int get totalDownvote => throw _privateConstructorUsedError;
|
||||
int get publisherId => throw _privateConstructorUsedError;
|
||||
int? get pollId => throw _privateConstructorUsedError;
|
||||
SnPublisher get publisher => throw _privateConstructorUsedError;
|
||||
SnMetric get metric => throw _privateConstructorUsedError;
|
||||
SnPostPreload? get preload => throw _privateConstructorUsedError;
|
||||
@ -95,6 +96,7 @@ abstract class $SnPostCopyWith<$Res> {
|
||||
int totalUpvote,
|
||||
int totalDownvote,
|
||||
int publisherId,
|
||||
int? pollId,
|
||||
SnPublisher publisher,
|
||||
SnMetric metric,
|
||||
SnPostPreload? preload});
|
||||
@ -149,6 +151,7 @@ class _$SnPostCopyWithImpl<$Res, $Val extends SnPost>
|
||||
Object? totalUpvote = null,
|
||||
Object? totalDownvote = null,
|
||||
Object? publisherId = null,
|
||||
Object? pollId = freezed,
|
||||
Object? publisher = null,
|
||||
Object? metric = null,
|
||||
Object? preload = freezed,
|
||||
@ -266,6 +269,10 @@ class _$SnPostCopyWithImpl<$Res, $Val extends SnPost>
|
||||
? _value.publisherId
|
||||
: publisherId // ignore: cast_nullable_to_non_nullable
|
||||
as int,
|
||||
pollId: freezed == pollId
|
||||
? _value.pollId
|
||||
: pollId // ignore: cast_nullable_to_non_nullable
|
||||
as int?,
|
||||
publisher: null == publisher
|
||||
? _value.publisher
|
||||
: publisher // ignore: cast_nullable_to_non_nullable
|
||||
@ -380,6 +387,7 @@ abstract class _$$SnPostImplCopyWith<$Res> implements $SnPostCopyWith<$Res> {
|
||||
int totalUpvote,
|
||||
int totalDownvote,
|
||||
int publisherId,
|
||||
int? pollId,
|
||||
SnPublisher publisher,
|
||||
SnMetric metric,
|
||||
SnPostPreload? preload});
|
||||
@ -437,6 +445,7 @@ class __$$SnPostImplCopyWithImpl<$Res>
|
||||
Object? totalUpvote = null,
|
||||
Object? totalDownvote = null,
|
||||
Object? publisherId = null,
|
||||
Object? pollId = freezed,
|
||||
Object? publisher = null,
|
||||
Object? metric = null,
|
||||
Object? preload = freezed,
|
||||
@ -554,6 +563,10 @@ class __$$SnPostImplCopyWithImpl<$Res>
|
||||
? _value.publisherId
|
||||
: publisherId // ignore: cast_nullable_to_non_nullable
|
||||
as int,
|
||||
pollId: freezed == pollId
|
||||
? _value.pollId
|
||||
: pollId // ignore: cast_nullable_to_non_nullable
|
||||
as int?,
|
||||
publisher: null == publisher
|
||||
? _value.publisher
|
||||
: publisher // ignore: cast_nullable_to_non_nullable
|
||||
@ -602,6 +615,7 @@ class _$SnPostImpl extends _SnPost {
|
||||
required this.totalUpvote,
|
||||
required this.totalDownvote,
|
||||
required this.publisherId,
|
||||
required this.pollId,
|
||||
required this.publisher,
|
||||
required this.metric,
|
||||
this.preload})
|
||||
@ -719,6 +733,8 @@ class _$SnPostImpl extends _SnPost {
|
||||
@override
|
||||
final int publisherId;
|
||||
@override
|
||||
final int? pollId;
|
||||
@override
|
||||
final SnPublisher publisher;
|
||||
@override
|
||||
final SnMetric metric;
|
||||
@ -727,7 +743,7 @@ class _$SnPostImpl extends _SnPost {
|
||||
|
||||
@override
|
||||
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
|
||||
@ -782,6 +798,7 @@ class _$SnPostImpl extends _SnPost {
|
||||
other.totalDownvote == totalDownvote) &&
|
||||
(identical(other.publisherId, publisherId) ||
|
||||
other.publisherId == publisherId) &&
|
||||
(identical(other.pollId, pollId) || other.pollId == pollId) &&
|
||||
(identical(other.publisher, publisher) ||
|
||||
other.publisher == publisher) &&
|
||||
(identical(other.metric, metric) || other.metric == metric) &&
|
||||
@ -820,6 +837,7 @@ class _$SnPostImpl extends _SnPost {
|
||||
totalUpvote,
|
||||
totalDownvote,
|
||||
publisherId,
|
||||
pollId,
|
||||
publisher,
|
||||
metric,
|
||||
preload
|
||||
@ -871,6 +889,7 @@ abstract class _SnPost extends SnPost {
|
||||
required final int totalUpvote,
|
||||
required final int totalDownvote,
|
||||
required final int publisherId,
|
||||
required final int? pollId,
|
||||
required final SnPublisher publisher,
|
||||
required final SnMetric metric,
|
||||
final SnPostPreload? preload}) = _$SnPostImpl;
|
||||
@ -935,6 +954,8 @@ abstract class _SnPost extends SnPost {
|
||||
@override
|
||||
int get publisherId;
|
||||
@override
|
||||
int? get pollId;
|
||||
@override
|
||||
SnPublisher get publisher;
|
||||
@override
|
||||
SnMetric get metric;
|
||||
@ -1568,6 +1589,7 @@ mixin _$SnPostPreload {
|
||||
SnAttachment? get thumbnail => throw _privateConstructorUsedError;
|
||||
List<SnAttachment?>? get attachments => throw _privateConstructorUsedError;
|
||||
SnAttachment? get video => throw _privateConstructorUsedError;
|
||||
SnPoll? get poll => throw _privateConstructorUsedError;
|
||||
|
||||
/// Serializes this SnPostPreload to a JSON map.
|
||||
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
|
||||
@ -1588,10 +1610,12 @@ abstract class $SnPostPreloadCopyWith<$Res> {
|
||||
$Res call(
|
||||
{SnAttachment? thumbnail,
|
||||
List<SnAttachment?>? attachments,
|
||||
SnAttachment? video});
|
||||
SnAttachment? video,
|
||||
SnPoll? poll});
|
||||
|
||||
$SnAttachmentCopyWith<$Res>? get thumbnail;
|
||||
$SnAttachmentCopyWith<$Res>? get video;
|
||||
$SnPollCopyWith<$Res>? get poll;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
@ -1612,6 +1636,7 @@ class _$SnPostPreloadCopyWithImpl<$Res, $Val extends SnPostPreload>
|
||||
Object? thumbnail = freezed,
|
||||
Object? attachments = freezed,
|
||||
Object? video = freezed,
|
||||
Object? poll = freezed,
|
||||
}) {
|
||||
return _then(_value.copyWith(
|
||||
thumbnail: freezed == thumbnail
|
||||
@ -1626,6 +1651,10 @@ class _$SnPostPreloadCopyWithImpl<$Res, $Val extends SnPostPreload>
|
||||
? _value.video
|
||||
: video // ignore: cast_nullable_to_non_nullable
|
||||
as SnAttachment?,
|
||||
poll: freezed == poll
|
||||
? _value.poll
|
||||
: poll // ignore: cast_nullable_to_non_nullable
|
||||
as SnPoll?,
|
||||
) as $Val);
|
||||
}
|
||||
|
||||
@ -1656,6 +1685,20 @@ class _$SnPostPreloadCopyWithImpl<$Res, $Val extends SnPostPreload>
|
||||
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
|
||||
@ -1669,12 +1712,15 @@ abstract class _$$SnPostPreloadImplCopyWith<$Res>
|
||||
$Res call(
|
||||
{SnAttachment? thumbnail,
|
||||
List<SnAttachment?>? attachments,
|
||||
SnAttachment? video});
|
||||
SnAttachment? video,
|
||||
SnPoll? poll});
|
||||
|
||||
@override
|
||||
$SnAttachmentCopyWith<$Res>? get thumbnail;
|
||||
@override
|
||||
$SnAttachmentCopyWith<$Res>? get video;
|
||||
@override
|
||||
$SnPollCopyWith<$Res>? get poll;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
@ -1693,6 +1739,7 @@ class __$$SnPostPreloadImplCopyWithImpl<$Res>
|
||||
Object? thumbnail = freezed,
|
||||
Object? attachments = freezed,
|
||||
Object? video = freezed,
|
||||
Object? poll = freezed,
|
||||
}) {
|
||||
return _then(_$SnPostPreloadImpl(
|
||||
thumbnail: freezed == thumbnail
|
||||
@ -1707,6 +1754,10 @@ class __$$SnPostPreloadImplCopyWithImpl<$Res>
|
||||
? _value.video
|
||||
: video // ignore: cast_nullable_to_non_nullable
|
||||
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(
|
||||
{required this.thumbnail,
|
||||
required final List<SnAttachment?>? attachments,
|
||||
required this.video})
|
||||
required this.video,
|
||||
required this.poll})
|
||||
: _attachments = attachments;
|
||||
|
||||
factory _$SnPostPreloadImpl.fromJson(Map<String, dynamic> json) =>
|
||||
@ -1737,10 +1789,12 @@ class _$SnPostPreloadImpl implements _SnPostPreload {
|
||||
|
||||
@override
|
||||
final SnAttachment? video;
|
||||
@override
|
||||
final SnPoll? poll;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'SnPostPreload(thumbnail: $thumbnail, attachments: $attachments, video: $video)';
|
||||
return 'SnPostPreload(thumbnail: $thumbnail, attachments: $attachments, video: $video, poll: $poll)';
|
||||
}
|
||||
|
||||
@override
|
||||
@ -1752,13 +1806,14 @@ class _$SnPostPreloadImpl implements _SnPostPreload {
|
||||
other.thumbnail == thumbnail) &&
|
||||
const DeepCollectionEquality()
|
||||
.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)
|
||||
@override
|
||||
int get hashCode => Object.hash(runtimeType, thumbnail,
|
||||
const DeepCollectionEquality().hash(_attachments), video);
|
||||
const DeepCollectionEquality().hash(_attachments), video, poll);
|
||||
|
||||
/// Create a copy of SnPostPreload
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@ -1780,7 +1835,8 @@ abstract class _SnPostPreload implements SnPostPreload {
|
||||
const factory _SnPostPreload(
|
||||
{required final SnAttachment? thumbnail,
|
||||
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) =
|
||||
_$SnPostPreloadImpl.fromJson;
|
||||
@ -1791,6 +1847,8 @@ abstract class _SnPostPreload implements SnPostPreload {
|
||||
List<SnAttachment?>? get attachments;
|
||||
@override
|
||||
SnAttachment? get video;
|
||||
@override
|
||||
SnPoll? get poll;
|
||||
|
||||
/// Create a copy of SnPostPreload
|
||||
/// 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(),
|
||||
totalDownvote: (json['total_downvote'] as num).toInt(),
|
||||
publisherId: (json['publisher_id'] as num).toInt(),
|
||||
pollId: (json['poll_id'] as num?)?.toInt(),
|
||||
publisher:
|
||||
SnPublisher.fromJson(json['publisher'] 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_downvote': instance.totalDownvote,
|
||||
'publisher_id': instance.publisherId,
|
||||
'poll_id': instance.pollId,
|
||||
'publisher': instance.publisher.toJson(),
|
||||
'metric': instance.metric.toJson(),
|
||||
'preload': instance.preload?.toJson(),
|
||||
@ -168,6 +170,9 @@ _$SnPostPreloadImpl _$$SnPostPreloadImplFromJson(Map<String, dynamic> json) =>
|
||||
video: json['video'] == null
|
||||
? null
|
||||
: 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) =>
|
||||
@ -175,6 +180,7 @@ Map<String, dynamic> _$$SnPostPreloadImplToJson(_$SnPostPreloadImpl instance) =>
|
||||
'thumbnail': instance.thumbnail?.toJson(),
|
||||
'attachments': instance.attachments?.map((e) => e?.toJson()).toList(),
|
||||
'video': instance.video?.toJson(),
|
||||
'poll': instance.poll?.toJson(),
|
||||
};
|
||||
|
||||
_$SnBodyImpl _$$SnBodyImplFromJson(Map<String, dynamic> json) => _$SnBodyImpl(
|
||||
|
@ -352,10 +352,9 @@ class ChatMessageInputState extends State<ChatMessageInput> {
|
||||
Symbols.mood,
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
visualDensity: const VisualDensity(
|
||||
horizontal: -4,
|
||||
vertical: -4,
|
||||
),
|
||||
visualDensity: const VisualDensity(horizontal: -4, vertical: -4),
|
||||
padding: EdgeInsets.zero,
|
||||
constraints: const BoxConstraints(),
|
||||
onPressed: () {
|
||||
_showEmojiPicker(context);
|
||||
},
|
||||
@ -373,10 +372,9 @@ class ChatMessageInputState extends State<ChatMessageInput> {
|
||||
Symbols.send,
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
visualDensity: const VisualDensity(
|
||||
horizontal: -4,
|
||||
vertical: -4,
|
||||
),
|
||||
visualDensity: const VisualDensity(horizontal: -4, vertical: -4),
|
||||
padding: EdgeInsets.zero,
|
||||
constraints: const BoxConstraints(),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
@ -4,6 +4,7 @@ import 'package:gap/gap.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:material_symbols_icons/symbols.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:responsive_framework/responsive_framework.dart';
|
||||
import 'package:styled_widget/styled_widget.dart';
|
||||
import 'package:surface/providers/post.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';
|
||||
|
||||
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 {
|
||||
final SnPost parentPost;
|
||||
final double? maxWidth;
|
||||
@ -71,6 +113,7 @@ class PostCommentSliverListState extends State<PostCommentSliverList> {
|
||||
|
||||
Future<void> refresh() async {
|
||||
_posts.clear();
|
||||
_postCount = null;
|
||||
_fetchPosts();
|
||||
}
|
||||
|
||||
|
@ -36,6 +36,7 @@ import 'package:surface/widgets/markdown_content.dart';
|
||||
import 'package:gap/gap.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_poll.dart';
|
||||
import 'package:surface/widgets/post/post_reaction.dart';
|
||||
import 'package:surface/widgets/post/publisher_popover.dart';
|
||||
import 'package:surface/widgets/universal_image.dart';
|
||||
@ -195,6 +196,57 @@ class PostItem extends StatelessWidget {
|
||||
final ua = context.read<UserProvider>();
|
||||
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
|
||||
if (!showFullPost && data.type == 'article') {
|
||||
return Container(
|
||||
@ -338,6 +390,7 @@ class PostItem extends StatelessWidget {
|
||||
fit: showFullPost ? BoxFit.cover : BoxFit.contain,
|
||||
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))
|
||||
LinkPreviewWidget(
|
||||
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
|
||||
description:
|
||||
name: build_daemon
|
||||
sha256: "294a2edaf4814a378725bfe6358210196f5ea37af89ecd81bfa32960113d4948"
|
||||
sha256: "8e928697a82be082206edb0b9c99c5a4ad6bc31c9e9b8b2f291ae65cd4a25daa"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.0.3"
|
||||
version: "4.0.4"
|
||||
build_resolvers:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: build_resolvers
|
||||
sha256: "99d3980049739a985cf9b21f30881f46db3ebc62c5b8d5e60e27440876b1ba1e"
|
||||
sha256: b9e4fda21d846e192628e7a4f6deda6888c36b5b69ba02ff291a01fd529140f0
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.4.3"
|
||||
version: "2.4.4"
|
||||
build_runner:
|
||||
dependency: "direct dev"
|
||||
description:
|
||||
name: build_runner
|
||||
sha256: "74691599a5bc750dc96a6b4bfd48f7d9d66453eab04c7f4063134800d6a5c573"
|
||||
sha256: "058fe9dce1de7d69c4b84fada934df3e0153dd000758c4d65964d0166779aa99"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.4.14"
|
||||
version: "2.4.15"
|
||||
build_runner_core:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -354,10 +354,10 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: dart_webrtc
|
||||
sha256: e65506edb452148220efab53d8d2f8bb9d827bd8bcd53cf3a3e6df70b27f3d86
|
||||
sha256: "3b3ff59c66cbc1577ed0f28d7005b5163555208fb1697a42207424ab8baa27c5"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.4.10"
|
||||
version: "1.5.0"
|
||||
dbus:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -498,10 +498,10 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: file_picker
|
||||
sha256: cacfdc5abe93e64d418caa9256eef663499ad791bb688d9fd12c85a311968fba
|
||||
sha256: ab13ae8ef5580a411c458d6207b6774a6c237d77ac37011b13994879f68a8810
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "8.3.2"
|
||||
version: "8.3.7"
|
||||
file_saver:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@ -772,10 +772,10 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: flutter_markdown
|
||||
sha256: b3ff1ef5fb3924ee02b4d38b974ffae3969d50603e68787684ee9dd45f6f144a
|
||||
sha256: e7bbc718adc9476aa14cfddc1ef048d2e21e4e8f18311aaac723266db9f9e7b5
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.7.6+1"
|
||||
version: "0.7.6+2"
|
||||
flutter_native_splash:
|
||||
dependency: "direct dev"
|
||||
description:
|
||||
@ -838,10 +838,10 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: flutter_webrtc
|
||||
sha256: e917067abeef2400e6a7a03db53a6e1418551e54809f18ab80447ac323eb77e4
|
||||
sha256: "572df3de6c828e571db4b75b4a96a15c2f34fa3d420a84438f44a3158b22e81a"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.12.8"
|
||||
version: "0.12.9"
|
||||
freezed:
|
||||
dependency: "direct dev"
|
||||
description:
|
||||
@ -1766,18 +1766,18 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: shared_preferences
|
||||
sha256: "688ee90fbfb6989c980254a56cb26ebe9bb30a3a2dff439a78894211f73de67a"
|
||||
sha256: "846849e3e9b68f3ef4b60c60cf4b3e02e9321bc7f4d8c4692cf87ffa82fc8a3a"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.5.1"
|
||||
version: "2.5.2"
|
||||
shared_preferences_android:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shared_preferences_android
|
||||
sha256: "650584dcc0a39856f369782874e562efd002a9c94aec032412c9eb81419cce1f"
|
||||
sha256: ea86be7b7114f9e94fddfbb52649e59a03d6627ccd2387ebddcd6624719e9f16
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.4.4"
|
||||
version: "2.4.5"
|
||||
shared_preferences_foundation:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -1830,10 +1830,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shelf_web_socket
|
||||
sha256: cc36c297b52866d203dbf9332263c94becc2fe0ceaa9681d07b6ef9807023b67
|
||||
sha256: "3632775c8e90d6c9712f883e633716432a27758216dfb61bd86a8321c0580925"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.1"
|
||||
version: "3.0.0"
|
||||
shortid:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -2187,10 +2187,10 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: video_compress
|
||||
sha256: "5b42d89f3970c956bad7a86c29682b0892c11a4ddf95ae6e29897ee28788e377"
|
||||
sha256: "31bc5cdb9a02ba666456e5e1907393c28e6e0e972980d7d8d619a7beda0d4f20"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.1.3"
|
||||
version: "3.1.4"
|
||||
vm_service:
|
||||
dependency: transitive
|
||||
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
|
||||
# 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.
|
||||
version: 2.3.2+66
|
||||
version: 2.3.2+68
|
||||
|
||||
environment:
|
||||
sdk: ^3.5.4
|
||||
|
Reference in New Issue
Block a user