Compare commits

...

2 Commits

Author SHA1 Message Date
a04bfe4cf9 🚀 Launch 1.3.7+8 2024-10-12 00:56:30 +08:00
7b7988e6cb ♻️ Refactored post layout 2024-10-12 00:41:03 +08:00
34 changed files with 560 additions and 441 deletions

View File

@ -269,7 +269,7 @@ PODS:
- shared_preferences_foundation (0.0.1):
- Flutter
- FlutterMacOS
- sqflite (0.0.3):
- sqflite_darwin (0.0.4):
- Flutter
- FlutterMacOS
- "sqlite3 (3.46.1+1)":
@ -334,7 +334,7 @@ DEPENDENCIES:
- screen_brightness_ios (from `.symlinks/plugins/screen_brightness_ios/ios`)
- share_plus (from `.symlinks/plugins/share_plus/ios`)
- shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`)
- sqflite (from `.symlinks/plugins/sqflite/darwin`)
- sqflite_darwin (from `.symlinks/plugins/sqflite_darwin/darwin`)
- sqlite3_flutter_libs (from `.symlinks/plugins/sqlite3_flutter_libs/ios`)
- url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`)
- volume_controller (from `.symlinks/plugins/volume_controller/ios`)
@ -437,8 +437,8 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/share_plus/ios"
shared_preferences_foundation:
:path: ".symlinks/plugins/shared_preferences_foundation/darwin"
sqflite:
:path: ".symlinks/plugins/sqflite/darwin"
sqflite_darwin:
:path: ".symlinks/plugins/sqflite_darwin/darwin"
sqlite3_flutter_libs:
:path: ".symlinks/plugins/sqlite3_flutter_libs/ios"
url_launcher_ios:
@ -505,7 +505,7 @@ SPEC CHECKSUMS:
SDWebImage: 8a6b7b160b4d710e2a22b6900e25301075c34cb3
share_plus: 8875f4f2500512ea181eef553c3e27dba5135aad
shared_preferences_foundation: fcdcbc04712aee1108ac7fda236f363274528f78
sqflite: 673a0e54cc04b7d6dba8d24fb8095b31c3a99eec
sqflite_darwin: a553b1fd6fe66f53bbb0fe5b4f5bab93f08d7a13
sqlite3: 0bb0e6389d824e40296f531b858a2a0b71c0d2fb
sqlite3_flutter_libs: c00457ebd31e59fa6bb830380ddba24d44fbcd3b
SwiftyGif: 706c60cf65fa2bc5ee0313beece843c8eb8194d4

View File

@ -83,7 +83,6 @@
</array>
<key>CFBundleLocalizations</key>
<array>
<string>zh_CN</string>
<string>en</string>
</array>
<key>UIStatusBarHidden</key>

View File

@ -192,7 +192,7 @@ class _PersonalizeScreenState extends State<PersonalizeScreen> {
const Gap(24),
Stack(
children: [
AccountAvatar(content: _avatar, radius: 40),
AttachedCircleAvatar(content: _avatar, radius: 40),
Positioned(
bottom: 0,
left: 40,

View File

@ -260,7 +260,8 @@ class _AccountProfilePageState extends State<AccountProfilePage> {
const Gap(8),
const Gap(8),
if (_userinfo != null)
AccountAvatar(content: _userinfo!.avatar, radius: 16),
AttachedCircleAvatar(
content: _userinfo!.avatar, radius: 16),
const Gap(12),
Expanded(
child: Column(

View File

@ -252,7 +252,7 @@ class _ChatListState extends State<ChatList> {
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
AccountAvatar(
AttachedCircleAvatar(
content: x.avatar,
radius: 14,
fallbackWidget: const Icon(

View File

@ -392,7 +392,7 @@ class _DashboardScreenState extends State<DashboardScreen> {
backgroundColor: Theme.of(context)
.colorScheme
.surfaceContainerLow,
),
).paddingAll(8),
),
),
).paddingSymmetric(horizontal: 8),

View File

@ -6,13 +6,13 @@ import 'package:get/get.dart';
import 'package:solian/controllers/post_list_controller.dart';
import 'package:solian/providers/auth.dart';
import 'package:solian/providers/navigation.dart';
import 'package:solian/router.dart';
import 'package:solian/screens/account/notification.dart';
import 'package:solian/theme.dart';
import 'package:solian/widgets/account/signin_required_overlay.dart';
import 'package:solian/widgets/current_state_action.dart';
import 'package:solian/widgets/app_bar_leading.dart';
import 'package:solian/widgets/navigation/realm_switcher.dart';
import 'package:solian/widgets/posts/post_creation.dart';
import 'package:solian/widgets/posts/post_list.dart';
import 'package:solian/widgets/posts/post_shuffle_swiper.dart';
import 'package:solian/widgets/root_container.dart';
@ -225,106 +225,3 @@ class _ExploreScreenState extends State<ExploreScreen>
super.dispose();
}
}
class PostCreatePopup extends StatelessWidget {
final bool hideDraftBox;
const PostCreatePopup({
super.key,
this.hideDraftBox = false,
});
@override
Widget build(BuildContext context) {
final AuthProvider auth = Get.find();
if (auth.isAuthorized.isFalse) {
return const SizedBox.shrink();
}
final List<dynamic> actionList = [
(
icon: const Icon(Icons.post_add),
label: 'postEditorModeStory'.tr,
onTap: () {
Navigator.pop(
context,
AppRouter.instance.pushNamed(
'postEditor',
queryParameters: {
'mode': 0.toString(),
},
),
);
},
),
(
icon: const Icon(Icons.description),
label: 'postEditorModeArticle'.tr,
onTap: () {
Navigator.pop(
context,
AppRouter.instance.pushNamed(
'postEditor',
queryParameters: {
'mode': 1.toString(),
},
),
);
},
),
(
icon: const Icon(Icons.drafts),
label: 'draftBoxOpen'.tr,
onTap: () {
Navigator.pop(
context,
AppRouter.instance.pushNamed('draftBox'),
);
},
),
];
return SizedBox(
height: MediaQuery.of(context).size.height * 0.38,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'postNew'.tr,
style: Theme.of(context).textTheme.headlineSmall,
).paddingOnly(left: 24, right: 24, top: 32, bottom: 16),
Expanded(
child: GridView.count(
physics: const NeverScrollableScrollPhysics(),
crossAxisCount: 3,
children: actionList
.map((x) => Card(
color: Theme.of(context).colorScheme.surfaceContainer,
child: InkWell(
borderRadius:
const BorderRadius.all(Radius.circular(8)),
onTap: x.onTap,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
x.icon,
const Gap(8),
Expanded(
child: Text(
x.label,
overflow: TextOverflow.fade,
),
),
],
).paddingAll(18),
),
))
.toList(),
).paddingSymmetric(horizontal: 20),
),
],
),
);
}
}

