Poll editor

This commit is contained in:
LittleSheep 2025-02-12 23:56:45 +08:00
parent d612097bb1
commit 4937dee182
16 changed files with 1187 additions and 29 deletions

View File

@ -625,5 +625,13 @@
"realmCommunityHint": "This realm is a community realm, you can freely join.", "realmCommunityHint": "This realm is a community realm, you can freely join.",
"realmCommunityPublicChannelsHint": "The public channels in this realm", "realmCommunityPublicChannelsHint": "The public channels in this realm",
"realmJoined": "Joined 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"
} }

View File

@ -624,5 +624,13 @@
"realmCommunityHint": "该领域是一个社区领域,你可以自由加入。", "realmCommunityHint": "该领域是一个社区领域,你可以自由加入。",
"realmCommunityPublicChannelsHint": "该领域包含的公共频道", "realmCommunityPublicChannelsHint": "该领域包含的公共频道",
"realmJoined": "已加入领域 {}。", "realmJoined": "已加入领域 {}。",
"join": "加入" "join": "加入",
"pollEditorNew": "新投票",
"pollEditorEdit": "编辑投票",
"pollEditorDelete": "删除投票",
"pollEditorDeleteDescription": "你确定要删除这个投票吗?该操作不可撤销。",
"pollEditorUnlink": "解除链接",
"pollOptionAdd": "添加选项",
"pollOptionName": "选项名称",
"pollLinkExisting": "链接现有投票"
} }

View File

