Compare commits

...

4 Commits

Author SHA1 Message Date
d8e79fb4f9 🚀 Launch 1.2.1+5 2024-08-04 20:49:11 +08:00
06e0fa465b Article has special badge 2024-08-04 20:48:51 +08:00
895a257f50 Better overflow effect 2024-08-04 20:43:25 +08:00
d9804ba00b 🚸 Enhanced share feature 2024-08-04 18:32:16 +08:00
7 changed files with 143 additions and 54 deletions

View File

@ -21,7 +21,7 @@ class _DraftBoxScreenState extends State<DraftBoxScreen> {
final PagingController<int, Post> _pagingController = final PagingController<int, Post> _pagingController =
PagingController(firstPageKey: 0); PagingController(firstPageKey: 0);
getPosts(int pageKey) async { _getPosts(int pageKey) async {
final PostProvider provider = Get.find(); final PostProvider provider = Get.find();
Response resp; Response resp;
@ -49,7 +49,7 @@ class _DraftBoxScreenState extends State<DraftBoxScreen> {
@override @override
void initState() { void initState() {
super.initState(); super.initState();
_pagingController.addPageRequestListener(getPosts); _pagingController.addPageRequestListener(_getPosts);
} }
@override @override
@ -76,6 +76,7 @@ class _DraftBoxScreenState extends State<DraftBoxScreen> {
itemBuilder: (context, item, index) { itemBuilder: (context, item, index) {
return PostOwnedListEntry( return PostOwnedListEntry(
item: item, item: item,
isFullContent: true,
backgroundColor: backgroundColor:
Theme.of(context).colorScheme.surfaceContainerLow, Theme.of(context).colorScheme.surfaceContainerLow,
onTap: () async { onTap: () async {

View File

@ -12,6 +12,7 @@ const i18nEnglish = {
'draftBox': 'Draft Box', 'draftBox': 'Draft Box',
'more': 'More', 'more': 'More',
'share': 'Share', 'share': 'Share',
'shareNoUri': 'Share text content',
'feed': 'Feed', 'feed': 'Feed',
'unlink': 'Unlink', 'unlink': 'Unlink',
'feedSearch': 'Search Feed', 'feedSearch': 'Search Feed',

View File

@ -20,6 +20,7 @@ const i18nSimplifiedChinese = {
'draftBox': '草稿箱', 'draftBox': '草稿箱',
'more': '更多', 'more': '更多',
'share': '分享', 'share': '分享',
'shareNoUri': '分享文字内容',
'feed': '资讯', 'feed': '资讯',
'unlink': '移除链接', 'unlink': '移除链接',
'feedSearch': '搜索资讯', 'feedSearch': '搜索资讯',

View File

@ -7,6 +7,7 @@ import 'package:get/get.dart';
import 'package:share_plus/share_plus.dart'; import 'package:share_plus/share_plus.dart';
import 'package:solian/exts.dart'; import 'package:solian/exts.dart';
import 'package:solian/models/post.dart'; import 'package:solian/models/post.dart';
import 'package:solian/platform.dart';
import 'package:solian/providers/auth.dart'; import 'package:solian/providers/auth.dart';
import 'package:solian/router.dart'; import 'package:solian/router.dart';
import 'package:solian/screens/posts/post_editor.dart'; import 'package:solian/screens/posts/post_editor.dart';
@ -38,12 +39,24 @@ class _PostActionState extends State<PostAction> {
}); });
} }
Future<void> _doShare() async { Future<void> _doShare({bool noUri = false}) async {
final box = context.findRenderObject() as RenderBox?; final box = context.findRenderObject() as RenderBox?;
if ((PlatformInfo.isAndroid || PlatformInfo.isIOS) && !noUri) {
await Share.shareUri(
Uri.parse('https://sn.solsynth.dev/posts/view/${widget.item.id}'),
sharePositionOrigin: box!.localToGlobal(Offset.zero) & box.size,
);
} else {
final extraContent = [
widget.item.body['title'],
widget.item.body['description'],
];
final isExtraNotEmpty = extraContent.any((x) => x != null);
await Share.share( await Share.share(
'postShareContent'.trParams({ 'postShareContent'.trParams({
'username': widget.item.author.nick, 'username': widget.item.author.nick,
'content': widget.item.body['content'] ?? 'no content', 'content':
'${extraContent.join('\n')}${isExtraNotEmpty ? '\n\n' : ''}${widget.item.body['content'] ?? 'no content'}',
'link': 'https://sn.solsynth.dev/posts/view/${widget.item.id}', 'link': 'https://sn.solsynth.dev/posts/view/${widget.item.id}',
}), }),
subject: 'postShareSubject'.trParams({ subject: 'postShareSubject'.trParams({
@ -52,6 +65,7 @@ class _PostActionState extends State<PostAction> {
sharePositionOrigin: box!.localToGlobal(Offset.zero) & box.size, sharePositionOrigin: box!.localToGlobal(Offset.zero) & box.size,
); );
} }
}
@override @override
void initState() { void initState() {
@ -86,6 +100,16 @@ class _PostActionState extends State<PostAction> {
contentPadding: const EdgeInsets.symmetric(horizontal: 24), contentPadding: const EdgeInsets.symmetric(horizontal: 24),
leading: const Icon(Icons.share), leading: const Icon(Icons.share),
title: Text('share'.tr), title: Text('share'.tr),
trailing: PlatformInfo.isIOS || PlatformInfo.isAndroid
? IconButton(
icon: const Icon(Icons.link_off),
tooltip: 'shareNoUri'.tr,
onPressed: () async {
await _doShare(noUri: true);
Navigator.pop(context);
},
)
: null,
onTap: () async { onTap: () async {
await _doShare(); await _doShare();
Navigator.pop(context); Navigator.pop(context);

View File

@ -76,12 +76,13 @@ class _PostItemState extends State<PostItem> {
Widget _buildHeader() { Widget _buildHeader() {
return Row( return Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
if (widget.isCompact) if (widget.isCompact)
AccountAvatar( AccountAvatar(
content: item.author.avatar.toString(), content: item.author.avatar.toString(),
radius: 10, radius: 10,
).paddingOnly(left: 2), ).paddingOnly(left: 2, top: 1),
Expanded( Expanded(
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
@ -108,18 +109,26 @@ class _PostItemState extends State<PostItem> {
item.body['description'], item.body['description'],
style: Theme.of(context).textTheme.bodySmall, style: Theme.of(context).textTheme.bodySmall,
), ),
if (item.body['description'] != null ||
item.body['title'] != null)
const Divider(thickness: 0.3, height: 1).paddingSymmetric(
vertical: 8,
),
], ],
).paddingOnly(left: widget.isCompact ? 6 : 12), ).paddingOnly(left: widget.isCompact ? 6 : 12),
), ),
if (widget.item.type == 'article')
Badge(
label: Text('article'.tr),
).paddingOnly(top: 3),
], ],
); );
} }
Widget _buildHeaderDivider() {
if (item.body['description'] != null || item.body['title'] != null) {
return const Divider(thickness: 0.3, height: 1).paddingSymmetric(
vertical: 8,
);
}
return const SizedBox();
}
Widget _buildFooter() { Widget _buildFooter() {
List<String> labels = List.empty(growable: true); List<String> labels = List.empty(growable: true);
if (widget.item.editedAt != null) { if (widget.item.editedAt != null) {
@ -273,7 +282,17 @@ class _PostItemState extends State<PostItem> {
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
_buildHeader().paddingSymmetric(horizontal: 12), _buildHeader().paddingSymmetric(horizontal: 12),
MarkdownTextContent( _buildHeaderDivider().paddingSymmetric(horizontal: 12),
Stack(
children: [
SizedContainer(
maxWidth: 640,
maxHeight: widget.isFullContent ? double.infinity : 80,
child: _MeasureSize(
onChange: (size) {
setState(() => _contentHeight = size.height);
},
child: MarkdownTextContent(
content: item.body['content'], content: item.body['content'],
isSelectable: widget.isContentSelectable, isSelectable: widget.isContentSelectable,
).paddingOnly( ).paddingOnly(
@ -282,6 +301,32 @@ class _PostItemState extends State<PostItem> {
top: 2, top: 2,
bottom: hasAttachment ? 4 : 0, bottom: hasAttachment ? 4 : 0,
), ),
),
),
if (_contentHeight >= 80 && !widget.isFullContent)
Align(
alignment: Alignment.bottomCenter,
child: IgnorePointer(
child: Container(
height: 80,
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.bottomCenter,
end: Alignment.topCenter,
colors: [
Theme.of(context).colorScheme.surfaceContainerLow,
Theme.of(context)
.colorScheme
.surface
.withOpacity(0),
],
),
),
),
),
),
],
),
_buildFooter().paddingOnly(left: 16), _buildFooter().paddingOnly(left: 16),
if (attachments.isNotEmpty) if (attachments.isNotEmpty)
Row( Row(
@ -330,12 +375,16 @@ class _PostItemState extends State<PostItem> {
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
_buildHeader(), _buildHeader(),
_buildHeaderDivider(),
Stack(
children: [
SizedContainer( SizedContainer(
maxWidth: 640, maxWidth: 640,
maxHeight: widget.isFullContent ? double.infinity : 320, maxHeight:
widget.isFullContent ? double.infinity : 320,
child: _MeasureSize( child: _MeasureSize(
onChange: (size) { onChange: (size) {
_contentHeight = size.height; setState(() => _contentHeight = size.height);
}, },
child: MarkdownTextContent( child: MarkdownTextContent(
content: item.body['content'], content: item.body['content'],
@ -343,20 +392,30 @@ class _PostItemState extends State<PostItem> {
).paddingOnly(left: 12, right: 8), ).paddingOnly(left: 12, right: 8),
), ),
), ),
if (_contentHeight >= 320 && if (_contentHeight >= 320 && !widget.isFullContent)
widget.isClickable && Align(
!widget.isFullContent) alignment: Alignment.bottomCenter,
InkWell( child: IgnorePointer(
child: Text( child: Container(
'readMore'.tr, height: 320,
style: TextStyle( decoration: BoxDecoration(
color: Theme.of(context).colorScheme.primary, gradient: LinearGradient(
begin: Alignment.bottomCenter,
end: Alignment.topCenter,
colors: [
Theme.of(context).colorScheme.surface,
Theme.of(context)
.colorScheme
.surface
.withOpacity(0),
],
), ),
), ),
onTap: () { ),
openContainer(); ),
}, ),
).paddingOnly(left: 12, top: 4), ],
),
if (widget.item.replyTo != null && widget.isShowEmbed) if (widget.item.replyTo != null && widget.isShowEmbed)
_buildReply(context).paddingOnly(top: 4), _buildReply(context).paddingOnly(top: 4),
if (widget.item.repostTo != null && widget.isShowEmbed) if (widget.item.repostTo != null && widget.isShowEmbed)

View File

@ -6,12 +6,14 @@ import 'package:solian/widgets/posts/post_item.dart';
class PostOwnedListEntry extends StatelessWidget { class PostOwnedListEntry extends StatelessWidget {
final Post item; final Post item;
final Function onTap; final Function onTap;
final bool isFullContent;
final Color? backgroundColor; final Color? backgroundColor;
const PostOwnedListEntry({ const PostOwnedListEntry({
super.key, super.key,
required this.item, required this.item,
required this.onTap, required this.onTap,
this.isFullContent = false,
this.backgroundColor, this.backgroundColor,
}); });
@ -29,6 +31,7 @@ class PostOwnedListEntry extends StatelessWidget {
isClickable: false, isClickable: false,
isShowReply: false, isShowReply: false,
isReactable: false, isReactable: false,
isFullContent: isFullContent,
backgroundColor: backgroundColor, backgroundColor: backgroundColor,
).paddingSymmetric(vertical: 8), ).paddingSymmetric(vertical: 8),
], ],

View File

@ -2,7 +2,7 @@ name: solian
description: "The Solar Network App" description: "The Solar Network App"
publish_to: "none" publish_to: "none"
version: 1.2.1+4 version: 1.2.1+5
environment: environment:
sdk: ">=3.3.4 <4.0.0" sdk: ">=3.3.4 <4.0.0"