✨ Post alias
This commit is contained in:
parent
454f711656
commit
3bea3a114a
@ -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,
|
||||
|
@ -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,
|
||||
|
@ -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,
|
||||
|
@ -13,6 +13,7 @@ const i18nEnglish = {
|
||||
'more': 'More',
|
||||
'share': 'Share',
|
||||
'shareNoUri': 'Share text content',
|
||||
'alias': 'Alias',
|
||||
'feed': 'Feed',
|
||||
'unlink': 'Unlink',
|
||||
'feedSearch': 'Search Feed',
|
||||
|
@ -21,6 +21,7 @@ const i18nSimplifiedChinese = {
|
||||
'more': '更多',
|
||||
'share': '分享',
|
||||
'shareNoUri': '分享文字内容',
|
||||
'alias': '别名',
|
||||
'feed': '资讯',
|
||||
'unlink': '移除链接',
|
||||
'feedSearch': '搜索资讯',
|
||||
|
@ -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
|
||||
|
@ -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(
|
||||
|
@ -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();
|
||||
},
|
||||
),
|
||||
|
@ -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(),
|
||||
|
@ -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(),
|
||||
|
@ -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: [
|
||||
|
Loading…
Reference in New Issue
Block a user