@ -16,6 +16,7 @@ import 'package:surface/providers/post.dart';
import 'package:surface/providers/sn_attachment.dart'; import 'package:surface/providers/sn_attachment.dart';
import 'package:surface/providers/sn_network.dart'; import 'package:surface/providers/sn_network.dart';
import 'package:surface/types/attachment.dart'; import 'package:surface/types/attachment.dart';
import 'package:surface/types/poll.dart';
import 'package:surface/types/post.dart'; import 'package:surface/types/post.dart';
import 'package:surface/widgets/dialog.dart'; import 'package:surface/widgets/dialog.dart';
import 'package:surface/widgets/universal_image.dart'; import 'package:surface/widgets/universal_image.dart';
@ -199,6 +200,7 @@ class PostWriteController extends ChangeNotifier {
List<PostWriteMedia> attachments = List.empty(growable: true); List<PostWriteMedia> attachments = List.empty(growable: true);
DateTime? publishedAt, publishedUntil; DateTime? publishedAt, publishedUntil;
SnAttachment? videoAttachment; SnAttachment? videoAttachment;
SnPoll? poll;
Future<void> fetchRelatedPost( Future<void> fetchRelatedPost(
BuildContext context, { BuildContext context, {
@ -229,6 +231,7 @@ class PostWriteController extends ChangeNotifier {
tags = List.from(post.tags.map((ele) => ele.alias), growable: true); tags = List.from(post.tags.map((ele) => ele.alias), growable: true);
categories = List.from(post.categories.map((ele) => ele.alias), growable: true); categories = List.from(post.categories.map((ele) => ele.alias), growable: true);
attachments.addAll(post.preload?.attachments?.map((ele) => PostWriteMedia(ele)) ?? []); attachments.addAll(post.preload?.attachments?.map((ele) => PostWriteMedia(ele)) ?? []);
poll = post.preload?.poll;
if (post.preload?.thumbnail != null && (post.preload?.thumbnail?.rid.isNotEmpty ?? false)) { if (post.preload?.thumbnail != null && (post.preload?.thumbnail?.rid.isNotEmpty ?? false)) {
thumbnail = PostWriteMedia(post.preload!.thumbnail); thumbnail = PostWriteMedia(post.preload!.thumbnail);
@ -367,6 +370,7 @@ class PostWriteController extends ChangeNotifier {
if (publishedUntil != null) 'published_until': publishedAt!.toUtc().toIso8601String(), if (publishedUntil != null) 'published_until': publishedAt!.toUtc().toIso8601String(),
if (replyingPost != null) 'reply_to': replyingPost!.toJson(), if (replyingPost != null) 'reply_to': replyingPost!.toJson(),
if (repostingPost != null) 'repost_to': repostingPost!.toJson(), if (repostingPost != null) 'repost_to': repostingPost!.toJson(),
if (poll != null) 'poll': poll!.toJson(),
}), }),
); );
}); });
@ -396,6 +400,7 @@ class PostWriteController extends ChangeNotifier {
if (data['published_until'] != null) publishedUntil = DateTime.tryParse(data['published_until'])?.toLocal(); if (data['published_until'] != null) publishedUntil = DateTime.tryParse(data['published_until'])?.toLocal();
replyingPost = data['reply_to'] != null ? SnPost.fromJson(data['reply_to']) : null; replyingPost = data['reply_to'] != null ? SnPost.fromJson(data['reply_to']) : null;
repostingPost = data['repost_to'] != null ? SnPost.fromJson(data['repost_to']) : null; repostingPost = data['repost_to'] != null ? SnPost.fromJson(data['repost_to']) : null;
poll = data['poll'] != null ? SnPoll.fromJson(data['poll']) : null;
temporaryRestored = true; temporaryRestored = true;
notifyListeners(); notifyListeners();
}); });
@ -511,6 +516,7 @@ class PostWriteController extends ChangeNotifier {
if (repostingPost != null) 'repost_to': repostingPost!.id, if (repostingPost != null) 'repost_to': repostingPost!.id,
if (reward != null) 'reward': reward, if (reward != null) 'reward': reward,
if (videoAttachment != null) 'video': videoAttachment!.rid, if (videoAttachment != null) 'video': videoAttachment!.rid,
if (poll != null) 'poll': poll!.id,
}, },
onSendProgress: (count, total) { onSendProgress: (count, total) {
progress = baseProgressVal + (count / total) * (kPostingProgressWeight / 2); progress = baseProgressVal + (count / total) * (kPostingProgressWeight / 2);
@ -642,6 +648,11 @@ class PostWriteController extends ChangeNotifier {
notifyListeners(); notifyListeners();
} }
void setPoll(SnPoll? value) {
poll = value;
notifyListeners();
}
void reset() { void reset() {
publishedAt = null; publishedAt = null;
publishedUntil = null; publishedUntil = null;

View File

@ -3,6 +3,7 @@ import 'package:provider/provider.dart';
import 'package:surface/providers/sn_attachment.dart'; import 'package:surface/providers/sn_attachment.dart';
import 'package:surface/providers/sn_network.dart'; import 'package:surface/providers/sn_network.dart';
import 'package:surface/providers/user_directory.dart'; import 'package:surface/providers/user_directory.dart';
import 'package:surface/types/poll.dart';
import 'package:surface/types/post.dart'; import 'package:surface/types/post.dart';
class SnPostContentProvider { class SnPostContentProvider {
@ -16,6 +17,11 @@ class SnPostContentProvider {
_attach = context.read<SnAttachmentProvider>(); _attach = context.read<SnAttachmentProvider>();
} }
Future<SnPoll> _fetchPoll(int id) async {
final resp = await _sn.client.get('/cgi/co/polls/$id');
return SnPoll.fromJson(resp.data);
}
Future<List<SnPost>> _preloadRelatedDataInBatch(List<SnPost> out) async { Future<List<SnPost>> _preloadRelatedDataInBatch(List<SnPost> out) async {
Set<String> rids = {}; Set<String> rids = {};
for (var i = 0; i < out.length; i++) { for (var i = 0; i < out.length; i++) {
@ -35,11 +41,17 @@ class SnPostContentProvider {
final attachments = await _attach.getMultiple(rids.toList()); final attachments = await _attach.getMultiple(rids.toList());
for (var i = 0; i < out.length; i++) { for (var i = 0; i < out.length; i++) {
SnPoll? poll;
if (out[i].pollId != null) {
poll = await _fetchPoll(out[i].pollId!);
}
out[i] = out[i].copyWith( out[i] = out[i].copyWith(
preload: SnPostPreload( preload: SnPostPreload(
thumbnail: attachments.where((ele) => ele?.rid == out[i].body['thumbnail']).firstOrNull, thumbnail: attachments.where((ele) => ele?.rid == out[i].body['thumbnail']).firstOrNull,
attachments: attachments.where((ele) => out[i].body['attachments']?.contains(ele?.rid) ?? false).toList(), attachments: attachments.where((ele) => out[i].body['attachments']?.contains(ele?.rid) ?? false).toList(),
video: attachments.where((ele) => ele?.rid == out[i].body['video']).firstOrNull, video: attachments.where((ele) => ele?.rid == out[i].body['video']).firstOrNull,
poll: poll,
), ),
); );
} }
@ -67,11 +79,18 @@ class SnPostContentProvider {
} }
final attachments = await _attach.getMultiple(rids.toList()); final attachments = await _attach.getMultiple(rids.toList());
SnPoll? poll;
if (out.pollId != null) {
poll = await _fetchPoll(out.pollId!);
}
out = out.copyWith( out = out.copyWith(
preload: SnPostPreload( preload: SnPostPreload(
thumbnail: attachments.where((ele) => ele?.rid == out.body['thumbnail']).firstOrNull, thumbnail: attachments.where((ele) => ele?.rid == out.body['thumbnail']).firstOrNull,
attachments: attachments.where((ele) => out.body['attachments']?.contains(ele?.rid) ?? false).toList(), attachments: attachments.where((ele) => out.body['attachments']?.contains(ele?.rid) ?? false).toList(),
video: attachments.where((ele) => ele?.rid == out.body['video']).firstOrNull, video: attachments.where((ele) => ele?.rid == out.body['video']).firstOrNull,
poll: poll,
), ),
); );

View File

@ -16,7 +16,6 @@ import 'package:surface/providers/sn_network.dart';
import 'package:surface/providers/userinfo.dart'; import 'package:surface/providers/userinfo.dart';
import 'package:surface/types/post.dart'; import 'package:surface/types/post.dart';
import 'package:surface/widgets/account/account_image.dart'; import 'package:surface/widgets/account/account_image.dart';
import 'package:surface/widgets/app_bar_leading.dart';
import 'package:surface/widgets/dialog.dart'; import 'package:surface/widgets/dialog.dart';
import 'package:surface/widgets/loading_indicator.dart'; import 'package:surface/widgets/loading_indicator.dart';
import 'package:surface/widgets/navigation/app_scaffold.dart'; import 'package:surface/widgets/navigation/app_scaffold.dart';

View File

@ -6,7 +6,6 @@ import 'package:gap/gap.dart';
import 'package:go_router/go_router.dart'; import 'package:go_router/go_router.dart';
import 'package:material_symbols_icons/symbols.dart'; import 'package:material_symbols_icons/symbols.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:responsive_framework/responsive_framework.dart';
import 'package:styled_widget/styled_widget.dart'; import 'package:styled_widget/styled_widget.dart';
import 'package:surface/providers/post.dart'; import 'package:surface/providers/post.dart';
import 'package:surface/providers/userinfo.dart'; import 'package:surface/providers/userinfo.dart';
@ -17,7 +16,6 @@ import 'package:surface/widgets/navigation/app_background.dart';
import 'package:surface/widgets/navigation/app_scaffold.dart'; import 'package:surface/widgets/navigation/app_scaffold.dart';
import 'package:surface/widgets/post/post_comment_list.dart'; import 'package:surface/widgets/post/post_comment_list.dart';
import 'package:surface/widgets/post/post_item.dart'; import 'package:surface/widgets/post/post_item.dart';
import 'package:surface/widgets/post/post_mini_editor.dart';
class PostDetailScreen extends StatefulWidget { class PostDetailScreen extends StatefulWidget {
final String slug; final String slug;

View File

@ -18,8 +18,10 @@ import 'package:surface/providers/config.dart';
import 'package:surface/providers/sn_attachment.dart'; import 'package:surface/providers/sn_attachment.dart';
import 'package:surface/providers/sn_network.dart'; import 'package:surface/providers/sn_network.dart';
import 'package:surface/types/attachment.dart'; import 'package:surface/types/attachment.dart';
import 'package:surface/types/poll.dart';
import 'package:surface/types/post.dart'; import 'package:surface/types/post.dart';
import 'package:surface/widgets/account/account_image.dart'; import 'package:surface/widgets/account/account_image.dart';
import 'package:surface/widgets/attachment/attachment_input.dart';
import 'package:surface/widgets/attachment/attachment_item.dart'; import 'package:surface/widgets/attachment/attachment_item.dart';
import 'package:surface/widgets/attachment/pending_attachment_alt.dart'; import 'package:surface/widgets/attachment/pending_attachment_alt.dart';
import 'package:surface/widgets/attachment/pending_attachment_boost.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/post/post_meta_editor.dart';
import 'package:surface/widgets/dialog.dart'; import 'package:surface/widgets/dialog.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:surface/widgets/post/post_poll.dart';
import 'package:uuid/uuid.dart'; import 'package:uuid/uuid.dart';
import '../../widgets/attachment/attachment_input.dart';
class PostEditorExtra { class PostEditorExtra {
final String? text; final String? text;
final String? title; final String? title;
@ -140,6 +141,23 @@ class _PostEditorScreenState extends State<PostEditorScreen> {
); );
} }
void _showPollEditorDialog() async {
final poll = await showDialog<dynamic>(
context: context,
builder: (context) => PollEditorDialog(
poll: _writeController.poll,
),
);
if (poll == null) return;
if (!mounted) return;
if (poll == false) {
_writeController.setPoll(null);
} else {
_writeController.setPoll(poll);
}
}
@override @override
void dispose() { void dispose() {
_writeController.dispose(); _writeController.dispose();
@ -238,7 +256,7 @@ class _PostEditorScreenState extends State<PostEditorScreen> {
children: [ children: [
SingleChildScrollView( SingleChildScrollView(
padding: EdgeInsets.only(bottom: 160), padding: EdgeInsets.only(bottom: 160),
child: switch (_writeController.mode) { child: StyledWidget(switch (_writeController.mode) {
'stories' => _PostStoryEditor( 'stories' => _PostStoryEditor(
controller: _writeController, controller: _writeController,
onTapPublisher: _showPublisherPopup, onTapPublisher: _showPublisherPopup,
@ -256,7 +274,8 @@ class _PostEditorScreenState extends State<PostEditorScreen> {
onTapPublisher: _showPublisherPopup, onTapPublisher: _showPublisherPopup,
), ),
_ => const Placeholder(), _ => const Placeholder(),
}, })
.padding(top: 8),
), ),
if (_writeController.attachments.isNotEmpty || _writeController.thumbnail != null) if (_writeController.attachments.isNotEmpty || _writeController.thumbnail != null)
Positioned( Positioned(
@ -304,7 +323,6 @@ class _PostEditorScreenState extends State<PostEditorScreen> {
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
LoadingIndicator(isActive: _isLoading),
if (_writeController.isBusy && _writeController.progress != null) if (_writeController.isBusy && _writeController.progress != null)
TweenAnimationBuilder<double>( TweenAnimationBuilder<double>(
tween: Tween(begin: 0, end: _writeController.progress), tween: Tween(begin: 0, end: _writeController.progress),
@ -313,6 +331,8 @@ class _PostEditorScreenState extends State<PostEditorScreen> {
) )
else if (_writeController.isBusy) else if (_writeController.isBusy)
const LinearProgressIndicator(value: null, minHeight: 2), const LinearProgressIndicator(value: null, minHeight: 2),
LoadingIndicator(isActive: _isLoading),
const Gap(4),
Container( Container(
child: _writeController.temporaryRestored child: _writeController.temporaryRestored
? Container( ? Container(
@ -360,6 +380,18 @@ 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();
},
),
], ],
), ),
), ),
@ -382,7 +414,6 @@ class _PostEditorScreenState extends State<PostEditorScreen> {
], ],
).padding( ).padding(
bottom: MediaQuery.of(context).padding.bottom + 8, bottom: MediaQuery.of(context).padding.bottom + 8,
top: 4,
), ),
), ),
], ],

View File

@ -1,7 +1,6 @@
import 'package:easy_localization/easy_localization.dart'; import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:gap/gap.dart'; import 'package:gap/gap.dart';
import 'package:go_router/go_router.dart';
import 'package:material_symbols_icons/symbols.dart'; import 'package:material_symbols_icons/symbols.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:styled_widget/styled_widget.dart'; import 'package:styled_widget/styled_widget.dart';
@ -261,7 +260,7 @@ class _RealmJoinPopupState extends State<_RealmJoinPopup> {
itemBuilder: (context, index) { itemBuilder: (context, index) {
final channel = _channels![index]; final channel = _channels![index];
return CheckboxListTile( return CheckboxListTile(
value: _planJoinChannels.contains(channel.alias) ?? false, value: _planJoinChannels.contains(channel.alias),
title: Text(channel.name), title: Text(channel.name),
subtitle: Text( subtitle: Text(
channel.description, channel.description,

45
lib/types/poll.dart Normal file
View 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,
required dynamic byOptions,
}) = _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);
}

714
lib/types/poll.freezed.dart Normal file
View File

@ -0,0 +1,714 @@
// 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;
dynamic get byOptions => 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, dynamic byOptions});
}
/// @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 = freezed,
}) {
return _then(_value.copyWith(
totalAnswer: null == totalAnswer
? _value.totalAnswer
: totalAnswer // ignore: cast_nullable_to_non_nullable
as int,
byOptions: freezed == byOptions
? _value.byOptions
: byOptions // ignore: cast_nullable_to_non_nullable
as dynamic,
) 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, dynamic byOptions});
}
/// @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 = freezed,
}) {
return _then(_$SnPollMetricImpl(
totalAnswer: null == totalAnswer
? _value.totalAnswer
: totalAnswer // ignore: cast_nullable_to_non_nullable
as int,
byOptions: freezed == byOptions
? _value.byOptions
: byOptions // ignore: cast_nullable_to_non_nullable
as dynamic,
));
}
}
/// @nodoc
@JsonSerializable()
class _$SnPollMetricImpl implements _SnPollMetric {
const _$SnPollMetricImpl(
{required this.totalAnswer, required this.byOptions});
factory _$SnPollMetricImpl.fromJson(Map<String, dynamic> json) =>
_$$SnPollMetricImplFromJson(json);
@override
final int totalAnswer;
@override
final dynamic byOptions;
@override
String toString() {
return 'SnPollMetric(totalAnswer: $totalAnswer, byOptions: $byOptions)';
}
@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));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(
runtimeType, totalAnswer, const DeepCollectionEquality().hash(byOptions));
/// 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,
required final dynamic byOptions}) = _$SnPollMetricImpl;
factory _SnPollMetric.fromJson(Map<String, dynamic> json) =
_$SnPollMetricImpl.fromJson;
@override
int get totalAnswer;
@override
dynamic get byOptions;
/// 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;
}

