Repostable and replyable post

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

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(