💄 New layout for article for optimized reading experience

🐛 Bug fixes on pending post media list
This commit is contained in:
LittleSheep 2024-12-07 21:33:01 +08:00
parent b583780cfc
commit 2a837227d5
6 changed files with 186 additions and 30 deletions

View File

@ -383,5 +383,7 @@
"accountStatusOnline": "Online",
"accountStatusOffline": "Offline",
"accountStatusLastSeen": "Last seen at {}",
"postArticle": "Article on the Solar Network"
"postArticle": "Article on the Solar Network",
"articleWrittenAt": "Written at {}",
"articleEditedAt": "Edited at {}"
}

View File

@ -383,5 +383,8 @@
"accountStatusOnline": "在线",
"accountStatusOffline": "离线",
"accountStatusLastSeen": "最后一次在 {} 上线",
"postArticle": "Solar Network 上的文章"
"postArticle": "Solar Network 上的文章",
"articleWrittenAt": "发表于 {}",
"articleEditedAt": "编辑于 {}"
}

View File

@ -264,6 +264,7 @@ class PostWriteController extends ChangeNotifier {
final item = await _uploadAttachment(context, media);
attachments[idx] = PostWriteMedia(item);
isBusy = false;
notifyListeners();
}
@ -395,6 +396,9 @@ class PostWriteController extends ChangeNotifier {
attachments.add(thumbnail!);
thumbnail = null;
} else {
if (thumbnail != null) {
attachments.add(thumbnail!);
}
thumbnail = attachments[idx];
attachments.removeAt(idx);
}

View File