View File

@ -24,11 +24,11 @@ class PostDetailScreen extends StatefulWidget {
class _PostDetailScreenState extends State<PostDetailScreen> {
Post? item;
Future<Post?> getDetail() async {
Future<Post?> _getDetail() async {
if (widget.post != null) {
setState(() {
item = widget.post;
Get.find<LastReadProvider>().feedLastReadAt = item?.id;
return widget.post;
});
}
final PostProvider provider = Get.find();
@ -48,7 +48,7 @@ class _PostDetailScreenState extends State<PostDetailScreen> {
@override
Widget build(BuildContext context) {
return FutureBuilder(
future: getDetail(),
future: _getDetail(),
builder: (context, snapshot) {
if (!snapshot.hasData || snapshot.data == null) {
return const Center(

View File

@ -156,7 +156,7 @@ class _RealmListScreenState extends State<RealmListScreen> {
size: 18,
),
)
: AccountAvatar(
: AttachedCircleAvatar(
content: element.avatar!,
bgColor: Theme.of(context).colorScheme.primary,
),

View File

@ -1,15 +1,16 @@
import 'package:flutter/material.dart';
import 'package:solian/services.dart';
import 'package:solian/widgets/account/account_profile_popup.dart';
import 'package:solian/widgets/auto_cache_image.dart';
class AccountAvatar extends StatelessWidget {
class AttachedCircleAvatar extends StatelessWidget {
final dynamic content;
final Color? bgColor;
final Color? feColor;
final double? radius;
final Widget? fallbackWidget;
const AccountAvatar({
const AttachedCircleAvatar({
super.key,
required this.content,
this.bgColor,
@ -39,7 +40,7 @@ class AccountAvatar extends StatelessWidget {
child: isEmpty
? (fallbackWidget ??
Icon(
Icons.account_circle,
Icons.image,
size: radius != null ? radius! * 1.2 : 24,
color: feColor,
))
@ -48,6 +49,54 @@ class AccountAvatar extends StatelessWidget {
}
}
class AccountAvatar extends StatelessWidget {
final dynamic content;
final String username;
final Color? bgColor;
final Color? feColor;
final double? radius;
final Widget? fallbackWidget;
const AccountAvatar({
super.key,
required this.content,
required this.username,
this.bgColor,
this.feColor,
this.radius,
this.fallbackWidget,
});
@override
Widget build(BuildContext context) {
return GestureDetector(
child: AttachedCircleAvatar(
content: content,
bgColor: bgColor,
feColor: feColor,
radius: radius,
fallbackWidget: (fallbackWidget ??
Icon(
Icons.account_circle,
size: radius != null ? radius! * 1.2 : 24,
color: feColor,
)),
),
onTap: () {
showModalBottomSheet(
useRootNavigator: true,
isScrollControlled: true,
backgroundColor: Theme.of(context).colorScheme.surface,
context: context,
builder: (context) => AccountProfilePopup(
name: username,
),
);
},
);
}
}
class AccountProfileImage extends StatelessWidget {
final dynamic content;
final BoxFit fit;

View File

@ -84,7 +84,7 @@ class AccountHeadingWidget extends StatelessWidget {
Positioned(
bottom: -30,
left: 32,
child: AccountAvatar(content: avatar, radius: 40),
child: AttachedCircleAvatar(content: avatar, radius: 40),
),
],
),

View File

@ -138,7 +138,7 @@ class _AccountSelectorState extends State<AccountSelector> {
return ListTile(
title: Text(element.nick),
subtitle: Text(element.name),
leading: AccountAvatar(content: element.avatar),
leading: AttachedCircleAvatar(content: element.avatar),
trailing: widget.trailingBuilder != null
? widget.trailingBuilder!(element)
: _checkSelected(element)

View File

@ -23,7 +23,7 @@ class SilverRelativeList extends StatelessWidget {
title: Text(element.related.nick),
subtitle: Text(element.related.name),
leading: GestureDetector(
child: AccountAvatar(content: element.related.avatar),
child: AttachedCircleAvatar(content: element.related.avatar),
onTap: () {
showModalBottomSheet(
useRootNavigator: true,

View File

@ -56,7 +56,7 @@ class _RelativeSelectorState extends State<RelativeSelector> {
return ListTile(
title: Text(element.nick),
subtitle: Text(element.name),
leading: AccountAvatar(content: element.avatar),
leading: AttachedCircleAvatar(content: element.avatar),
trailing: widget.trailingBuilder != null
? widget.trailingBuilder!(element)
: null,

View File

@ -175,7 +175,7 @@ class _AttachmentFullScreenState extends State<AttachmentFullScreen> {
Row(
children: [
IgnorePointer(
child: AccountAvatar(
child: AttachedCircleAvatar(
content: widget.item.account!.avatar,
radius: 19,
),

View File

@ -19,9 +19,8 @@ class AttachmentList extends StatefulWidget {
final List<Attachment>? attachments;
final bool isGrid;
final bool isColumn;
final bool isForceGrid;
final bool isFullWidth;
final bool autoload;
final double flatMaxHeight;
final double columnMaxWidth;
final double? width;
@ -34,9 +33,8 @@ class AttachmentList extends StatefulWidget {
this.attachments,
this.isGrid = false,
this.isColumn = false,
this.isForceGrid = false,
this.isFullWidth = false,
this.autoload = false,
this.flatMaxHeight = 720,
this.columnMaxWidth = 480,
this.width,
this.viewport,
@ -175,9 +173,70 @@ class _AttachmentListState extends State<AttachmentList> {
.fadeIn(duration: 1250.ms);
}
const radius = BorderRadius.all(Radius.circular(8));
if (widget.isFullWidth && _attachments.length == 1) {
final element = _attachments.first;
double ratio = element!.metadata?['ratio']?.toDouble() ?? 16 / 9;
return Container(
constraints: BoxConstraints(
maxWidth: widget.columnMaxWidth,
maxHeight: 640,
),
child: AspectRatio(
aspectRatio: ratio,
child: Container(
decoration: BoxDecoration(
border: Border.symmetric(
horizontal: BorderSide(
color: Theme.of(context).dividerColor,
width: 1,
),
),
),
child: _buildEntry(element, 0),
),
),
);
}
final isNotPureImage = _attachments.any(
(x) => x?.mimetype.split('/').firstOrNull != 'image',
);
if (widget.isGrid && !isNotPureImage) {
return GridView.builder(
padding: EdgeInsets.zero,
primary: false,
physics: const NeverScrollableScrollPhysics(),
shrinkWrap: true,
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: math.min(3, _attachments.length),
mainAxisSpacing: 8.0,
crossAxisSpacing: 8.0,
),
itemCount: _attachments.length,
itemBuilder: (context, idx) {
final element = _attachments[idx];
return Container(
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.surfaceContainerHigh,
border: Border.all(
color: Theme.of(context).dividerColor,
width: 1,
),
borderRadius: radius,
),
child: ClipRRect(
borderRadius: radius,
child: _buildEntry(element, idx),
),
);
},
);
}
if (widget.isColumn) {
var idx = 0;
const radius = BorderRadius.all(Radius.circular(8));
return Wrap(
spacing: 8,
runSpacing: 8,
@ -212,27 +271,31 @@ class _AttachmentListState extends State<AttachmentList> {
);
}
final isNotPureImage = _attachments.any(
(x) => x?.mimetype.split('/').firstOrNull != 'image',
);
if (widget.isGrid && (widget.isForceGrid || !isNotPureImage)) {
const radius = BorderRadius.all(Radius.circular(8));
return GridView.builder(
padding: EdgeInsets.zero,
primary: false,
physics: const NeverScrollableScrollPhysics(),
shrinkWrap: true,
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: math.min(3, _attachments.length),
mainAxisSpacing: 8.0,
crossAxisSpacing: 8.0,
return SizedBox(
width: math.min(MediaQuery.of(context).size.width, widget.columnMaxWidth),
child: CarouselSlider.builder(
options: CarouselOptions(
disableCenter: true,
animateToClosest: true,
aspectRatio: _aspectRatio,
enlargeCenterPage: true,
viewportFraction: widget.viewport ?? 0.95,
enableInfiniteScroll: false,
),
itemCount: _attachments.length,
itemBuilder: (context, idx) {
itemBuilder: (context, idx, _) {
final element = _attachments[idx];
if (element == null) const SizedBox.shrink();
double ratio = element!.metadata?['ratio']?.toDouble() ?? 16 / 9;
return Container(
constraints: BoxConstraints(
maxWidth: widget.columnMaxWidth,
maxHeight: 640,
),
child: AspectRatio(
aspectRatio: ratio,
child: Container(
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.surfaceContainerHigh,
border: Border.all(
color: Theme.of(context).dividerColor,
width: 1,
@ -243,38 +306,10 @@ class _AttachmentListState extends State<AttachmentList> {
borderRadius: radius,
child: _buildEntry(element, idx),
),
),
),
);
},
).paddingSymmetric(horizontal: 24);
}
return Container(
width: MediaQuery.of(context).size.width,
constraints: BoxConstraints(
maxHeight: widget.flatMaxHeight,
),
decoration: BoxDecoration(
color: Colors.transparent,
border: Border.symmetric(
horizontal: BorderSide(
width: 0.3,
color: Theme.of(context).dividerColor,
),
),
),
child: CarouselSlider.builder(
options: CarouselOptions(
animateToClosest: true,
aspectRatio: _aspectRatio,
viewportFraction:
widget.viewport ?? (_attachments.length > 1 ? 0.95 : 1),
enableInfiniteScroll: false,
),
itemCount: _attachments.length,
itemBuilder: (context, idx, _) {
final element = _attachments[idx];
return _buildEntry(element, idx);
},
),
);
}

View File

@ -205,7 +205,7 @@ class _ChannelListWidgetState extends State<ChannelListWidget> {
item.members!.where((e) => e.account.id != widget.selfId).firstOrNull;
if (item.type == 1 && otherside != null) {
final avatar = AccountAvatar(
final avatar = AttachedCircleAvatar(
content: otherside.account.avatar,
radius: 20,
bgColor: Theme.of(context).colorScheme.primary,
@ -241,7 +241,7 @@ class _ChannelListWidgetState extends State<ChannelListWidget> {
padding: const EdgeInsets.all(2),
elevation: 8,
),
badgeContent: AccountAvatar(
badgeContent: AttachedCircleAvatar(
content: item.realm?.avatar,
radius: 10,
fallbackWidget: const Icon(

View File

@ -152,7 +152,8 @@ class _ChannelMemberListPopupState extends State<ChannelMemberListPopup> {
title: Text(element.account.nick),
subtitle: Text(element.account.name),
leading: GestureDetector(
child: AccountAvatar(content: element.account.avatar),
child:
AttachedCircleAvatar(content: element.account.avatar),
onTap: () {
showModalBottomSheet(
useRootNavigator: true,

View File

@ -74,7 +74,7 @@ class _NoContentWidgetState extends State<NoContentWidget>
),
)
],
child: AccountAvatar(
child: AttachedCircleAvatar(
content: widget.userinfo!.avatar,
bgColor: Colors.transparent,
radius: radius,

View File

@ -220,7 +220,7 @@ class ChatEvent extends StatelessWidget {
children: [
Row(
children: [
AccountAvatar(
AttachedCircleAvatar(
content: item.sender.account.avatar,
radius: 9,
),
@ -250,7 +250,8 @@ class ChatEvent extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
GestureDetector(
child: AccountAvatar(content: item.sender.account.avatar),
child:
AttachedCircleAvatar(content: item.sender.account.avatar),
onTap: () {
showModalBottomSheet(
useRootNavigator: true,

View File

@ -443,7 +443,7 @@ class _ChatMessageInputState extends State<ChatMessageInput> {
.map(
(x) => ChatMessageSuggestion(
type: 'users',
leading: AccountAvatar(content: x.avatar),
leading: AttachedCircleAvatar(content: x.avatar),
display: x.nick,
content: '@${x.name}',
),

View File

@ -69,7 +69,7 @@ class _AppAccountWidgetState extends State<AppAccountWidget> {
bottom: 0,
end: -2,
),
child: AccountAvatar(
child: AttachedCircleAvatar(
radius: 14,
content: auth.userProfile.value!['avatar'],
),

View File

@ -36,7 +36,7 @@ class RealmSwitcher extends StatelessWidget {
mainAxisAlignment: MainAxisAlignment.center,
children: [
if (item != null)
AccountAvatar(
AttachedCircleAvatar(
content: item.avatar,
radius: 14,
fallbackWidget: const Icon(

View File

@ -0,0 +1,108 @@
import 'package:flutter/material.dart';
import 'package:gap/gap.dart';
import 'package:get/get.dart';
import 'package:solian/providers/auth.dart';
import 'package:solian/router.dart';
class PostCreatePopup extends StatelessWidget {
final bool hideDraftBox;
const PostCreatePopup({
super.key,
this.hideDraftBox = false,
});
@override
Widget build(BuildContext context) {
final AuthProvider auth = Get.find();
if (auth.isAuthorized.isFalse) {
return const SizedBox.shrink();
}
final List<dynamic> actionList = [
(
icon: const Icon(Icons.post_add),
label: 'postEditorModeStory'.tr,
onTap: () {
Navigator.pop(
context,
AppRouter.instance.pushNamed(
'postEditor',
queryParameters: {
'mode': 0.toString(),
},
),
);
},
),
(
icon: const Icon(Icons.description),
label: 'postEditorModeArticle'.tr,
onTap: () {
Navigator.pop(
context,
AppRouter.instance.pushNamed(
'postEditor',
queryParameters: {
'mode': 1.toString(),
},
),
);
},
),
(
icon: const Icon(Icons.drafts),
label: 'draftBoxOpen'.tr,
onTap: () {
Navigator.pop(
context,
AppRouter.instance.pushNamed('draftBox'),
);
},
),
];
return SizedBox(
height: MediaQuery.of(context).size.height * 0.38,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'postNew'.tr,
style: Theme.of(context).textTheme.headlineSmall,
).paddingOnly(left: 24, right: 24, top: 32, bottom: 16),
Expanded(
child: GridView.count(
physics: const NeverScrollableScrollPhysics(),
crossAxisCount: 3,
children: actionList
.map((x) => Card(
color: Theme.of(context).colorScheme.surfaceContainer,
child: InkWell(
borderRadius:
const BorderRadius.all(Radius.circular(8)),
onTap: x.onTap,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
x.icon,
const Gap(8),
Expanded(
child: Text(
x.label,
overflow: TextOverflow.fade,
),
),
],
).paddingAll(18),
),
))
.toList(),
).paddingSymmetric(horizontal: 20),
),
],
),
);
}
}

View File

@ -12,7 +12,6 @@ import 'package:solian/screens/posts/post_detail.dart';
import 'package:solian/shells/title_shell.dart';
import 'package:solian/theme.dart';
import 'package:solian/widgets/account/account_avatar.dart';
import 'package:solian/widgets/account/account_profile_popup.dart';
import 'package:solian/widgets/attachments/attachment_list.dart';
import 'package:solian/widgets/link_expansion.dart';
import 'package:solian/widgets/markdown_text_content.dart';
@ -36,6 +35,7 @@ class PostItem extends StatefulWidget {
final bool showFeaturedReply;
final String? attachmentParent;
final Color? backgroundColor;
final Function? onComment;
const PostItem({
super.key,
@ -52,6 +52,7 @@ class PostItem extends StatefulWidget {
this.showFeaturedReply = false,
this.attachmentParent,
this.backgroundColor,
this.onComment,
});
@override
@ -92,8 +93,6 @@ class _PostItemState extends State<PostItem> {
item: item,
).paddingSymmetric(horizontal: 12),
_PostHeaderDividerWidget(item: item).paddingSymmetric(horizontal: 12),
Stack(
children: [
SizedContainer(
maxWidth: 640,
maxHeight: widget.isFullContent ? double.infinity : 80,
@ -110,15 +109,12 @@ class _PostItemState extends State<PostItem> {
isSelectable: widget.isContentSelectable,
),
).paddingOnly(
left: 16,
left: 12,
right: 12,
top: 2,
bottom: hasAttachment ? 4 : 0,
),
),
),
],
),
if (_contentHeight >= 80 && !widget.isFullContent)
Opacity(
opacity: 0.8,
@ -132,7 +128,7 @@ class _PostItemState extends State<PostItem> {
right: 8,
top: 4,
),
_PostFooterWidget(item: item).paddingOnly(left: 16),
_PostFooterWidget(item: item).paddingOnly(left: 12),
if (attachments.isNotEmpty)
Row(
children: [
@ -148,7 +144,7 @@ class _PostItemState extends State<PostItem> {
style: TextStyle(color: _unFocusColor),
)
],
).paddingOnly(left: 16, top: 4),
).paddingOnly(left: 14, top: 4),
],
);
}
@ -162,25 +158,7 @@ class _PostItemState extends State<PostItem> {
rid: item.body['thumbnail'],
parentId: widget.item.id.toString(),
).paddingOnly(bottom: 4),
Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
GestureDetector(
child: AccountAvatar(content: item.author.avatar),
onTap: () {
showModalBottomSheet(
useRootNavigator: true,
isScrollControlled: true,
backgroundColor: Theme.of(context).colorScheme.surface,
context: context,
builder: (context) => AccountProfilePopup(
name: item.author.name,
),
);
},
),
Expanded(
child: Column(
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_PostHeaderWidget(
@ -188,12 +166,9 @@ class _PostItemState extends State<PostItem> {
item: item,
),
_PostHeaderDividerWidget(item: item),
Stack(
children: [
SizedContainer(
maxWidth: 640,
maxHeight:
widget.isFullContent ? double.infinity : 320,
maxHeight: widget.isFullContent ? double.infinity : 320,
child: _MeasureSize(
onChange: (size) {
setState(() => _contentHeight = size.height);
@ -205,30 +180,24 @@ class _PostItemState extends State<PostItem> {
content: item.body['content'],
isAutoWarp: item.type == 'story',
isSelectable: widget.isContentSelectable,
isLargeText: item.type == 'article' &&
widget.isFullContent,
).paddingOnly(left: 12, right: 8),
isLargeText:
item.type == 'article' && widget.isFullContent,
),
),
),
],
),
if (_contentHeight >= 320 && !widget.isFullContent)
Opacity(
opacity: 0.8,
child: InkWell(child: Text('readMore'.tr)),
).paddingOnly(
left: 12,
top: 4,
),
).paddingOnly(top: 4),
if (widget.item.replyTo != null && widget.isShowEmbed)
Container(
constraints: const BoxConstraints(maxWidth: 480),
padding: const EdgeInsets.only(top: 4),
padding: const EdgeInsets.only(top: 8),
child: _PostEmbedWidget(
isClickable: widget.isClickable,
isOverrideEmbedClickable:
widget.isOverrideEmbedClickable,
isOverrideEmbedClickable: widget.isOverrideEmbedClickable,
item: widget.item.replyTo!,
username: widget.item.replyTo!.author.name,
hintText: 'postRepliedNotify',
@ -239,11 +208,10 @@ class _PostItemState extends State<PostItem> {
if (widget.item.repostTo != null && widget.isShowEmbed)
Container(
constraints: const BoxConstraints(maxWidth: 480),
padding: const EdgeInsets.only(top: 4),
padding: const EdgeInsets.only(top: 8),
child: _PostEmbedWidget(
isClickable: widget.isClickable,
isOverrideEmbedClickable:
widget.isOverrideEmbedClickable,
isOverrideEmbedClickable: widget.isOverrideEmbedClickable,
item: widget.item.repostTo!,
username: widget.item.repostTo!.author.name,
hintText: 'postRepostedNotify',
@ -251,24 +219,19 @@ class _PostItemState extends State<PostItem> {
id: widget.item.repostTo!.id.toString(),
),
),
_PostFooterWidget(item: item).paddingOnly(left: 12),
LinkExpansion(content: item.body['content'])
.paddingOnly(top: 4),
],
),
),
_PostFooterWidget(item: item),
LinkExpansion(content: item.body['content']).paddingOnly(top: 4),
],
).paddingOnly(
top: 10,
bottom:
(attachments.length == 1 && !AppTheme.isLargeScreen(context))
? 10
: 0,
right: 16,
left: 16,
),
_PostAttachmentWidget(item: item),
if (widget.showFeaturedReply) _PostFeaturedReplyWidget(item: item),
if (widget.showFeaturedReply)
_PostFeaturedReplyWidget(item: item).paddingSymmetric(
horizontal: 12,
),
if (widget.showFeaturedReply) const Gap(8),
if (widget.isShowReply || widget.isReactable)
PostQuickAction(
isShowReply: widget.isShowReply,
@ -280,19 +243,15 @@ class _PostItemState extends State<PostItem> {
(item.metric!.reactionList[symbol] ?? 0) + changes;
});
},
onComment: () {
if (widget.onComment != null) {
widget.onComment!();
}
},
).paddingOnly(
top: (attachments.length == 1 && !AppTheme.isLargeScreen(context))
? 10
: 6,
left:
(attachments.length == 1 && !AppTheme.isLargeScreen(context))
? 24
: 60,
right: 16,
bottom: 10,
left: 14,
right: 14,
)
else
const Gap(10),
],
),
openBuilder: (_, __) => TitleShell(
@ -317,7 +276,6 @@ class _PostFeaturedReplyWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
final isLargeScreen = AppTheme.isLargeScreen(context);
final unFocusColor =
Theme.of(context).colorScheme.onSurface.withOpacity(0.75);
@ -325,13 +283,10 @@ class _PostFeaturedReplyWidget extends StatelessWidget {
return const SizedBox.shrink();
}
final List<String> attachments = item.body['attachments'] is List
? List.from(item.body['attachments']?.whereType<String>())
: List.empty();
return FutureBuilder(
future:
Get.find<PostProvider>().listPostFeaturedReply(item.id.toString()),
future: Get.find<PostProvider>().listPostFeaturedReply(
item.id.toString(),
),
builder: (context, snapshot) {
if (!snapshot.hasData || snapshot.data!.isEmpty) {
return const SizedBox.shrink();
@ -351,7 +306,7 @@ class _PostFeaturedReplyWidget extends StatelessWidget {
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
AccountAvatar(
AttachedCircleAvatar(
content: reply.author.avatar,
radius: 10,
),
@ -423,16 +378,9 @@ class _PostFeaturedReplyWidget extends StatelessWidget {
.toList(),
),
),
)
.animate()
.fadeIn(
).animate().fadeIn(
duration: 300.ms,
curve: Curves.easeIn,
)
.paddingOnly(
top: (attachments.length == 1 && !isLargeScreen) ? 10 : 6,
left: (attachments.length == 1 && !isLargeScreen) ? 24 : 60,
right: 16,
);
},
);
@ -452,30 +400,33 @@ class _PostAttachmentWidget extends StatelessWidget {
? List.from(item.body['attachments']?.whereType<String>())
: List.empty();
if (attachments.length > 3) {
if (attachments.isEmpty) return const SizedBox.shrink();
if (attachments.length == 1) {
return AttachmentList(
parentId: item.id.toString(),
attachmentIds: item.preload == null ? attachments : null,
attachments: item.preload?.attachments,
autoload: false,
isFullWidth: true,
).paddingOnly(top: 4);
} else if (attachments.length > 1 &&
attachments.length % 3 == 0 &&
!isLargeScreen) {
return AttachmentList(
parentId: item.id.toString(),
attachmentIds: item.preload == null ? attachments : null,
attachments: item.preload?.attachments,
autoload: false,
isGrid: true,
).paddingOnly(left: 36, top: 4, bottom: 4);
} else if (attachments.length > 1 || isLargeScreen) {
return AttachmentList(
parentId: item.id.toString(),
attachmentIds: item.preload == null ? attachments : null,
attachments: item.preload?.attachments,
autoload: false,
isColumn: true,
).paddingOnly(left: 60, right: 24, top: 4, bottom: 4);
).paddingSymmetric(horizontal: 14, vertical: 8);
} else {
return AttachmentList(
flatMaxHeight: MediaQuery.of(context).size.width,
parentId: item.id.toString(),
attachmentIds: item.preload == null ? attachments : null,
attachments: item.preload?.attachments,
autoload: false,
);
).paddingOnly(bottom: 8, top: 4);
}
}
}
@ -515,16 +466,17 @@ class _PostEmbedWidget extends StatelessWidget {
size: 16,
color: unFocusColor,
),
const Gap(6),
Expanded(
child: Text(
hintText.trParams(
{'username': '@$username'},
),
style: TextStyle(color: unFocusColor),
).paddingOnly(left: 6),
),
),
],
).paddingOnly(left: 12),
).paddingOnly(left: 2),
Card(
elevation: 1,
child: PostItem(
@ -560,9 +512,7 @@ class _PostHeaderDividerWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
if (item.body['description'] != null || item.body['title'] != null) {
return const Divider(thickness: 0.3, height: 1).paddingSymmetric(
vertical: 8,
);
return const Gap(8);
}
return const SizedBox.shrink();
}
@ -634,49 +584,59 @@ class _PostHeaderWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Row(
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (isCompact)
AccountAvatar(
content: item.author.avatar,
radius: 10,
).paddingOnly(left: 2, top: 1),
username: item.author.name,
radius: isCompact ? 10 : null,
),
Gap(isCompact ? 6 : 8),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Text(
item.author.nick,
style: const TextStyle(fontWeight: FontWeight.bold),
),
RelativeDate(item.publishedAt?.toLocal() ?? DateTime.now())
.paddingOnly(left: 4),
if (isCompact) const Gap(4),
if (isCompact)
RelativeDate(
item.publishedAt?.toLocal() ?? DateTime.now(),
).paddingOnly(top: 1),
],
),
if (item.body['title'] != null)
Text(
item.body['title'],
style: Theme.of(context)
.textTheme
.bodyMedium!
.copyWith(fontSize: 15),
),
if (item.body['description'] != null)
Text(
item.body['description'],
style: Theme.of(context).textTheme.bodySmall,
),
if (!isCompact)
RelativeDate(item.publishedAt?.toLocal() ?? DateTime.now()),
],
).paddingOnly(left: isCompact ? 6 : 12),
),
),
if (item.type == 'article')
Badge(
label: Text('article'.tr),
).paddingOnly(top: 3),
],
),
const Gap(8),
if (item.body['title'] != null)
Text(
item.body['title'],
style: Theme.of(context).textTheme.titleMedium,
),
if (item.body['description'] != null)
Text(
item.body['description'],
style: Theme.of(context).textTheme.titleSmall,
),
],
);
}
}

View File

@ -3,6 +3,8 @@ import 'package:get/get.dart';
import 'package:infinite_scroll_pagination/infinite_scroll_pagination.dart';
import 'package:solian/models/post.dart';
import 'package:solian/providers/auth.dart';
import 'package:solian/router.dart';
import 'package:solian/screens/posts/post_editor.dart';
import 'package:solian/widgets/posts/post_action.dart';
import 'package:solian/widgets/posts/post_item.dart';
@ -12,6 +14,7 @@ class PostListWidget extends StatelessWidget {
final bool isNestedClickable;
final PagingController<int, Post> controller;
final Color? backgroundColor;
final EdgeInsets? padding;
const PostListWidget({
super.key,
@ -20,6 +23,7 @@ class PostListWidget extends StatelessWidget {
this.isClickable = true,
this.isNestedClickable = true,
this.backgroundColor,
this.padding,
});
@override
@ -29,7 +33,9 @@ class PostListWidget extends StatelessWidget {
pagingController: controller,
builderDelegate: PagedChildBuilderDelegate<Post>(
itemBuilder: (context, item, index) {
return PostListEntryWidget(
return Padding(
padding: padding ?? EdgeInsets.zero,
child: PostListEntryWidget(
isShowEmbed: isShowEmbed,
isNestedClickable: isNestedClickable,
isClickable: isClickable,
@ -39,6 +45,7 @@ class PostListWidget extends StatelessWidget {
onUpdate: () {
controller.refresh();
},
),
);
},
),
@ -77,6 +84,22 @@ class PostListEntryWidget extends StatelessWidget {
isClickable: isNestedClickable,
showFeaturedReply: showFeaturedReply,
backgroundColor: backgroundColor,
onComment: () {
AppRouter.instance
.pushNamed(
'postEditor',
extra: PostPublishArguments(reply: item),
)
.then((value) {
if (value is Future) {
value.then((_) {
onUpdate();
});
} else if (value != null) {
onUpdate();
}
});
},
).paddingSymmetric(vertical: 8),
onLongPress: () {
final AuthProvider auth = Get.find();

View File

@ -11,6 +11,7 @@ class PostQuickAction extends StatefulWidget {
final Post item;
final bool isReactable;
final bool isShowReply;
final Function onComment;
final void Function(String symbol, int num) onReact;
const PostQuickAction({
@ -18,6 +19,7 @@ class PostQuickAction extends StatefulWidget {
required this.item,
this.isShowReply = true,
this.isReactable = true,
required this.onComment,
required this.onReact,
});
@ -106,7 +108,11 @@ class _PostQuickActionState extends State<PostQuickAction> {
builder: (context) {
return PostReplyListPopup(item: widget.item);
},
);
).then((signal) {
if (signal == true) {
widget.onComment();
}
});
},
),
),

View File

@ -53,6 +53,7 @@ class _PostReplyListState extends State<PostReplyList> {
@override
Widget build(BuildContext context) {
return PostListWidget(
padding: EdgeInsets.symmetric(horizontal: 10),
isShowEmbed: false,
controller: _pagingController,
backgroundColor: widget.backgroundColor,
@ -70,16 +71,30 @@ class PostReplyListPopup extends StatelessWidget {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
Row(
children: [
Expanded(
child: Text(
'postReplies'.tr,
style: Theme.of(context).textTheme.headlineSmall,
).paddingOnly(left: 24, right: 24, top: 32, bottom: 16),
),
),
IconButton(
icon: const Icon(Icons.add_comment),
visualDensity: const VisualDensity(horizontal: -4),
onPressed: () {
Navigator.pop(context, true);
},
),
],
).paddingOnly(left: 24, right: 24, top: 24, bottom: 8),
Expanded(
child: CustomScrollView(
slivers: [
PostReplyList(
item: item,
backgroundColor: Theme.of(context).colorScheme.surfaceContainerLow,
backgroundColor:
Theme.of(context).colorScheme.surfaceContainerLow,
),
],
),

View File

@ -149,7 +149,8 @@ class _RealmMemberListPopupState extends State<RealmMemberListPopup> {
title: Text(element.account.nick),
subtitle: Text(element.account.name),
leading: GestureDetector(
child: AccountAvatar(content: element.account.avatar),
child:
AttachedCircleAvatar(content: element.account.avatar),
onTap: () {
showModalBottomSheet(
useRootNavigator: true,

View File

@ -29,7 +29,7 @@ import protocol_handler_macos
import screen_brightness_macos
import share_plus
import shared_preferences_foundation
import sqflite
import sqflite_darwin
import sqlite3_flutter_libs
import url_launcher_macos
import wakelock_plus

View File

@ -195,7 +195,7 @@ PODS:
- shared_preferences_foundation (0.0.1):
- Flutter
- FlutterMacOS
- sqflite (0.0.3):
- sqflite_darwin (0.0.4):
- Flutter
- FlutterMacOS
- "sqlite3 (3.46.1+1)":
@ -249,7 +249,7 @@ DEPENDENCIES:
- screen_brightness_macos (from `Flutter/ephemeral/.symlinks/plugins/screen_brightness_macos/macos`)
- share_plus (from `Flutter/ephemeral/.symlinks/plugins/share_plus/macos`)
- shared_preferences_foundation (from `Flutter/ephemeral/.symlinks/plugins/shared_preferences_foundation/darwin`)
- sqflite (from `Flutter/ephemeral/.symlinks/plugins/sqflite/darwin`)
- sqflite_darwin (from `Flutter/ephemeral/.symlinks/plugins/sqflite_darwin/darwin`)
- sqlite3_flutter_libs (from `Flutter/ephemeral/.symlinks/plugins/sqlite3_flutter_libs/macos`)
- url_launcher_macos (from `Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos`)
- wakelock_plus (from `Flutter/ephemeral/.symlinks/plugins/wakelock_plus/macos`)
@ -328,8 +328,8 @@ EXTERNAL SOURCES:
:path: Flutter/ephemeral/.symlinks/plugins/share_plus/macos
shared_preferences_foundation:
:path: Flutter/ephemeral/.symlinks/plugins/shared_preferences_foundation/darwin
sqflite:
:path: Flutter/ephemeral/.symlinks/plugins/sqflite/darwin
sqflite_darwin:
:path: Flutter/ephemeral/.symlinks/plugins/sqflite_darwin/darwin
sqlite3_flutter_libs:
:path: Flutter/ephemeral/.symlinks/plugins/sqlite3_flutter_libs/macos
url_launcher_macos:
@ -371,16 +371,16 @@ SPEC CHECKSUMS:
media_kit_native_event_loop: 81fd5b45192b72f8b5b69eaf5b540f45777eb8d5
media_kit_video: c75b07f14d59706c775778e4dd47dd027de8d1e5
nanopb: fad817b59e0457d11a5dfbde799381cd727c1275
package_info_plus: fa739dd842b393193c5ca93c26798dff6e3d0e0c
package_info_plus: d2f71247aab4b6521434f887276093acc70d214c
pasteboard: 9b69dba6fedbb04866be632205d532fe2f6b1d99
path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46
PromisesObjC: f5707f49cb48b9636751c5b2e7d227e43fba9f47
PromisesSwift: 9d77319bbe72ebf6d872900551f7eeba9bce2851
protocol_handler_macos: d10a6c01d6373389ffd2278013ab4c47ed6d6daa
screen_brightness_macos: 2d6d3af2165592d9a55ffcd95b7550970e41ebda
share_plus: 36537c04ce0c3e3f5bd297ce4318b6d5ee5fd6cf
share_plus: a182a58e04e51647c0481aadabbc4de44b3a2bce
shared_preferences_foundation: fcdcbc04712aee1108ac7fda236f363274528f78
sqflite: 673a0e54cc04b7d6dba8d24fb8095b31c3a99eec
sqflite_darwin: a553b1fd6fe66f53bbb0fe5b4f5bab93f08d7a13
sqlite3: 0bb0e6389d824e40296f531b858a2a0b71c0d2fb
sqlite3_flutter_libs: 5ca46c1a04eddfbeeb5b16566164aa7ad1616e7b
url_launcher_macos: c82c93949963e55b228a30115bd219499a6fe404

View File

@ -49,7 +49,6 @@
<string>NSApplication</string>
<key>CFBundleLocalizations</key>
<array>
<string>zh_CN</string>
<string>en</string>
</array>
<key>NSUserActivityTypes</key>

View File

@ -450,10 +450,10 @@ packages:
dependency: transitive
description:
name: file
sha256: "5fc22d7c25582e38ad9a8515372cd9a93834027aacf1801cf01164dac0ffa08c"
sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4
url: "https://pub.dev"
source: hosted
version: "7.0.0"
version: "7.0.1"
file_picker:
dependency: "direct main"
description:
@ -695,10 +695,10 @@ packages:
dependency: "direct main"
description:
name: flutter_card_swiper
sha256: "880ad669017154d6d1f8c3abd861db08af97b3b7b0f7d7d5cbde690a9253811d"
sha256: "1eacbfab31b572223042e03409726553aec431abe48af48c8d591d376d070d3d"
url: "https://pub.dev"
source: hosted
version: "7.0.1"
version: "7.0.2"
flutter_keyboard_visibility:
dependency: transitive
description:
@ -1401,10 +1401,10 @@ packages:
dependency: "direct main"
description:
name: package_info_plus
sha256: a75164ade98cb7d24cfd0a13c6408927c6b217fa60dee5a7ff5c116a58f28918
sha256: "894f37107424311bdae3e476552229476777b8752c5a2a2369c0cb9a2d5442ef"
url: "https://pub.dev"
source: hosted
version: "8.0.2"
version: "8.0.3"
package_info_plus_platform_interface:
dependency: transitive
description:
@ -1449,10 +1449,10 @@ packages:
dependency: transitive
description:
name: path_provider_android
sha256: f7544c346a0742aee1450f9e5c0f5269d7c602b9c95fdbcd9fb8f5b1df13b1cc
sha256: c464428172cb986b758c6d1724c603097febb8fb855aa265aeecc9280c294d4a
url: "https://pub.dev"
source: hosted
version: "2.2.11"
version: "2.2.12"
path_provider_foundation:
dependency: transitive
description:
@ -1769,18 +1769,18 @@ packages:
dependency: "direct main"
description:
name: share_plus
sha256: "468c43f285207c84bcabf5737f33b914ceb8eb38398b91e5e3ad1698d1b72a52"
sha256: fec12c3c39f01e4df1ec6ad92b6e85503c5ca64ffd6e28d18c9ffe53fcc4cb11
url: "https://pub.dev"
source: hosted
version: "10.0.2"
version: "10.0.3"
share_plus_platform_interface:
dependency: transitive
description:
name: share_plus_platform_interface
sha256: "6ababf341050edff57da8b6990f11f4e99eaba837865e2e6defe16d039619db5"
sha256: c57c0bbfec7142e3a0f55633be504b796af72e60e3c791b44d5a017b985f7a48
url: "https://pub.dev"
source: hosted
version: "5.0.0"
version: "5.0.1"
shared_preferences:
dependency: "direct main"
description:
@ -1902,18 +1902,42 @@ packages:
dependency: transitive
description:
name: sqflite
sha256: ff5a2436ef8ebdfda748fbfe957f9981524cb5ff11e7bafa8c42771840e8a788
sha256: "79a297dc3cc137e758c6a4baf83342b039e5a6d2436fcdf3f96a00adaaf2ad62"
url: "https://pub.dev"
source: hosted
version: "2.3.3+2"
version: "2.4.0"
sqflite_android:
dependency: transitive
description:
name: sqflite_android
sha256: "78f489aab276260cdd26676d2169446c7ecd3484bbd5fead4ca14f3ed4dd9ee3"
url: "https://pub.dev"
source: hosted
version: "2.4.0"
sqflite_common:
dependency: transitive
description:
name: sqflite_common
sha256: "2d8e607db72e9cb7748c9c6e739e2c9618320a5517de693d5a24609c4671b1a4"
sha256: "4468b24876d673418a7b7147e5a08a715b4998a7ae69227acafaab762e0e5490"
url: "https://pub.dev"
source: hosted
version: "2.5.4+4"
version: "2.5.4+5"
sqflite_darwin:
dependency: transitive
description:
name: sqflite_darwin
sha256: "769733dddf94622d5541c73e4ddc6aa7b252d865285914b6fcd54a63c4b4f027"
url: "https://pub.dev"
source: hosted
version: "2.4.1-1"
sqflite_platform_interface:
dependency: transitive
description:
name: sqflite_platform_interface
sha256: "8dd4515c7bdcae0a785b0062859336de775e8c65db81ae33dd5445f35be61920"
url: "https://pub.dev"
source: hosted
version: "2.4.0"
sqlite3:
dependency: transitive
description:
@ -2062,10 +2086,10 @@ packages:
dependency: "direct main"
description:
name: url_launcher
sha256: "21b704ce5fa560ea9f3b525b43601c678728ba46725bab9b01187b4831377ed3"
sha256: "9d06212b1362abc2f0f0d78e6f09f726608c74e3b9462e8368bb03314aa8d603"
url: "https://pub.dev"
source: hosted
version: "6.3.0"
version: "6.3.1"
url_launcher_android:
dependency: transitive
description:

View File

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