60
lib/types/poll.g.dart Normal file
View File

@ -0,0 +1,60 @@
// 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'],
);
Map<String, dynamic> _$$SnPollMetricImplToJson(_$SnPollMetricImpl instance) =>
<String, dynamic>{
'total_answer': instance.totalAnswer,
'by_options': instance.byOptions,
};
_$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,
};

View File

@ -1,5 +1,6 @@
import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:surface/types/attachment.dart'; import 'package:surface/types/attachment.dart';
import 'package:surface/types/poll.dart';
part 'post.freezed.dart'; part 'post.freezed.dart';
part 'post.g.dart'; part 'post.g.dart';
@ -37,6 +38,7 @@ class SnPost with _$SnPost {
required int totalUpvote, required int totalUpvote,
required int totalDownvote, required int totalDownvote,
required int publisherId, required int publisherId,
required int? pollId,
required SnPublisher publisher, required SnPublisher publisher,
required SnMetric metric, required SnMetric metric,
SnPostPreload? preload, SnPostPreload? preload,
@ -90,6 +92,7 @@ class SnPostPreload with _$SnPostPreload {
required SnAttachment? thumbnail, required SnAttachment? thumbnail,
required List<SnAttachment?>? attachments, required List<SnAttachment?>? attachments,
required SnAttachment? video, required SnAttachment? video,
required SnPoll? poll,
}) = _SnPostPreload; }) = _SnPostPreload;
factory SnPostPreload.fromJson(Map<String, Object?> json) => factory SnPostPreload.fromJson(Map<String, Object?> json) =>