@ -1,44 +1,48 @@
import 'dart:ui';
import 'package:dismissible_page/dismissible_page.dart';
import 'package:flutter/material.dart';
import 'package:flutter_markdown/flutter_markdown.dart';
import 'package:go_router/go_router.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:markdown/markdown.dart' as markdown;
import 'package:styled_widget/styled_widget.dart';
import 'package:surface/types/attachment.dart';
import 'package:surface/widgets/attachment/attachment_item.dart';
import 'package:surface/widgets/universal_image.dart';
import 'package:syntax_highlight/syntax_highlight.dart';
import 'package:url_launcher/url_launcher_string.dart';
import 'package:path/path.dart';
import 'package:uuid/uuid.dart';
class MarkdownTextContent extends StatefulWidget {
import 'attachment/attachment_detail.dart';
class MarkdownTextContent extends StatelessWidget {
final String content;
final bool isSelectable;
final bool isLargeText;
final bool isAutoWarp;
final TextScaler? textScaler;
final List<SnAttachment?>? attachments;
const MarkdownTextContent({
super.key,
required this.content,
this.isSelectable = false,
this.isLargeText = false,
this.isAutoWarp = false,
this.textScaler,
this.attachments,
});
@override
State<MarkdownTextContent> createState() => _MarkdownTextContentState();
}
class _MarkdownTextContentState extends State<MarkdownTextContent> {
Widget _buildContent(BuildContext context) {
return Markdown(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
data: widget.content,
data: content,
padding: EdgeInsets.zero,
styleSheet: MarkdownStyleSheet.fromTheme(
Theme.of(context),
).copyWith(
textScaler: TextScaler.linear(widget.isLargeText ? 1.1 : 1),
textScaler: textScaler,
blockquote: TextStyle(
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
@ -73,7 +77,7 @@ class _MarkdownTextContentState extends State<MarkdownTextContent> {
...markdown.ExtensionSet.gitHubFlavored.blockSyntaxes,
],
<markdown.InlineSyntax>[
if (widget.isAutoWarp) markdown.LineBreakSyntax(),
if (isAutoWarp) markdown.LineBreakSyntax(),
_UserNameCardInlineSyntax(),
markdown.AutolinkSyntax(),
markdown.AutolinkExtensionSyntax(),
@ -85,7 +89,12 @@ class _MarkdownTextContentState extends State<MarkdownTextContent> {
onTapLink: (text, href, title) async {
if (href == null) return;
if (href.startsWith('solink://')) {
// final segments = href.replaceFirst('solink://', '').split('/');
final uri = href.replaceFirst('solink://', '');
final segments = uri.split('/');
switch (segments[0]) {
default:
GoRouter.of(context).push(uri);
}
return;
}
@ -99,7 +108,57 @@ class _MarkdownTextContentState extends State<MarkdownTextContent> {
double? width, height;
BoxFit? fit;
if (url.startsWith('solink://')) {
// final segments = url.replaceFirst('solink://', '').split('/');
final segments = url.replaceFirst('solink://', '').split('/');
switch (segments[0]) {
case 'attachments':
final attachment = attachments?.firstWhere(
(ele) => ele?.rid == segments[1],
orElse: () => null,
);
if (attachment != null) {
const uuid = Uuid();
final heroTag = uuid.v4();
return GestureDetector(
child: Container(
decoration: BoxDecoration(
borderRadius: const BorderRadius.all(Radius.circular(8)),
border: Border.all(
color: Theme.of(context).dividerColor,
width: 1,
),
),
child: ClipRRect(
borderRadius: const BorderRadius.all(Radius.circular(8)),
child: AspectRatio(
aspectRatio: attachment.metadata['ratio'] ??
switch (attachment.mimetype.split('/').firstOrNull) {
'audio' => 16 / 9,
'video' => 16 / 9,
_ => 1,
}
.toDouble(),
child: AttachmentItem(
data: attachment,
heroTag: heroTag,
),
),
),
),
onTap: () {
context.pushTransparentRoute(
AttachmentZoomView(
data: [attachment],
initialIndex: 0,
heroTags: [heroTag],
),
backgroundColor: Colors.black.withOpacity(0.7),
rootNavigator: true,
);
},
);
}
break;
}
return const SizedBox.shrink();
}
return UniversalImage(
@ -114,7 +173,7 @@ class _MarkdownTextContentState extends State<MarkdownTextContent> {
@override
Widget build(BuildContext context) {
if (widget.isSelectable) {
if (isSelectable) {
return SelectionArea(child: _buildContent(context));
}
return _buildContent(context);
@ -153,13 +212,11 @@ class _MarkdownTextCodeElement extends MarkdownElementBuilder {
child: FutureBuilder(
future: (() async {
final docPath = '../../../';
final highlightingPath =
join(docPath, 'assets/highlighting', language);
final highlightingPath = join(docPath, 'assets/highlighting', language);
await Highlighter.initialize([highlightingPath]);
return Highlighter(
language: highlightingPath,
theme: PlatformDispatcher.instance.platformBrightness ==
Brightness.light
theme: PlatformDispatcher.instance.platformBrightness == Brightness.light
? await HighlighterTheme.loadLightTheme()
: await HighlighterTheme.loadDarkTheme(),
);

View File

@ -3,6 +3,7 @@ import 'dart:math' as math;
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:material_symbols_icons/symbols.dart';
import 'package:popover/popover.dart';
import 'package:provider/provider.dart';
@ -136,8 +137,14 @@ class PostItem extends StatelessWidget {
},
).padding(horizontal: 12, vertical: 8),
if (data.body['title'] != null || data.body['description'] != null)
_PostHeadline(data: data).padding(horizontal: 16, bottom: 8),
_PostContentBody(data: data.body).padding(horizontal: 16, bottom: 6),
_PostHeadline(
data: data,
isEnlarge: data.type == 'article' && showFullPost,
).padding(horizontal: 16, bottom: 8),
_PostContentBody(
data: data,
isEnlarge: data.type == 'article' && showFullPost,
).padding(horizontal: 16, bottom: 6),
if (data.repostTo != null)
_PostQuoteContent(child: data.repostTo!).padding(
horizontal: 12,
@ -156,7 +163,7 @@ class PostItem extends StatelessWidget {
],
),
),
if (data.preload?.attachments?.isNotEmpty ?? false)
if ((data.preload?.attachments?.isNotEmpty ?? false) && data.type != 'article')
AttachmentList(
data: data.preload!.attachments!,
bordered: true,
@ -292,11 +299,82 @@ class _PostBottomAction extends StatelessWidget {
class _PostHeadline extends StatelessWidget {
final SnPost data;
final bool isEnlarge;
const _PostHeadline({super.key, required this.data});
const _PostHeadline({
super.key,
required this.data,
this.isEnlarge = false,
});
@override
Widget build(BuildContext context) {
if (isEnlarge) {
final sn = context.read<SnNetworkProvider>();
final textScaler = TextScaler.linear(1.5);
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (data.preload?.thumbnail != null)
Container(
margin: const EdgeInsets.only(bottom: 8),
decoration: BoxDecoration(
borderRadius: const BorderRadius.all(Radius.circular(8)),
border: Border.all(
color: Theme.of(context).dividerColor,
width: 1,
),
),
child: AspectRatio(
aspectRatio: 16 / 9,
child: ClipRRect(
borderRadius: const BorderRadius.all(Radius.circular(8)),
child: AutoResizeUniversalImage(
sn.getAttachmentUrl(data.preload!.thumbnail!.rid),
fit: BoxFit.cover,
),
),
),
),
if (data.body['title'] != null)
Text(
data.body['title'],
style: Theme.of(context).textTheme.titleMedium,
textScaler: TextScaler.linear(1.4),
),
if (data.body['description'] != null)
Text(
data.body['description'],
style: Theme.of(context).textTheme.bodyMedium,
textScaler: TextScaler.linear(1.1),
),
if (data.body['description'] != null) const Gap(8) else const Gap(4),
Row(
children: [
Text(
'articleWrittenAt'.tr(
args: [DateFormat('y/M/d HH:mm').format(data.createdAt)],
),
style: TextStyle(fontSize: 13),
),
const Gap(8),
if (data.updatedAt != data.createdAt)
Text(
'articleEditedAt'.tr(
args: [DateFormat('y/M/d HH:mm').format(data.updatedAt)],
),
style: TextStyle(fontSize: 13),
),
],
).opacity(0.75),
const Gap(8),
const Divider(height: 1, thickness: 1),
const Gap(8),
],
);
}
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
@ -502,14 +580,22 @@ class _PostContentHeader extends StatelessWidget {
}
class _PostContentBody extends StatelessWidget {
final dynamic data;
final SnPost data;
final bool isEnlarge;
const _PostContentBody({this.data});
const _PostContentBody({
required this.data,
this.isEnlarge = false,
});
@override
Widget build(BuildContext context) {
if (data['content'] == null) return const SizedBox.shrink();
return MarkdownTextContent(content: data['content']);
if (data.body['content'] == null) return const SizedBox.shrink();
return MarkdownTextContent(
textScaler: isEnlarge ? TextScaler.linear(1.1) : null,
content: data.body['content'],
attachments: data.preload?.attachments,
);
}
}
@ -537,7 +623,7 @@ class _PostQuoteContent extends StatelessWidget {
showMenu: false,
onDeleted: () {},
).padding(bottom: 4),
_PostContentBody(data: child.body),
_PostContentBody(data: child),
],
),
);

View File

@ -208,7 +208,11 @@ class PostMediaPendingList extends StatelessWidget {
),
),
),
if (thumbnail != null) const VerticalDivider(width: 1).padding(horizontal: 8),
if (thumbnail != null)
const VerticalDivider(width: 1, thickness: 1).padding(
horizontal: 12,
vertical: 16,
),
Expanded(
child: ListView.separated(
scrollDirection: Axis.horizontal,