✨ Repostable and replyable post
This commit is contained in:
parent
0a8c9fb208
commit
c1e10916ee
@ -78,5 +78,7 @@
|
||||
"fieldPostTitle": "Title",
|
||||
"fieldPostDescription": "Description",
|
||||
"postPublish": "Publish",
|
||||
"postEditingNotice": "You're about to editing a post that posted {}."
|
||||
"postEditingNotice": "You're about to editing a post that posted {}.",
|
||||
"postReplyingNotice": "You're about to reply to a post that posted {}.",
|
||||
"postRepostingNotice": "You're about to repost a post that posted {}."
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"nextVersionAlert": "高强度开发提示",
|
||||
"nextVersionNotice": "您正在使用的是 Solian 2.0 的抢先体验版本,目前稳定分支(sn.solsynth.dev)版本为 1.4。该版本还在剧烈的开发中,部分功能可能不稳定,也并非所有功能都支持了。您可以通过 TestFlight 回滚到 1.4.X 或者继续体验新版本(sn-next.solsynth.dev)。",
|
||||
"nextVersionNotice": "您正在使用的是 Solian 2.0 的抢先体验版本,目前稳定分支(sn.solsynth.dev)版本为 1.4。该版本还在持续的开发中,部分功能可能不稳定,也并非所有功能都支持了。您可以通过 TestFlight 回滚到 1.4.X 或者继续体验新版本(sn-next.solsynth.dev)。",
|
||||
"screen": "页面",
|
||||
"screenHome": "首页",
|
||||
"screenExplore": "探索",
|
||||
@ -78,5 +78,7 @@
|
||||
"fieldPostTitle": "标题",
|
||||
"fieldPostDescription": "描述",
|
||||
"postPublish": "发布",
|
||||
"postEditingNotice": "你正在修改由 {} 发布的帖子。"
|
||||
"postEditingNotice": "你正在修改由 {} 发布的帖子。",
|
||||
"postReplyingNotice": "你正在回复由 {} 发布的帖子。",
|
||||
"postRepostingNotice": "你正在转发由 {} 发布的帖子。"
|
||||
}
|
||||
|
@ -191,6 +191,8 @@ class _PostEditorScreenState extends State<PostEditorScreen> {
|
||||
'title': _title,
|
||||
'description': _description,
|
||||
'attachments': _attachments.map((e) => e.rid).toList(),
|
||||
if (_replyingTo != null) 'reply_to': _replyingTo!.id,
|
||||
if (_repostingTo != null) 'repost_to': _repostingTo!.id,
|
||||
},
|
||||
onSendProgress: (count, total) {
|
||||
setState(() {
|
||||
@ -385,19 +387,58 @@ class _PostEditorScreenState extends State<PostEditorScreen> {
|
||||
const Divider(height: 1),
|
||||
Expanded(
|
||||
child: SingleChildScrollView(
|
||||
padding: EdgeInsets.only(
|
||||
top: _editingOg == null ? 8 : 0,
|
||||
bottom: 8,
|
||||
),
|
||||
padding: EdgeInsets.only(bottom: 8),
|
||||
child: Column(
|
||||
children: [
|
||||
// Replying Notice
|
||||
if (_replyingTo != null)
|
||||
Column(
|
||||
children: [
|
||||
Theme(
|
||||
data: Theme.of(context).copyWith(
|
||||
dividerColor: Colors.transparent,
|
||||
),
|
||||
child: ExpansionTile(
|
||||
minTileHeight: 48,
|
||||
leading: const Icon(Symbols.reply).padding(left: 4),
|
||||
title: Text('postReplyingNotice')
|
||||
.fontSize(15)
|
||||
.tr(args: ['@${_replyingTo!.publisher.name}']),
|
||||
children: <Widget>[PostItem(data: _replyingTo!)],
|
||||
),
|
||||
),
|
||||
const Divider(height: 1),
|
||||
],
|
||||
),
|
||||
// Reposting Notice
|
||||
if (_repostingTo != null)
|
||||
Column(
|
||||
children: [
|
||||
Theme(
|
||||
data: Theme.of(context).copyWith(
|
||||
dividerColor: Colors.transparent,
|
||||
),
|
||||
child: ExpansionTile(
|
||||
minTileHeight: 48,
|
||||
leading:
|
||||
const Icon(Symbols.forward).padding(left: 4),
|
||||
title: Text('postRepostingNotice')
|
||||
.fontSize(15)
|
||||
.tr(args: ['@${_repostingTo!.publisher.name}']),
|
||||
children: <Widget>[PostItem(data: _repostingTo!)],
|
||||
),
|
||||
),
|
||||
const Divider(height: 1),
|
||||
],
|
||||
),
|
||||
// Editing Notice
|
||||
if (_editingOg != null)
|
||||
Column(
|
||||
children: [
|
||||
Theme(
|
||||
data: Theme.of(context)
|
||||
.copyWith(dividerColor: Colors.transparent),
|
||||
data: Theme.of(context).copyWith(
|
||||
dividerColor: Colors.transparent,
|
||||
),
|
||||
child: ExpansionTile(
|
||||
minTileHeight: 48,
|
||||
leading:
|
||||
@ -405,13 +446,10 @@ class _PostEditorScreenState extends State<PostEditorScreen> {
|
||||
title: Text('postEditingNotice')
|
||||
.fontSize(15)
|
||||
.tr(args: ['@${_editingOg!.publisher.name}']),
|
||||
children: <Widget>[
|
||||
PostItem(data: _editingOg!),
|
||||
],
|
||||
children: <Widget>[PostItem(data: _editingOg!)],
|
||||
),
|
||||
),
|
||||
const Divider(height: 1),
|
||||
const Gap(8)
|
||||
],
|
||||
),
|
||||
// Content Input Area
|
||||
@ -430,7 +468,8 @@ class _PostEditorScreenState extends State<PostEditorScreen> {
|
||||
onTapOutside: (_) =>
|
||||
FocusManager.instance.primaryFocus?.unfocus(),
|
||||
)
|
||||
],
|
||||
].expand((ele) => [ele, const Gap(8)]).toList()
|
||||
..removeLast(),
|
||||
),
|
||||
),
|
||||
),
|
||||
@ -448,7 +487,7 @@ class _PostEditorScreenState extends State<PostEditorScreen> {
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
LoadingIndicator(isActive: _isBusy),
|
||||
LoadingIndicator(isActive: _isLoading),
|
||||
if (_isBusy && _progress != null)
|
||||
TweenAnimationBuilder<double>(
|
||||
tween: Tween(begin: 0, end: 1),
|
||||
|
@ -33,7 +33,7 @@ class SnPost with _$SnPost {
|
||||
required DateTime? pinnedAt,
|
||||
required DateTime? lockedAt,
|
||||
required bool isDraft,
|
||||
required DateTime publishedAt,
|
||||
required DateTime? publishedAt,
|
||||
required dynamic publishedUntil,
|
||||
required int totalUpvote,
|
||||
required int totalDownvote,
|
||||
|
@ -44,7 +44,7 @@ mixin _$SnPost {
|
||||
DateTime? get pinnedAt => throw _privateConstructorUsedError;
|
||||
DateTime? get lockedAt => throw _privateConstructorUsedError;
|
||||
bool get isDraft => throw _privateConstructorUsedError;
|
||||
DateTime get publishedAt => throw _privateConstructorUsedError;
|
||||
DateTime? get publishedAt => throw _privateConstructorUsedError;
|
||||
dynamic get publishedUntil => throw _privateConstructorUsedError;
|
||||
int get totalUpvote => throw _privateConstructorUsedError;
|
||||
int get totalDownvote => throw _privateConstructorUsedError;
|
||||
@ -94,7 +94,7 @@ abstract class $SnPostCopyWith<$Res> {
|
||||
DateTime? pinnedAt,
|
||||
DateTime? lockedAt,
|
||||
bool isDraft,
|
||||
DateTime publishedAt,
|
||||
DateTime? publishedAt,
|
||||
dynamic publishedUntil,
|
||||
int totalUpvote,
|
||||
int totalDownvote,
|
||||
@ -149,7 +149,7 @@ class _$SnPostCopyWithImpl<$Res, $Val extends SnPost>
|
||||
Object? pinnedAt = freezed,
|
||||
Object? lockedAt = freezed,
|
||||
Object? isDraft = null,
|
||||
Object? publishedAt = null,
|
||||
Object? publishedAt = freezed,
|
||||
Object? publishedUntil = freezed,
|
||||
Object? totalUpvote = null,
|
||||
Object? totalDownvote = null,
|
||||
@ -257,10 +257,10 @@ class _$SnPostCopyWithImpl<$Res, $Val extends SnPost>
|
||||
? _value.isDraft
|
||||
: isDraft // ignore: cast_nullable_to_non_nullable
|
||||
as bool,
|
||||
publishedAt: null == publishedAt
|
||||
publishedAt: freezed == publishedAt
|
||||
? _value.publishedAt
|
||||
: publishedAt // ignore: cast_nullable_to_non_nullable
|
||||
as DateTime,
|
||||
as DateTime?,
|
||||
publishedUntil: freezed == publishedUntil
|
||||
? _value.publishedUntil
|
||||
: publishedUntil // ignore: cast_nullable_to_non_nullable
|
||||
@ -367,7 +367,7 @@ abstract class _$$SnPostImplCopyWith<$Res> implements $SnPostCopyWith<$Res> {
|
||||
DateTime? pinnedAt,
|
||||
DateTime? lockedAt,
|
||||
bool isDraft,
|
||||
DateTime publishedAt,
|
||||
DateTime? publishedAt,
|
||||
dynamic publishedUntil,
|
||||
int totalUpvote,
|
||||
int totalDownvote,
|
||||
@ -423,7 +423,7 @@ class __$$SnPostImplCopyWithImpl<$Res>
|
||||
Object? pinnedAt = freezed,
|
||||
Object? lockedAt = freezed,
|
||||
Object? isDraft = null,
|
||||
Object? publishedAt = null,
|
||||
Object? publishedAt = freezed,
|
||||
Object? publishedUntil = freezed,
|
||||
Object? totalUpvote = null,
|
||||
Object? totalDownvote = null,
|
||||
@ -531,10 +531,10 @@ class __$$SnPostImplCopyWithImpl<$Res>
|
||||
? _value.isDraft
|
||||
: isDraft // ignore: cast_nullable_to_non_nullable
|
||||
as bool,
|
||||
publishedAt: null == publishedAt
|
||||
publishedAt: freezed == publishedAt
|
||||
? _value.publishedAt
|
||||
: publishedAt // ignore: cast_nullable_to_non_nullable
|
||||
as DateTime,
|
||||
as DateTime?,
|
||||
publishedUntil: freezed == publishedUntil
|
||||
? _value.publishedUntil
|
||||
: publishedUntil // ignore: cast_nullable_to_non_nullable
|
||||
@ -688,7 +688,7 @@ class _$SnPostImpl extends _SnPost {
|
||||
@override
|
||||
final bool isDraft;
|
||||
@override
|
||||
final DateTime publishedAt;
|
||||
final DateTime? publishedAt;
|
||||
@override
|
||||
final dynamic publishedUntil;
|
||||
@override
|
||||
@ -854,7 +854,7 @@ abstract class _SnPost extends SnPost {
|
||||
required final DateTime? pinnedAt,
|
||||
required final DateTime? lockedAt,
|
||||
required final bool isDraft,
|
||||
required final DateTime publishedAt,
|
||||
required final DateTime? publishedAt,
|
||||
required final dynamic publishedUntil,
|
||||
required final int totalUpvote,
|
||||
required final int totalDownvote,
|
||||
@ -917,7 +917,7 @@ abstract class _SnPost extends SnPost {
|
||||
@override
|
||||
bool get isDraft;
|
||||
@override
|
||||
DateTime get publishedAt;
|
||||
DateTime? get publishedAt;
|
||||
@override
|
||||
dynamic get publishedUntil;
|
||||
@override
|
||||
|
@ -39,7 +39,9 @@ _$SnPostImpl _$$SnPostImplFromJson(Map<String, dynamic> json) => _$SnPostImpl(
|
||||
? null
|
||||
: DateTime.parse(json['locked_at'] as String),
|
||||
isDraft: json['is_draft'] as bool,
|
||||
publishedAt: DateTime.parse(json['published_at'] as String),
|
||||
publishedAt: json['published_at'] == null
|
||||
? null
|
||||
: DateTime.parse(json['published_at'] as String),
|
||||
publishedUntil: json['published_until'],
|
||||
totalUpvote: (json['total_upvote'] as num).toInt(),
|
||||
totalDownvote: (json['total_downvote'] as num).toInt(),
|
||||
@ -80,7 +82,7 @@ Map<String, dynamic> _$$SnPostImplToJson(_$SnPostImpl instance) =>
|
||||
'pinned_at': instance.pinnedAt?.toIso8601String(),
|
||||
'locked_at': instance.lockedAt?.toIso8601String(),
|
||||
'is_draft': instance.isDraft,
|
||||
'published_at': instance.publishedAt.toIso8601String(),
|
||||
'published_at': instance.publishedAt?.toIso8601String(),
|
||||
'published_until': instance.publishedUntil,
|
||||
'total_upvote': instance.totalUpvote,
|
||||
'total_downvote': instance.totalDownvote,
|
||||
|
@ -2,8 +2,10 @@ import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:material_symbols_icons/symbols.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:relative_time/relative_time.dart';
|
||||
import 'package:styled_widget/styled_widget.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/attachment/attachment_list.dart';
|
||||
@ -34,6 +36,9 @@ class _PostContentHeader extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final ua = context.read<UserProvider>();
|
||||
final isAuthor = ua.isAuthorized && data.publisher.accountId == ua.user!.id;
|
||||
|
||||
return Row(
|
||||
children: [
|
||||
AccountImage(content: data.publisher.avatar),
|
||||
@ -47,8 +52,9 @@ class _PostContentHeader extends StatelessWidget {
|
||||
children: [
|
||||
Text('@${data.publisher.name}').fontSize(13),
|
||||
const Gap(4),
|
||||
Text(RelativeTime(context).format(data.publishedAt))
|
||||
.fontSize(13),
|
||||
Text(RelativeTime(context).format(
|
||||
data.publishedAt ?? data.createdAt,
|
||||
)).fontSize(13),
|
||||
],
|
||||
).opacity(0.8),
|
||||
],
|
||||
@ -60,30 +66,65 @@ class _PostContentHeader extends StatelessWidget {
|
||||
visualDensity: VisualDensity(horizontal: -4, vertical: -4),
|
||||
),
|
||||
itemBuilder: (BuildContext context) => <PopupMenuEntry>[
|
||||
if (isAuthor)
|
||||
PopupMenuItem(
|
||||
child: Row(
|
||||
children: [
|
||||
const Icon(Symbols.edit),
|
||||
const Gap(16),
|
||||
Text('edit').tr(),
|
||||
],
|
||||
),
|
||||
onTap: () {
|
||||
GoRouter.of(context).pushNamed(
|
||||
'postEditor',
|
||||
pathParameters: {'mode': data.typePlural},
|
||||
queryParameters: {'editing': data.id.toString()},
|
||||
);
|
||||
},
|
||||
),
|
||||
if (isAuthor)
|
||||
PopupMenuItem(
|
||||
child: Row(
|
||||
children: [
|
||||
const Icon(Symbols.delete),
|
||||
const Gap(16),
|
||||
Text('delete').tr(),
|
||||
],
|
||||
),
|
||||
),
|
||||
if (isAuthor) const PopupMenuDivider(),
|
||||
PopupMenuItem(
|
||||
child: Row(
|
||||
children: [
|
||||
const Icon(Symbols.edit),
|
||||
const Icon(Symbols.reply),
|
||||
const Gap(16),
|
||||
Text('edit').tr(),
|
||||
Text('reply').tr(),
|
||||
],
|
||||
),
|
||||
onTap: () {
|
||||
GoRouter.of(context).pushNamed(
|
||||
'postEditor',
|
||||
pathParameters: {'mode': data.typePlural},
|
||||
queryParameters: {'editing': data.id.toString()},
|
||||
queryParameters: {'replying': data.id.toString()},
|
||||
);
|
||||
},
|
||||
),
|
||||
PopupMenuItem(
|
||||
child: Row(
|
||||
children: [
|
||||
const Icon(Symbols.delete),
|
||||
const Icon(Symbols.forward),
|
||||
const Gap(16),
|
||||
Text('delete').tr(),
|
||||
Text('repost').tr(),
|
||||
],
|
||||
),
|
||||
onTap: () {
|
||||
GoRouter.of(context).pushNamed(
|
||||
'postEditor',
|
||||
pathParameters: {'mode': data.typePlural},
|
||||
queryParameters: {'reposting': data.id.toString()},
|
||||
);
|
||||
},
|
||||
),
|
||||
const PopupMenuDivider(),
|
||||
PopupMenuItem(
|
||||
|
Loading…
Reference in New Issue
Block a user