View File

@ -48,6 +48,7 @@ mixin _$SnPost {
int get totalUpvote => throw _privateConstructorUsedError; int get totalUpvote => throw _privateConstructorUsedError;
int get totalDownvote => throw _privateConstructorUsedError; int get totalDownvote => throw _privateConstructorUsedError;
int get publisherId => throw _privateConstructorUsedError; int get publisherId => throw _privateConstructorUsedError;
int? get pollId => throw _privateConstructorUsedError;
SnPublisher get publisher => throw _privateConstructorUsedError; SnPublisher get publisher => throw _privateConstructorUsedError;
SnMetric get metric => throw _privateConstructorUsedError; SnMetric get metric => throw _privateConstructorUsedError;
SnPostPreload? get preload => throw _privateConstructorUsedError; SnPostPreload? get preload => throw _privateConstructorUsedError;
@ -95,6 +96,7 @@ abstract class $SnPostCopyWith<$Res> {
int totalUpvote, int totalUpvote,
int totalDownvote, int totalDownvote,
int publisherId, int publisherId,
int? pollId,
SnPublisher publisher, SnPublisher publisher,
SnMetric metric, SnMetric metric,
SnPostPreload? preload}); SnPostPreload? preload});
@ -149,6 +151,7 @@ class _$SnPostCopyWithImpl<$Res, $Val extends SnPost>
Object? totalUpvote = null, Object? totalUpvote = null,
Object? totalDownvote = null, Object? totalDownvote = null,
Object? publisherId = null, Object? publisherId = null,
Object? pollId = freezed,
Object? publisher = null, Object? publisher = null,
Object? metric = null, Object? metric = null,
Object? preload = freezed, Object? preload = freezed,
@ -266,6 +269,10 @@ class _$SnPostCopyWithImpl<$Res, $Val extends SnPost>
? _value.publisherId ? _value.publisherId
: publisherId // ignore: cast_nullable_to_non_nullable : publisherId // ignore: cast_nullable_to_non_nullable
as int, as int,
pollId: freezed == pollId
? _value.pollId
: pollId // ignore: cast_nullable_to_non_nullable
as int?,
publisher: null == publisher publisher: null == publisher
? _value.publisher ? _value.publisher
: publisher // ignore: cast_nullable_to_non_nullable : publisher // ignore: cast_nullable_to_non_nullable
@ -380,6 +387,7 @@ abstract class _$$SnPostImplCopyWith<$Res> implements $SnPostCopyWith<$Res> {
int totalUpvote, int totalUpvote,
int totalDownvote, int totalDownvote,
int publisherId, int publisherId,
int? pollId,
SnPublisher publisher, SnPublisher publisher,
SnMetric metric, SnMetric metric,
SnPostPreload? preload}); SnPostPreload? preload});
@ -437,6 +445,7 @@ class __$$SnPostImplCopyWithImpl<$Res>
Object? totalUpvote = null, Object? totalUpvote = null,
Object? totalDownvote = null, Object? totalDownvote = null,
Object? publisherId = null, Object? publisherId = null,
Object? pollId = freezed,
Object? publisher = null, Object? publisher = null,
Object? metric = null, Object? metric = null,
Object? preload = freezed, Object? preload = freezed,
@ -554,6 +563,10 @@ class __$$SnPostImplCopyWithImpl<$Res>
? _value.publisherId ? _value.publisherId
: publisherId // ignore: cast_nullable_to_non_nullable : publisherId // ignore: cast_nullable_to_non_nullable
as int, as int,
pollId: freezed == pollId
? _value.pollId
: pollId // ignore: cast_nullable_to_non_nullable
as int?,
publisher: null == publisher publisher: null == publisher
? _value.publisher ? _value.publisher
: publisher // ignore: cast_nullable_to_non_nullable : publisher // ignore: cast_nullable_to_non_nullable
@ -602,6 +615,7 @@ class _$SnPostImpl extends _SnPost {
required this.totalUpvote, required this.totalUpvote,
required this.totalDownvote, required this.totalDownvote,
required this.publisherId, required this.publisherId,
required this.pollId,
required this.publisher, required this.publisher,
required this.metric, required this.metric,
this.preload}) this.preload})
@ -719,6 +733,8 @@ class _$SnPostImpl extends _SnPost {
@override @override
final int publisherId; final int publisherId;
@override @override
final int? pollId;
@override
final SnPublisher publisher; final SnPublisher publisher;
@override @override
final SnMetric metric; final SnMetric metric;
@ -727,7 +743,7 @@ class _$SnPostImpl extends _SnPost {
@override @override
String toString() { String toString() {
return 'SnPost(id: $id, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt, type: $type, body: $body, language: $language, alias: $alias, aliasPrefix: $aliasPrefix, tags: $tags, categories: $categories, replies: $replies, replyId: $replyId, repostId: $repostId, replyTo: $replyTo, repostTo: $repostTo, visibleUsersList: $visibleUsersList, invisibleUsersList: $invisibleUsersList, visibility: $visibility, editedAt: $editedAt, pinnedAt: $pinnedAt, lockedAt: $lockedAt, isDraft: $isDraft, publishedAt: $publishedAt, publishedUntil: $publishedUntil, totalUpvote: $totalUpvote, totalDownvote: $totalDownvote, publisherId: $publisherId, publisher: $publisher, metric: $metric, preload: $preload)'; return 'SnPost(id: $id, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt, type: $type, body: $body, language: $language, alias: $alias, aliasPrefix: $aliasPrefix, tags: $tags, categories: $categories, replies: $replies, replyId: $replyId, repostId: $repostId, replyTo: $replyTo, repostTo: $repostTo, visibleUsersList: $visibleUsersList, invisibleUsersList: $invisibleUsersList, visibility: $visibility, editedAt: $editedAt, pinnedAt: $pinnedAt, lockedAt: $lockedAt, isDraft: $isDraft, publishedAt: $publishedAt, publishedUntil: $publishedUntil, totalUpvote: $totalUpvote, totalDownvote: $totalDownvote, publisherId: $publisherId, pollId: $pollId, publisher: $publisher, metric: $metric, preload: $preload)';
} }
@override @override
@ -782,6 +798,7 @@ class _$SnPostImpl extends _SnPost {
other.totalDownvote == totalDownvote) && other.totalDownvote == totalDownvote) &&
(identical(other.publisherId, publisherId) || (identical(other.publisherId, publisherId) ||
other.publisherId == publisherId) && other.publisherId == publisherId) &&
(identical(other.pollId, pollId) || other.pollId == pollId) &&
(identical(other.publisher, publisher) || (identical(other.publisher, publisher) ||
other.publisher == publisher) && other.publisher == publisher) &&
(identical(other.metric, metric) || other.metric == metric) && (identical(other.metric, metric) || other.metric == metric) &&
@ -820,6 +837,7 @@ class _$SnPostImpl extends _SnPost {
totalUpvote, totalUpvote,
totalDownvote, totalDownvote,
publisherId, publisherId,
pollId,
publisher, publisher,
metric, metric,
preload preload
@ -871,6 +889,7 @@ abstract class _SnPost extends SnPost {
required final int totalUpvote, required final int totalUpvote,
required final int totalDownvote, required final int totalDownvote,
required final int publisherId, required final int publisherId,
required final int? pollId,
required final SnPublisher publisher, required final SnPublisher publisher,
required final SnMetric metric, required final SnMetric metric,
final SnPostPreload? preload}) = _$SnPostImpl; final SnPostPreload? preload}) = _$SnPostImpl;
@ -935,6 +954,8 @@ abstract class _SnPost extends SnPost {
@override @override
int get publisherId; int get publisherId;
@override @override
int? get pollId;
@override
SnPublisher get publisher; SnPublisher get publisher;
@override @override
SnMetric get metric; SnMetric get metric;
@ -1568,6 +1589,7 @@ mixin _$SnPostPreload {
SnAttachment? get thumbnail => throw _privateConstructorUsedError; SnAttachment? get thumbnail => throw _privateConstructorUsedError;
List<SnAttachment?>? get attachments => throw _privateConstructorUsedError; List<SnAttachment?>? get attachments => throw _privateConstructorUsedError;
SnAttachment? get video => throw _privateConstructorUsedError; SnAttachment? get video => throw _privateConstructorUsedError;
SnPoll? get poll => throw _privateConstructorUsedError;
/// Serializes this SnPostPreload to a JSON map. /// Serializes this SnPostPreload to a JSON map.
Map<String, dynamic> toJson() => throw _privateConstructorUsedError; Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
@ -1588,10 +1610,12 @@ abstract class $SnPostPreloadCopyWith<$Res> {
$Res call( $Res call(
{SnAttachment? thumbnail, {SnAttachment? thumbnail,
List<SnAttachment?>? attachments, List<SnAttachment?>? attachments,
SnAttachment? video}); SnAttachment? video,
SnPoll? poll});
$SnAttachmentCopyWith<$Res>? get thumbnail; $SnAttachmentCopyWith<$Res>? get thumbnail;
$SnAttachmentCopyWith<$Res>? get video; $SnAttachmentCopyWith<$Res>? get video;
$SnPollCopyWith<$Res>? get poll;
} }
/// @nodoc /// @nodoc
@ -1612,6 +1636,7 @@ class _$SnPostPreloadCopyWithImpl<$Res, $Val extends SnPostPreload>
Object? thumbnail = freezed, Object? thumbnail = freezed,
Object? attachments = freezed, Object? attachments = freezed,
Object? video = freezed, Object? video = freezed,
Object? poll = freezed,
}) { }) {
return _then(_value.copyWith( return _then(_value.copyWith(
thumbnail: freezed == thumbnail thumbnail: freezed == thumbnail
@ -1626,6 +1651,10 @@ class _$SnPostPreloadCopyWithImpl<$Res, $Val extends SnPostPreload>
? _value.video ? _value.video
: video // ignore: cast_nullable_to_non_nullable : video // ignore: cast_nullable_to_non_nullable
as SnAttachment?, as SnAttachment?,
poll: freezed == poll
? _value.poll
: poll // ignore: cast_nullable_to_non_nullable
as SnPoll?,
) as $Val); ) as $Val);
} }
@ -1656,6 +1685,20 @@ class _$SnPostPreloadCopyWithImpl<$Res, $Val extends SnPostPreload>
return _then(_value.copyWith(video: value) as $Val); return _then(_value.copyWith(video: value) as $Val);
}); });
} }
/// Create a copy of SnPostPreload
/// with the given fields replaced by the non-null parameter values.
@override
@pragma('vm:prefer-inline')
$SnPollCopyWith<$Res>? get poll {
if (_value.poll == null) {
return null;
}
return $SnPollCopyWith<$Res>(_value.poll!, (value) {
return _then(_value.copyWith(poll: value) as $Val);
});
}
} }
/// @nodoc /// @nodoc
@ -1669,12 +1712,15 @@ abstract class _$$SnPostPreloadImplCopyWith<$Res>
$Res call( $Res call(
{SnAttachment? thumbnail, {SnAttachment? thumbnail,
List<SnAttachment?>? attachments, List<SnAttachment?>? attachments,
SnAttachment? video}); SnAttachment? video,
SnPoll? poll});
@override @override
$SnAttachmentCopyWith<$Res>? get thumbnail; $SnAttachmentCopyWith<$Res>? get thumbnail;
@override @override
$SnAttachmentCopyWith<$Res>? get video; $SnAttachmentCopyWith<$Res>? get video;
@override
$SnPollCopyWith<$Res>? get poll;
} }
/// @nodoc /// @nodoc
@ -1693,6 +1739,7 @@ class __$$SnPostPreloadImplCopyWithImpl<$Res>
Object? thumbnail = freezed, Object? thumbnail = freezed,
Object? attachments = freezed, Object? attachments = freezed,
Object? video = freezed, Object? video = freezed,
Object? poll = freezed,
}) { }) {
return _then(_$SnPostPreloadImpl( return _then(_$SnPostPreloadImpl(
thumbnail: freezed == thumbnail thumbnail: freezed == thumbnail
@ -1707,6 +1754,10 @@ class __$$SnPostPreloadImplCopyWithImpl<$Res>
? _value.video ? _value.video
: video // ignore: cast_nullable_to_non_nullable : video // ignore: cast_nullable_to_non_nullable
as SnAttachment?, as SnAttachment?,
poll: freezed == poll
? _value.poll
: poll // ignore: cast_nullable_to_non_nullable
as SnPoll?,
)); ));
} }
} }
@ -1717,7 +1768,8 @@ class _$SnPostPreloadImpl implements _SnPostPreload {
const _$SnPostPreloadImpl( const _$SnPostPreloadImpl(
{required this.thumbnail, {required this.thumbnail,
required final List<SnAttachment?>? attachments, required final List<SnAttachment?>? attachments,
required this.video}) required this.video,
required this.poll})
: _attachments = attachments; : _attachments = attachments;
factory _$SnPostPreloadImpl.fromJson(Map<String, dynamic> json) => factory _$SnPostPreloadImpl.fromJson(Map<String, dynamic> json) =>
@ -1737,10 +1789,12 @@ class _$SnPostPreloadImpl implements _SnPostPreload {
@override @override
final SnAttachment? video; final SnAttachment? video;
@override
final SnPoll? poll;
@override @override
String toString() { String toString() {
return 'SnPostPreload(thumbnail: $thumbnail, attachments: $attachments, video: $video)'; return 'SnPostPreload(thumbnail: $thumbnail, attachments: $attachments, video: $video, poll: $poll)';
} }
@override @override
@ -1752,13 +1806,14 @@ class _$SnPostPreloadImpl implements _SnPostPreload {
other.thumbnail == thumbnail) && other.thumbnail == thumbnail) &&
const DeepCollectionEquality() const DeepCollectionEquality()
.equals(other._attachments, _attachments) && .equals(other._attachments, _attachments) &&
(identical(other.video, video) || other.video == video)); (identical(other.video, video) || other.video == video) &&
(identical(other.poll, poll) || other.poll == poll));
} }
@JsonKey(includeFromJson: false, includeToJson: false) @JsonKey(includeFromJson: false, includeToJson: false)
@override @override
int get hashCode => Object.hash(runtimeType, thumbnail, int get hashCode => Object.hash(runtimeType, thumbnail,
const DeepCollectionEquality().hash(_attachments), video); const DeepCollectionEquality().hash(_attachments), video, poll);
/// Create a copy of SnPostPreload /// Create a copy of SnPostPreload
/// with the given fields replaced by the non-null parameter values. /// with the given fields replaced by the non-null parameter values.
@ -1780,7 +1835,8 @@ abstract class _SnPostPreload implements SnPostPreload {
const factory _SnPostPreload( const factory _SnPostPreload(
{required final SnAttachment? thumbnail, {required final SnAttachment? thumbnail,
required final List<SnAttachment?>? attachments, required final List<SnAttachment?>? attachments,
required final SnAttachment? video}) = _$SnPostPreloadImpl; required final SnAttachment? video,
required final SnPoll? poll}) = _$SnPostPreloadImpl;
factory _SnPostPreload.fromJson(Map<String, dynamic> json) = factory _SnPostPreload.fromJson(Map<String, dynamic> json) =
_$SnPostPreloadImpl.fromJson; _$SnPostPreloadImpl.fromJson;
@ -1791,6 +1847,8 @@ abstract class _SnPostPreload implements SnPostPreload {
List<SnAttachment?>? get attachments; List<SnAttachment?>? get attachments;
@override @override
SnAttachment? get video; SnAttachment? get video;
@override
SnPoll? get poll;
/// Create a copy of SnPostPreload /// Create a copy of SnPostPreload
/// with the given fields replaced by the non-null parameter values. /// with the given fields replaced by the non-null parameter values.

View File

@ -63,6 +63,7 @@ _$SnPostImpl _$$SnPostImplFromJson(Map<String, dynamic> json) => _$SnPostImpl(
totalUpvote: (json['total_upvote'] as num).toInt(), totalUpvote: (json['total_upvote'] as num).toInt(),
totalDownvote: (json['total_downvote'] as num).toInt(), totalDownvote: (json['total_downvote'] as num).toInt(),
publisherId: (json['publisher_id'] as num).toInt(), publisherId: (json['publisher_id'] as num).toInt(),
pollId: (json['poll_id'] as num?)?.toInt(),
publisher: publisher:
SnPublisher.fromJson(json['publisher'] as Map<String, dynamic>), SnPublisher.fromJson(json['publisher'] as Map<String, dynamic>),
metric: SnMetric.fromJson(json['metric'] as Map<String, dynamic>), metric: SnMetric.fromJson(json['metric'] as Map<String, dynamic>),
@ -101,6 +102,7 @@ Map<String, dynamic> _$$SnPostImplToJson(_$SnPostImpl instance) =>
'total_upvote': instance.totalUpvote, 'total_upvote': instance.totalUpvote,
'total_downvote': instance.totalDownvote, 'total_downvote': instance.totalDownvote,
'publisher_id': instance.publisherId, 'publisher_id': instance.publisherId,
'poll_id': instance.pollId,
'publisher': instance.publisher.toJson(), 'publisher': instance.publisher.toJson(),
'metric': instance.metric.toJson(), 'metric': instance.metric.toJson(),
'preload': instance.preload?.toJson(), 'preload': instance.preload?.toJson(),
@ -168,6 +170,9 @@ _$SnPostPreloadImpl _$$SnPostPreloadImplFromJson(Map<String, dynamic> json) =>
video: json['video'] == null video: json['video'] == null
? null ? null
: SnAttachment.fromJson(json['video'] as Map<String, dynamic>), : SnAttachment.fromJson(json['video'] as Map<String, dynamic>),
poll: json['poll'] == null
? null
: SnPoll.fromJson(json['poll'] as Map<String, dynamic>),
); );
Map<String, dynamic> _$$SnPostPreloadImplToJson(_$SnPostPreloadImpl instance) => Map<String, dynamic> _$$SnPostPreloadImplToJson(_$SnPostPreloadImpl instance) =>
@ -175,6 +180,7 @@ Map<String, dynamic> _$$SnPostPreloadImplToJson(_$SnPostPreloadImpl instance) =>
'thumbnail': instance.thumbnail?.toJson(), 'thumbnail': instance.thumbnail?.toJson(),
'attachments': instance.attachments?.map((e) => e?.toJson()).toList(), 'attachments': instance.attachments?.map((e) => e?.toJson()).toList(),
'video': instance.video?.toJson(), 'video': instance.video?.toJson(),
'poll': instance.poll?.toJson(),
}; };
_$SnBodyImpl _$$SnBodyImplFromJson(Map<String, dynamic> json) => _$SnBodyImpl( _$SnBodyImpl _$$SnBodyImplFromJson(Map<String, dynamic> json) => _$SnBodyImpl(

View File

@ -352,10 +352,9 @@ class ChatMessageInputState extends State<ChatMessageInput> {
Symbols.mood, Symbols.mood,
color: Theme.of(context).colorScheme.primary, color: Theme.of(context).colorScheme.primary,
), ),
visualDensity: const VisualDensity( visualDensity: const VisualDensity(horizontal: -4, vertical: -4),
horizontal: -4, padding: EdgeInsets.zero,
vertical: -4, constraints: const BoxConstraints(),
),
onPressed: () { onPressed: () {
_showEmojiPicker(context); _showEmojiPicker(context);
}, },
@ -373,10 +372,9 @@ class ChatMessageInputState extends State<ChatMessageInput> {
Symbols.send, Symbols.send,
color: Theme.of(context).colorScheme.primary, color: Theme.of(context).colorScheme.primary,
), ),
visualDensity: const VisualDensity( visualDensity: const VisualDensity(horizontal: -4, vertical: -4),
horizontal: -4, padding: EdgeInsets.zero,
vertical: -4, constraints: const BoxConstraints(),
),
), ),
], ],
), ),

View 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()),
),
],
);
}
}