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.",
"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"
}

View File

@ -624,5 +624,13 @@
"realmCommunityHint": "该领域是一个社区领域,你可以自由加入。",
"realmCommunityPublicChannelsHint": "该领域包含的公共频道",
"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_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;

View File

@ -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,
),
);

View File

@ -16,7 +16,6 @@ import 'package:surface/providers/sn_network.dart';
import 'package:surface/providers/userinfo.dart';
import 'package:surface/types/post.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/loading_indicator.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: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;

View File

@ -18,8 +18,10 @@ import 'package:surface/providers/config.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/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.dart';
import 'package:uuid/uuid.dart';
import '../../widgets/attachment/attachment_input.dart';
class PostEditorExtra {
final String? text;
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
void dispose() {
_writeController.dispose();
@ -238,7 +256,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,7 +274,8 @@ class _PostEditorScreenState extends State<PostEditorScreen> {
onTapPublisher: _showPublisherPopup,
),
_ => const Placeholder(),
},
})
.padding(top: 8),
),
if (_writeController.attachments.isNotEmpty || _writeController.thumbnail != null)
Positioned(
@ -304,7 +323,6 @@ class _PostEditorScreenState extends State<PostEditorScreen> {
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
LoadingIndicator(isActive: _isLoading),
if (_writeController.isBusy && _writeController.progress != null)
TweenAnimationBuilder<double>(
tween: Tween(begin: 0, end: _writeController.progress),
@ -313,6 +331,8 @@ class _PostEditorScreenState extends State<PostEditorScreen> {
)
else if (_writeController.isBusy)
const LinearProgressIndicator(value: null, minHeight: 2),
LoadingIndicator(isActive: _isLoading),
const Gap(4),
Container(
child: _writeController.temporaryRestored
? 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(
bottom: MediaQuery.of(context).padding.bottom + 8,
top: 4,
),
),
],

View File

@ -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
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: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) =>

View File

@ -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.

View File

@ -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(

View File

@ -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(),
),
],
),

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