Post alias

This commit is contained in:
LittleSheep 2024-08-17 18:44:20 +08:00
parent 454f711656
commit 3bea3a114a
11 changed files with 132 additions and 53 deletions

View File

@ -17,6 +17,7 @@ import 'package:shared_preferences/shared_preferences.dart';
class PostEditorController extends GetxController {
late final SharedPreferences _prefs;
final aliasController = TextEditingController();
final titleController = TextEditingController();
final descriptionController = TextEditingController();
final contentController = TextEditingController();
@ -197,16 +198,23 @@ class PostEditorController extends GetxController {
type = value.type;
editTo.value = value;
realmZone.value = value.realm;
isDraft.value = value.isDraft ?? false;
aliasController.text = value.alias ?? '';
titleController.text = value.body['title'] ?? '';
descriptionController.text = value.body['description'] ?? '';
contentController.text = value.body['content'] ?? '';
publishedAt.value = value.publishedAt;
publishedUntil.value = value.publishedUntil;
tags.value =
value.body['tags']?.map((x) => x['alias']).toList() ?? List.empty();
tags.value = List.from(
value.body['tags']?.map((x) => x['alias']).toList() ?? List.empty(),
growable: true,
);
tags.refresh();
attachments.value = value.body['attachments']?.cast<int>() ?? List.empty();
attachments.value = List.from(
value.body['attachments'] ?? List.empty(),
growable: true,
);
attachments.refresh();
thumbnail.value = value.body['thumbnail'];
@ -256,6 +264,7 @@ class PostEditorController extends GetxController {
Map<String, dynamic> get payload {
return {
'alias': aliasController.text,
'title': title,
'description': description,
'content': contentController.text,
@ -277,20 +286,33 @@ class PostEditorController extends GetxController {
set payload(Map<String, dynamic> value) {
type = value['type'];
tags.value = value['tags'].map((x) => x['alias']).toList().cast<String>();
tags.value = List.from(
value['tags'].map((x) => x['alias']).toList(),
growable: true,
);
aliasController.text = value['alias'] ?? '';
titleController.text = value['title'] ?? '';
descriptionController.text = value['description'] ?? '';
contentController.text = value['content'] ?? '';
attachments.value = value['attachments'].cast<int>() ?? List.empty();
attachments.value = List.from(
value['attachments'] ?? List.empty(),
growable: true,
);
attachments.refresh();
thumbnail.value = value['thumbnail'];
visibility.value = value['visibility'];
isDraft.value = value['is_draft'];
if (value['visible_users'] != null) {
visibleUsers.value = value['visible_users'].cast<int>();
visibleUsers.value = List.from(
value['visible_users'],
growable: true,
);
}
if (value['invisible_users'] != null) {
invisibleUsers.value = value['invisible_users'].cast<int>();
invisibleUsers.value = List.from(
value['invisible_users'],
growable: true,
);
}
if (value['published_at'] != null) {
publishedAt.value = DateTime.parse(value['published_at']).toLocal();
@ -319,6 +341,7 @@ class PostEditorController extends GetxController {
bool get isNotEmpty {
return [
aliasController.text.isNotEmpty,
titleController.text.isNotEmpty,
descriptionController.text.isNotEmpty,
contentController.text.isNotEmpty,

View File

@ -8,6 +8,8 @@ class Post {
DateTime updatedAt;
DateTime? editedAt;
DateTime? deletedAt;
String? alias;
String? areaAlias;
dynamic body;
List<Tag>? tags;
List<Category>? categories;
@ -33,6 +35,8 @@ class Post {
required this.updatedAt,
required this.editedAt,
required this.deletedAt,
required this.alias,
required this.areaAlias,
required this.type,
required this.body,
required this.tags,
@ -60,6 +64,8 @@ class Post {
deletedAt: json['deleted_at'] != null
? DateTime.parse(json['deleted_at'])
: null,
alias: json['alias'],
areaAlias: json['area_alias'],
type: json['type'],
body: json['body'],
tags: json['tags']?.map((x) => Tag.fromJson(x)).toList().cast<Tag>(),
@ -101,6 +107,8 @@ class Post {
'updated_at': updatedAt.toIso8601String(),
'edited_at': editedAt?.toIso8601String(),
'deleted_at': deletedAt?.toIso8601String(),
'alias': alias,
'area_alias': areaAlias,
'type': type,
'body': body,
'tags': tags,

View File

@ -176,11 +176,20 @@ class _PostPublishScreenState extends State<PostPublishScreen> {
children: [
ListTile(
tileColor: Theme.of(context).colorScheme.surfaceContainerLow,
title: Text(
title: Row(
children: [
Text(
_editorController.title ?? 'title'.tr,
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
const SizedBox(width: 6),
if (_editorController.aliasController.text.isNotEmpty)
Badge(
label: Text('#${_editorController.aliasController.text}'),
),
],
),
subtitle: Text(
_editorController.description ?? 'description'.tr,
maxLines: 2,
@ -255,6 +264,7 @@ class _PostPublishScreenState extends State<PostPublishScreen> {
),
],
),
if (_isBusy) const LinearProgressIndicator().animate().scaleX(),
Expanded(
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
@ -265,10 +275,6 @@ class _PostPublishScreenState extends State<PostPublishScreen> {
Expanded(
child: ListView(
children: [
if (_isBusy)
const LinearProgressIndicator()
.animate()
.scaleX(),
Container(
padding: const EdgeInsets.symmetric(
horizontal: 16,

View File

@ -13,6 +13,7 @@ const i18nEnglish = {
'more': 'More',
'share': 'Share',
'shareNoUri': 'Share text content',
'alias': 'Alias',
'feed': 'Feed',
'unlink': 'Unlink',
'feedSearch': 'Search Feed',

View File

@ -21,6 +21,7 @@ const i18nSimplifiedChinese = {
'more': '更多',
'share': '分享',
'shareNoUri': '分享文字内容',
'alias': '别名',
'feed': '资讯',
'unlink': '移除链接',
'feedSearch': '搜索资讯',

View File

@ -238,8 +238,8 @@ class _AttachmentItemVideoState extends State<_AttachmentItemVideo> {
bool _showContent = false;
void _startLoad() {
_player.open(
Future<void> _startLoad() async {
await _player.open(
Media(ServiceFinder.buildUrl('files', '/attachments/${widget.item.id}')),
play: false,
);
@ -249,7 +249,9 @@ class _AttachmentItemVideoState extends State<_AttachmentItemVideo> {
@override
void initState() {
super.initState();
_showContent = widget.autoload;
if (widget.autoload) {
_startLoad();
}
}
@override

View File

@ -57,10 +57,12 @@ class _AttachmentListState extends State<AttachmentList> {
}
attach.listMetadata(widget.attachmentsId).then((result) {
if (mounted) {
setState(() {
_attachmentsMeta = result;
_isLoading = false;
});
}
_calculateAspectRatio();
});
}
@ -111,6 +113,7 @@ class _AttachmentListState extends State<AttachmentList> {
showBadge: _attachmentsMeta.length > 1 && !widget.isGrid,
showBorder: widget.attachmentsId.length > 1,
showMature: _showMature,
autoload: widget.autoload,
onReveal: (value) {
setState(() => _showMature = value);
},
@ -138,8 +141,9 @@ class _AttachmentListState extends State<AttachmentList> {
);
}
final isNotPureImage = _attachmentsMeta
.any((x) => x?.mimetype.split('/').firstOrNull != 'image');
final isNotPureImage = _attachmentsMeta.any(
(x) => x?.mimetype.split('/').firstOrNull != 'image',
);
if (widget.isGrid && (widget.isForceGrid || !isNotPureImage)) {
const radius = BorderRadius.all(Radius.circular(8));
return GridView.builder(
@ -157,8 +161,10 @@ class _AttachmentListState extends State<AttachmentList> {
final element = _attachmentsMeta[idx];
return Container(
decoration: BoxDecoration(
border:
Border.all(color: Theme.of(context).dividerColor, width: 1),
border: Border.all(
color: Theme.of(context).dividerColor,
width: 1,
),
borderRadius: radius,
),
child: ClipRRect(

View File

@ -19,7 +19,7 @@ class PostEditorCategoriesDialog extends StatelessWidget {
initialTags: controller.tags,
hintText: 'postTagsPlaceholder'.tr,
onUpdate: (value) {
controller.tags.value = value;
controller.tags.value = List.from(value, growable: true);
controller.tags.refresh();
},
),

View File

@ -14,12 +14,25 @@ class PostEditorOverviewDialog extends StatelessWidget {
content: Column(
mainAxisSize: MainAxisSize.min,
children: [
TextField(
autofocus: true,
autocorrect: true,
controller: controller.aliasController,
decoration: InputDecoration(
isDense: true,
border: const OutlineInputBorder(),
hintText: 'alias'.tr,
),
onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(),
),
const SizedBox(height: 16),
TextField(
autofocus: true,
autocorrect: true,
controller: controller.titleController,
decoration: InputDecoration(
border: const UnderlineInputBorder(),
isDense: true,
border: const OutlineInputBorder(),
hintText: 'title'.tr,
),
onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(),
@ -33,7 +46,8 @@ class PostEditorOverviewDialog extends StatelessWidget {
keyboardType: TextInputType.multiline,
controller: controller.descriptionController,
decoration: InputDecoration(
border: const UnderlineInputBorder(),
isDense: true,
border: const OutlineInputBorder(),
hintText: 'description'.tr,
),
onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(),

View File

@ -42,10 +42,16 @@ class _PostActionState extends State<PostAction> {
Future<void> _doShare({bool noUri = false}) async {
ShareResult result;
String id;
final box = context.findRenderObject() as RenderBox?;
if (widget.item.alias?.isNotEmpty ?? false) {
id = '${widget.item.areaAlias}:${widget.item.alias}';
} else {
id = '${widget.item.id}';
}
if ((PlatformInfo.isAndroid || PlatformInfo.isIOS) && !noUri) {
result = await Share.shareUri(
Uri.parse('https://solsynth.dev/posts/${widget.item.id}'),
Uri.parse('https://solsynth.dev/posts/$id'),
sharePositionOrigin: box!.localToGlobal(Offset.zero) & box.size,
);
} else {
@ -59,7 +65,7 @@ class _PostActionState extends State<PostAction> {
'username': widget.item.author.nick,
'content':
'${extraContent.join('\n')}${isExtraNotEmpty ? '\n\n' : ''}${widget.item.body['content'] ?? 'no content'}',
'link': 'https://solsynth.dev/posts/${widget.item.id}',
'link': 'https://solsynth.dev/posts/$id',
}),
subject: 'postShareSubject'.trParams({
'username': widget.item.author.nick,
@ -96,10 +102,28 @@ class _PostActionState extends State<PostAction> {
'postActionList'.tr,
style: Theme.of(context).textTheme.headlineSmall,
),
Row(
children: [
Text(
'#${widget.item.id.toString().padLeft(8, '0')}',
style: Theme.of(context).textTheme.bodySmall,
),
if (widget.item.alias?.isNotEmpty ?? false)
Text(
'·',
style: Theme.of(context).textTheme.bodySmall,
).paddingSymmetric(horizontal: 6),
if (widget.item.alias?.isNotEmpty ?? false)
Expanded(
child: Text(
'${widget.item.areaAlias}:${widget.item.alias}',
style: Theme.of(context).textTheme.bodySmall,
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
),
],
),
],
).paddingOnly(left: 24, right: 24, top: 32, bottom: 16),
if (_isBusy) const LinearProgressIndicator().animate().scaleX(),

View File

@ -78,25 +78,19 @@ class _PostItemState extends State<PostItem> {
Widget _buildThumbnail() {
if (widget.item.body['thumbnail'] == null) return const SizedBox();
const radius = BorderRadius.all(Radius.circular(8));
return AspectRatio(
aspectRatio: 16 / 9,
child: Container(
decoration: BoxDecoration(
border: Border.all(
final border = BorderSide(
color: Theme.of(context).dividerColor,
width: 0.3,
),
borderRadius: radius,
),
child: ClipRRect(
borderRadius: radius,
);
return Container(
decoration: BoxDecoration(border: Border(top: border, bottom: border)),
child: AspectRatio(
aspectRatio: 16 / 9,
child: AttachmentSelfContainedEntry(
id: widget.item.body['thumbnail'],
parentId: 'p${item.id}-thumbnail',
),
),
),
);
}
@ -307,7 +301,7 @@ class _PostItemState extends State<PostItem> {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildThumbnail().paddingSymmetric(horizontal: 12, vertical: 4),
_buildThumbnail(),
_buildHeader().paddingSymmetric(horizontal: 12),
_buildHeaderDivider().paddingSymmetric(horizontal: 12),
Stack(
@ -381,7 +375,7 @@ class _PostItemState extends State<PostItem> {
closedBuilder: (_, openContainer) => Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildThumbnail().paddingSymmetric(horizontal: 12, vertical: 4),
_buildThumbnail().paddingOnly(bottom: 4),
Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [