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