Repostable and replyable post

This commit is contained in:
LittleSheep 2024-11-10 20:07:26 +08:00
parent 0a8c9fb208
commit c1e10916ee
7 changed files with 123 additions and 37 deletions

View File

@ -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 {}."
}

View File

@ -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": "你正在转发由 {} 发布的帖子。"
}

View File

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

View File

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

View File

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

View File

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

View File

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