♻️ Refactored post layout
This commit is contained in:
parent
81a616157e
commit
7b7988e6cb
@ -192,7 +192,7 @@ class _PersonalizeScreenState extends State<PersonalizeScreen> {
|
|||||||
const Gap(24),
|
const Gap(24),
|
||||||
Stack(
|
Stack(
|
||||||
children: [
|
children: [
|
||||||
AccountAvatar(content: _avatar, radius: 40),
|
AttachedCircleAvatar(content: _avatar, radius: 40),
|
||||||
Positioned(
|
Positioned(
|
||||||
bottom: 0,
|
bottom: 0,
|
||||||
left: 40,
|
left: 40,
|
||||||
|
@ -260,7 +260,8 @@ class _AccountProfilePageState extends State<AccountProfilePage> {
|
|||||||
const Gap(8),
|
const Gap(8),
|
||||||
const Gap(8),
|
const Gap(8),
|
||||||
if (_userinfo != null)
|
if (_userinfo != null)
|
||||||
AccountAvatar(content: _userinfo!.avatar, radius: 16),
|
AttachedCircleAvatar(
|
||||||
|
content: _userinfo!.avatar, radius: 16),
|
||||||
const Gap(12),
|
const Gap(12),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Column(
|
child: Column(
|
||||||
|
@ -252,7 +252,7 @@ class _ChatListState extends State<ChatList> {
|
|||||||
child: Row(
|
child: Row(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
AccountAvatar(
|
AttachedCircleAvatar(
|
||||||
content: x.avatar,
|
content: x.avatar,
|
||||||
radius: 14,
|
radius: 14,
|
||||||
fallbackWidget: const Icon(
|
fallbackWidget: const Icon(
|
||||||
|
@ -392,7 +392,7 @@ class _DashboardScreenState extends State<DashboardScreen> {
|
|||||||
backgroundColor: Theme.of(context)
|
backgroundColor: Theme.of(context)
|
||||||
.colorScheme
|
.colorScheme
|
||||||
.surfaceContainerLow,
|
.surfaceContainerLow,
|
||||||
),
|
).paddingAll(8),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
).paddingSymmetric(horizontal: 8),
|
).paddingSymmetric(horizontal: 8),
|
||||||
|
@ -6,13 +6,13 @@ import 'package:get/get.dart';
|
|||||||
import 'package:solian/controllers/post_list_controller.dart';
|
import 'package:solian/controllers/post_list_controller.dart';
|
||||||
import 'package:solian/providers/auth.dart';
|
import 'package:solian/providers/auth.dart';
|
||||||
import 'package:solian/providers/navigation.dart';
|
import 'package:solian/providers/navigation.dart';
|
||||||
import 'package:solian/router.dart';
|
|
||||||
import 'package:solian/screens/account/notification.dart';
|
import 'package:solian/screens/account/notification.dart';
|
||||||
import 'package:solian/theme.dart';
|
import 'package:solian/theme.dart';
|
||||||
import 'package:solian/widgets/account/signin_required_overlay.dart';
|
import 'package:solian/widgets/account/signin_required_overlay.dart';
|
||||||
import 'package:solian/widgets/current_state_action.dart';
|
import 'package:solian/widgets/current_state_action.dart';
|
||||||
import 'package:solian/widgets/app_bar_leading.dart';
|
import 'package:solian/widgets/app_bar_leading.dart';
|
||||||
import 'package:solian/widgets/navigation/realm_switcher.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_list.dart';
|
||||||
import 'package:solian/widgets/posts/post_shuffle_swiper.dart';
|
import 'package:solian/widgets/posts/post_shuffle_swiper.dart';
|
||||||
import 'package:solian/widgets/root_container.dart';
|
import 'package:solian/widgets/root_container.dart';
|
||||||
@ -225,106 +225,3 @@ class _ExploreScreenState extends State<ExploreScreen>
|
|||||||
super.dispose();
|
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),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -24,11 +24,11 @@ class PostDetailScreen extends StatefulWidget {
|
|||||||
class _PostDetailScreenState extends State<PostDetailScreen> {
|
class _PostDetailScreenState extends State<PostDetailScreen> {
|
||||||
Post? item;
|
Post? item;
|
||||||
|
|
||||||
Future<Post?> getDetail() async {
|
Future<Post?> _getDetail() async {
|
||||||
if (widget.post != null) {
|
if (widget.post != null) {
|
||||||
|
setState(() {
|
||||||
item = widget.post;
|
item = widget.post;
|
||||||
Get.find<LastReadProvider>().feedLastReadAt = item?.id;
|
});
|
||||||
return widget.post;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
final PostProvider provider = Get.find();
|
final PostProvider provider = Get.find();
|
||||||
@ -48,7 +48,7 @@ class _PostDetailScreenState extends State<PostDetailScreen> {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return FutureBuilder(
|
return FutureBuilder(
|
||||||
future: getDetail(),
|
future: _getDetail(),
|
||||||
builder: (context, snapshot) {
|
builder: (context, snapshot) {
|
||||||
if (!snapshot.hasData || snapshot.data == null) {
|
if (!snapshot.hasData || snapshot.data == null) {
|
||||||
return const Center(
|
return const Center(
|
||||||
|
@ -156,7 +156,7 @@ class _RealmListScreenState extends State<RealmListScreen> {
|
|||||||
size: 18,
|
size: 18,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
: AccountAvatar(
|
: AttachedCircleAvatar(
|
||||||
content: element.avatar!,
|
content: element.avatar!,
|
||||||
bgColor: Theme.of(context).colorScheme.primary,
|
bgColor: Theme.of(context).colorScheme.primary,
|
||||||
),
|
),
|
||||||
|
@ -1,15 +1,16 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:solian/services.dart';
|
import 'package:solian/services.dart';
|
||||||
|
import 'package:solian/widgets/account/account_profile_popup.dart';
|
||||||
import 'package:solian/widgets/auto_cache_image.dart';
|
import 'package:solian/widgets/auto_cache_image.dart';
|
||||||
|
|
||||||
class AccountAvatar extends StatelessWidget {
|
class AttachedCircleAvatar extends StatelessWidget {
|
||||||
final dynamic content;
|
final dynamic content;
|
||||||
final Color? bgColor;
|
final Color? bgColor;
|
||||||
final Color? feColor;
|
final Color? feColor;
|
||||||
final double? radius;
|
final double? radius;
|
||||||
final Widget? fallbackWidget;
|
final Widget? fallbackWidget;
|
||||||
|
|
||||||
const AccountAvatar({
|
const AttachedCircleAvatar({
|
||||||
super.key,
|
super.key,
|
||||||
required this.content,
|
required this.content,
|
||||||
this.bgColor,
|
this.bgColor,
|
||||||
@ -39,7 +40,7 @@ class AccountAvatar extends StatelessWidget {
|
|||||||
child: isEmpty
|
child: isEmpty
|
||||||
? (fallbackWidget ??
|
? (fallbackWidget ??
|
||||||
Icon(
|
Icon(
|
||||||
Icons.account_circle,
|
Icons.image,
|
||||||
size: radius != null ? radius! * 1.2 : 24,
|
size: radius != null ? radius! * 1.2 : 24,
|
||||||
color: feColor,
|
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 {
|
class AccountProfileImage extends StatelessWidget {
|
||||||
final dynamic content;
|
final dynamic content;
|
||||||
final BoxFit fit;
|
final BoxFit fit;
|
||||||
|
@ -84,7 +84,7 @@ class AccountHeadingWidget extends StatelessWidget {
|
|||||||
Positioned(
|
Positioned(
|
||||||
bottom: -30,
|
bottom: -30,
|
||||||
left: 32,
|
left: 32,
|
||||||
child: AccountAvatar(content: avatar, radius: 40),
|
child: AttachedCircleAvatar(content: avatar, radius: 40),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
@ -138,7 +138,7 @@ class _AccountSelectorState extends State<AccountSelector> {
|
|||||||
return ListTile(
|
return ListTile(
|
||||||
title: Text(element.nick),
|
title: Text(element.nick),
|
||||||
subtitle: Text(element.name),
|
subtitle: Text(element.name),
|
||||||
leading: AccountAvatar(content: element.avatar),
|
leading: AttachedCircleAvatar(content: element.avatar),
|
||||||
trailing: widget.trailingBuilder != null
|
trailing: widget.trailingBuilder != null
|
||||||
? widget.trailingBuilder!(element)
|
? widget.trailingBuilder!(element)
|
||||||
: _checkSelected(element)
|
: _checkSelected(element)
|
||||||
|
@ -23,7 +23,7 @@ class SilverRelativeList extends StatelessWidget {
|
|||||||
title: Text(element.related.nick),
|
title: Text(element.related.nick),
|
||||||
subtitle: Text(element.related.name),
|
subtitle: Text(element.related.name),
|
||||||
leading: GestureDetector(
|
leading: GestureDetector(
|
||||||
child: AccountAvatar(content: element.related.avatar),
|
child: AttachedCircleAvatar(content: element.related.avatar),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
showModalBottomSheet(
|
showModalBottomSheet(
|
||||||
useRootNavigator: true,
|
useRootNavigator: true,
|
||||||
|
@ -56,7 +56,7 @@ class _RelativeSelectorState extends State<RelativeSelector> {
|
|||||||
return ListTile(
|
return ListTile(
|
||||||
title: Text(element.nick),
|
title: Text(element.nick),
|
||||||
subtitle: Text(element.name),
|
subtitle: Text(element.name),
|
||||||
leading: AccountAvatar(content: element.avatar),
|
leading: AttachedCircleAvatar(content: element.avatar),
|
||||||
trailing: widget.trailingBuilder != null
|
trailing: widget.trailingBuilder != null
|
||||||
? widget.trailingBuilder!(element)
|
? widget.trailingBuilder!(element)
|
||||||
: null,
|
: null,
|
||||||
|
@ -175,7 +175,7 @@ class _AttachmentFullScreenState extends State<AttachmentFullScreen> {
|
|||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
IgnorePointer(
|
IgnorePointer(
|
||||||
child: AccountAvatar(
|
child: AttachedCircleAvatar(
|
||||||
content: widget.item.account!.avatar,
|
content: widget.item.account!.avatar,
|
||||||
radius: 19,
|
radius: 19,
|
||||||
),
|
),
|
||||||
|
@ -19,9 +19,8 @@ class AttachmentList extends StatefulWidget {
|
|||||||
final List<Attachment>? attachments;
|
final List<Attachment>? attachments;
|
||||||
final bool isGrid;
|
final bool isGrid;
|
||||||
final bool isColumn;
|
final bool isColumn;
|
||||||
final bool isForceGrid;
|
final bool isFullWidth;
|
||||||
final bool autoload;
|
final bool autoload;
|
||||||
final double flatMaxHeight;
|
|
||||||
final double columnMaxWidth;
|
final double columnMaxWidth;
|
||||||
|
|
||||||
final double? width;
|
final double? width;
|
||||||
@ -34,9 +33,8 @@ class AttachmentList extends StatefulWidget {
|
|||||||
this.attachments,
|
this.attachments,
|
||||||
this.isGrid = false,
|
this.isGrid = false,
|
||||||
this.isColumn = false,
|
this.isColumn = false,
|
||||||
this.isForceGrid = false,
|
this.isFullWidth = false,
|
||||||
this.autoload = false,
|
this.autoload = false,
|
||||||
this.flatMaxHeight = 720,
|
|
||||||
this.columnMaxWidth = 480,
|
this.columnMaxWidth = 480,
|
||||||
this.width,
|
this.width,
|
||||||
this.viewport,
|
this.viewport,
|
||||||
@ -175,9 +173,70 @@ class _AttachmentListState extends State<AttachmentList> {
|
|||||||
.fadeIn(duration: 1250.ms);
|
.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) {
|
if (widget.isColumn) {
|
||||||
var idx = 0;
|
var idx = 0;
|
||||||
const radius = BorderRadius.all(Radius.circular(8));
|
|
||||||
return Wrap(
|
return Wrap(
|
||||||
spacing: 8,
|
spacing: 8,
|
||||||
runSpacing: 8,
|
runSpacing: 8,
|
||||||
@ -212,27 +271,31 @@ class _AttachmentListState extends State<AttachmentList> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
final isNotPureImage = _attachments.any(
|
return SizedBox(
|
||||||
(x) => x?.mimetype.split('/').firstOrNull != 'image',
|
width: math.min(MediaQuery.of(context).size.width, widget.columnMaxWidth),
|
||||||
);
|
child: CarouselSlider.builder(
|
||||||
if (widget.isGrid && (widget.isForceGrid || !isNotPureImage)) {
|
options: CarouselOptions(
|
||||||
const radius = BorderRadius.all(Radius.circular(8));
|
disableCenter: true,
|
||||||
return GridView.builder(
|
animateToClosest: true,
|
||||||
padding: EdgeInsets.zero,
|
aspectRatio: _aspectRatio,
|
||||||
primary: false,
|
enlargeCenterPage: true,
|
||||||
physics: const NeverScrollableScrollPhysics(),
|
viewportFraction: widget.viewport ?? 0.95,
|
||||||
shrinkWrap: true,
|
enableInfiniteScroll: false,
|
||||||
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
|
|
||||||
crossAxisCount: math.min(3, _attachments.length),
|
|
||||||
mainAxisSpacing: 8.0,
|
|
||||||
crossAxisSpacing: 8.0,
|
|
||||||
),
|
),
|
||||||
itemCount: _attachments.length,
|
itemCount: _attachments.length,
|
||||||
itemBuilder: (context, idx) {
|
itemBuilder: (context, idx, _) {
|
||||||
final element = _attachments[idx];
|
final element = _attachments[idx];
|
||||||
|
if (element == null) const SizedBox.shrink();
|
||||||
|
double ratio = element!.metadata?['ratio']?.toDouble() ?? 16 / 9;
|
||||||
return Container(
|
return Container(
|
||||||
|
constraints: BoxConstraints(
|
||||||
|
maxWidth: widget.columnMaxWidth,
|
||||||
|
maxHeight: 640,
|
||||||
|
),
|
||||||
|
child: AspectRatio(
|
||||||
|
aspectRatio: ratio,
|
||||||
|
child: Container(
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: Theme.of(context).colorScheme.surfaceContainerHigh,
|
|
||||||
border: Border.all(
|
border: Border.all(
|
||||||
color: Theme.of(context).dividerColor,
|
color: Theme.of(context).dividerColor,
|
||||||
width: 1,
|
width: 1,
|
||||||
@ -243,38 +306,10 @@ class _AttachmentListState extends State<AttachmentList> {
|
|||||||
borderRadius: radius,
|
borderRadius: radius,
|
||||||
child: _buildEntry(element, idx),
|
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);
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -205,7 +205,7 @@ class _ChannelListWidgetState extends State<ChannelListWidget> {
|
|||||||
item.members!.where((e) => e.account.id != widget.selfId).firstOrNull;
|
item.members!.where((e) => e.account.id != widget.selfId).firstOrNull;
|
||||||
|
|
||||||
if (item.type == 1 && otherside != null) {
|
if (item.type == 1 && otherside != null) {
|
||||||
final avatar = AccountAvatar(
|
final avatar = AttachedCircleAvatar(
|
||||||
content: otherside.account.avatar,
|
content: otherside.account.avatar,
|
||||||
radius: 20,
|
radius: 20,
|
||||||
bgColor: Theme.of(context).colorScheme.primary,
|
bgColor: Theme.of(context).colorScheme.primary,
|
||||||
@ -241,7 +241,7 @@ class _ChannelListWidgetState extends State<ChannelListWidget> {
|
|||||||
padding: const EdgeInsets.all(2),
|
padding: const EdgeInsets.all(2),
|
||||||
elevation: 8,
|
elevation: 8,
|
||||||
),
|
),
|
||||||
badgeContent: AccountAvatar(
|
badgeContent: AttachedCircleAvatar(
|
||||||
content: item.realm?.avatar,
|
content: item.realm?.avatar,
|
||||||
radius: 10,
|
radius: 10,
|
||||||
fallbackWidget: const Icon(
|
fallbackWidget: const Icon(
|
||||||
|
@ -152,7 +152,8 @@ class _ChannelMemberListPopupState extends State<ChannelMemberListPopup> {
|
|||||||
title: Text(element.account.nick),
|
title: Text(element.account.nick),
|
||||||
subtitle: Text(element.account.name),
|
subtitle: Text(element.account.name),
|
||||||
leading: GestureDetector(
|
leading: GestureDetector(
|
||||||
child: AccountAvatar(content: element.account.avatar),
|
child:
|
||||||
|
AttachedCircleAvatar(content: element.account.avatar),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
showModalBottomSheet(
|
showModalBottomSheet(
|
||||||
useRootNavigator: true,
|
useRootNavigator: true,
|
||||||
|
@ -74,7 +74,7 @@ class _NoContentWidgetState extends State<NoContentWidget>
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
child: AccountAvatar(
|
child: AttachedCircleAvatar(
|
||||||
content: widget.userinfo!.avatar,
|
content: widget.userinfo!.avatar,
|
||||||
bgColor: Colors.transparent,
|
bgColor: Colors.transparent,
|
||||||
radius: radius,
|
radius: radius,
|
||||||
|
@ -220,7 +220,7 @@ class ChatEvent extends StatelessWidget {
|
|||||||
children: [
|
children: [
|
||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
AccountAvatar(
|
AttachedCircleAvatar(
|
||||||
content: item.sender.account.avatar,
|
content: item.sender.account.avatar,
|
||||||
radius: 9,
|
radius: 9,
|
||||||
),
|
),
|
||||||
@ -250,7 +250,8 @@ class ChatEvent extends StatelessWidget {
|
|||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
GestureDetector(
|
GestureDetector(
|
||||||
child: AccountAvatar(content: item.sender.account.avatar),
|
child:
|
||||||
|
AttachedCircleAvatar(content: item.sender.account.avatar),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
showModalBottomSheet(
|
showModalBottomSheet(
|
||||||
useRootNavigator: true,
|
useRootNavigator: true,
|
||||||
|
@ -443,7 +443,7 @@ class _ChatMessageInputState extends State<ChatMessageInput> {
|
|||||||
.map(
|
.map(
|
||||||
(x) => ChatMessageSuggestion(
|
(x) => ChatMessageSuggestion(
|
||||||
type: 'users',
|
type: 'users',
|
||||||
leading: AccountAvatar(content: x.avatar),
|
leading: AttachedCircleAvatar(content: x.avatar),
|
||||||
display: x.nick,
|
display: x.nick,
|
||||||
content: '@${x.name}',
|
content: '@${x.name}',
|
||||||
),
|
),
|
||||||
|
@ -69,7 +69,7 @@ class _AppAccountWidgetState extends State<AppAccountWidget> {
|
|||||||
bottom: 0,
|
bottom: 0,
|
||||||
end: -2,
|
end: -2,
|
||||||
),
|
),
|
||||||
child: AccountAvatar(
|
child: AttachedCircleAvatar(
|
||||||
radius: 14,
|
radius: 14,
|
||||||
content: auth.userProfile.value!['avatar'],
|
content: auth.userProfile.value!['avatar'],
|
||||||
),
|
),
|
||||||
|
@ -36,7 +36,7 @@ class RealmSwitcher extends StatelessWidget {
|
|||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
if (item != null)
|
if (item != null)
|
||||||
AccountAvatar(
|
AttachedCircleAvatar(
|
||||||
content: item.avatar,
|
content: item.avatar,
|
||||||
radius: 14,
|
radius: 14,
|
||||||
fallbackWidget: const Icon(
|
fallbackWidget: const Icon(
|
||||||
|
108
lib/widgets/posts/post_creation.dart
Normal file
108
lib/widgets/posts/post_creation.dart
Normal 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),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -12,7 +12,6 @@ import 'package:solian/screens/posts/post_detail.dart';
|
|||||||
import 'package:solian/shells/title_shell.dart';
|
import 'package:solian/shells/title_shell.dart';
|
||||||
import 'package:solian/theme.dart';
|
import 'package:solian/theme.dart';
|
||||||
import 'package:solian/widgets/account/account_avatar.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/attachments/attachment_list.dart';
|
||||||
import 'package:solian/widgets/link_expansion.dart';
|
import 'package:solian/widgets/link_expansion.dart';
|
||||||
import 'package:solian/widgets/markdown_text_content.dart';
|
import 'package:solian/widgets/markdown_text_content.dart';
|
||||||
@ -36,6 +35,7 @@ class PostItem extends StatefulWidget {
|
|||||||
final bool showFeaturedReply;
|
final bool showFeaturedReply;
|
||||||
final String? attachmentParent;
|
final String? attachmentParent;
|
||||||
final Color? backgroundColor;
|
final Color? backgroundColor;
|
||||||
|
final Function? onComment;
|
||||||
|
|
||||||
const PostItem({
|
const PostItem({
|
||||||
super.key,
|
super.key,
|
||||||
@ -52,6 +52,7 @@ class PostItem extends StatefulWidget {
|
|||||||
this.showFeaturedReply = false,
|
this.showFeaturedReply = false,
|
||||||
this.attachmentParent,
|
this.attachmentParent,
|
||||||
this.backgroundColor,
|
this.backgroundColor,
|
||||||
|
this.onComment,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -92,8 +93,6 @@ class _PostItemState extends State<PostItem> {
|
|||||||
item: item,
|
item: item,
|
||||||
).paddingSymmetric(horizontal: 12),
|
).paddingSymmetric(horizontal: 12),
|
||||||
_PostHeaderDividerWidget(item: item).paddingSymmetric(horizontal: 12),
|
_PostHeaderDividerWidget(item: item).paddingSymmetric(horizontal: 12),
|
||||||
Stack(
|
|
||||||
children: [
|
|
||||||
SizedContainer(
|
SizedContainer(
|
||||||
maxWidth: 640,
|
maxWidth: 640,
|
||||||
maxHeight: widget.isFullContent ? double.infinity : 80,
|
maxHeight: widget.isFullContent ? double.infinity : 80,
|
||||||
@ -110,15 +109,12 @@ class _PostItemState extends State<PostItem> {
|
|||||||
isSelectable: widget.isContentSelectable,
|
isSelectable: widget.isContentSelectable,
|
||||||
),
|
),
|
||||||
).paddingOnly(
|
).paddingOnly(
|
||||||
left: 16,
|
left: 12,
|
||||||
right: 12,
|
right: 12,
|
||||||
top: 2,
|
|
||||||
bottom: hasAttachment ? 4 : 0,
|
bottom: hasAttachment ? 4 : 0,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
|
||||||
),
|
|
||||||
if (_contentHeight >= 80 && !widget.isFullContent)
|
if (_contentHeight >= 80 && !widget.isFullContent)
|
||||||
Opacity(
|
Opacity(
|
||||||
opacity: 0.8,
|
opacity: 0.8,
|
||||||
@ -132,7 +128,7 @@ class _PostItemState extends State<PostItem> {
|
|||||||
right: 8,
|
right: 8,
|
||||||
top: 4,
|
top: 4,
|
||||||
),
|
),
|
||||||
_PostFooterWidget(item: item).paddingOnly(left: 16),
|
_PostFooterWidget(item: item).paddingOnly(left: 12),
|
||||||
if (attachments.isNotEmpty)
|
if (attachments.isNotEmpty)
|
||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
@ -148,7 +144,7 @@ class _PostItemState extends State<PostItem> {
|
|||||||
style: TextStyle(color: _unFocusColor),
|
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'],
|
rid: item.body['thumbnail'],
|
||||||
parentId: widget.item.id.toString(),
|
parentId: widget.item.id.toString(),
|
||||||
).paddingOnly(bottom: 4),
|
).paddingOnly(bottom: 4),
|
||||||
Row(
|
Column(
|
||||||
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(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
_PostHeaderWidget(
|
_PostHeaderWidget(
|
||||||
@ -188,12 +166,9 @@ class _PostItemState extends State<PostItem> {
|
|||||||
item: item,
|
item: item,
|
||||||
),
|
),
|
||||||
_PostHeaderDividerWidget(item: item),
|
_PostHeaderDividerWidget(item: item),
|
||||||
Stack(
|
|
||||||
children: [
|
|
||||||
SizedContainer(
|
SizedContainer(
|
||||||
maxWidth: 640,
|
maxWidth: 640,
|
||||||
maxHeight:
|
maxHeight: widget.isFullContent ? double.infinity : 320,
|
||||||
widget.isFullContent ? double.infinity : 320,
|
|
||||||
child: _MeasureSize(
|
child: _MeasureSize(
|
||||||
onChange: (size) {
|
onChange: (size) {
|
||||||
setState(() => _contentHeight = size.height);
|
setState(() => _contentHeight = size.height);
|
||||||
@ -205,30 +180,24 @@ class _PostItemState extends State<PostItem> {
|
|||||||
content: item.body['content'],
|
content: item.body['content'],
|
||||||
isAutoWarp: item.type == 'story',
|
isAutoWarp: item.type == 'story',
|
||||||
isSelectable: widget.isContentSelectable,
|
isSelectable: widget.isContentSelectable,
|
||||||
isLargeText: item.type == 'article' &&
|
isLargeText:
|
||||||
widget.isFullContent,
|
item.type == 'article' && widget.isFullContent,
|
||||||
).paddingOnly(left: 12, right: 8),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
|
||||||
),
|
),
|
||||||
if (_contentHeight >= 320 && !widget.isFullContent)
|
if (_contentHeight >= 320 && !widget.isFullContent)
|
||||||
Opacity(
|
Opacity(
|
||||||
opacity: 0.8,
|
opacity: 0.8,
|
||||||
child: InkWell(child: Text('readMore'.tr)),
|
child: InkWell(child: Text('readMore'.tr)),
|
||||||
).paddingOnly(
|
).paddingOnly(top: 4),
|
||||||
left: 12,
|
|
||||||
top: 4,
|
|
||||||
),
|
|
||||||
if (widget.item.replyTo != null && widget.isShowEmbed)
|
if (widget.item.replyTo != null && widget.isShowEmbed)
|
||||||
Container(
|
Container(
|
||||||
constraints: const BoxConstraints(maxWidth: 480),
|
constraints: const BoxConstraints(maxWidth: 480),
|
||||||
padding: const EdgeInsets.only(top: 4),
|
padding: const EdgeInsets.only(top: 8),
|
||||||
child: _PostEmbedWidget(
|
child: _PostEmbedWidget(
|
||||||
isClickable: widget.isClickable,
|
isClickable: widget.isClickable,
|
||||||
isOverrideEmbedClickable:
|
isOverrideEmbedClickable: widget.isOverrideEmbedClickable,
|
||||||
widget.isOverrideEmbedClickable,
|
|
||||||
item: widget.item.replyTo!,
|
item: widget.item.replyTo!,
|
||||||
username: widget.item.replyTo!.author.name,
|
username: widget.item.replyTo!.author.name,
|
||||||
hintText: 'postRepliedNotify',
|
hintText: 'postRepliedNotify',
|
||||||
@ -239,11 +208,10 @@ class _PostItemState extends State<PostItem> {
|
|||||||
if (widget.item.repostTo != null && widget.isShowEmbed)
|
if (widget.item.repostTo != null && widget.isShowEmbed)
|
||||||
Container(
|
Container(
|
||||||
constraints: const BoxConstraints(maxWidth: 480),
|
constraints: const BoxConstraints(maxWidth: 480),
|
||||||
padding: const EdgeInsets.only(top: 4),
|
padding: const EdgeInsets.only(top: 8),
|
||||||
child: _PostEmbedWidget(
|
child: _PostEmbedWidget(
|
||||||
isClickable: widget.isClickable,
|
isClickable: widget.isClickable,
|
||||||
isOverrideEmbedClickable:
|
isOverrideEmbedClickable: widget.isOverrideEmbedClickable,
|
||||||
widget.isOverrideEmbedClickable,
|
|
||||||
item: widget.item.repostTo!,
|
item: widget.item.repostTo!,
|
||||||
username: widget.item.repostTo!.author.name,
|
username: widget.item.repostTo!.author.name,
|
||||||
hintText: 'postRepostedNotify',
|
hintText: 'postRepostedNotify',
|
||||||
@ -251,24 +219,19 @@ class _PostItemState extends State<PostItem> {
|
|||||||
id: widget.item.repostTo!.id.toString(),
|
id: widget.item.repostTo!.id.toString(),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
_PostFooterWidget(item: item).paddingOnly(left: 12),
|
_PostFooterWidget(item: item),
|
||||||
LinkExpansion(content: item.body['content'])
|
LinkExpansion(content: item.body['content']).paddingOnly(top: 4),
|
||||||
.paddingOnly(top: 4),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
).paddingOnly(
|
).paddingOnly(
|
||||||
top: 10,
|
|
||||||
bottom:
|
|
||||||
(attachments.length == 1 && !AppTheme.isLargeScreen(context))
|
|
||||||
? 10
|
|
||||||
: 0,
|
|
||||||
right: 16,
|
right: 16,
|
||||||
left: 16,
|
left: 16,
|
||||||
),
|
),
|
||||||
_PostAttachmentWidget(item: item),
|
_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)
|
if (widget.isShowReply || widget.isReactable)
|
||||||
PostQuickAction(
|
PostQuickAction(
|
||||||
isShowReply: widget.isShowReply,
|
isShowReply: widget.isShowReply,
|
||||||
@ -280,19 +243,15 @@ class _PostItemState extends State<PostItem> {
|
|||||||
(item.metric!.reactionList[symbol] ?? 0) + changes;
|
(item.metric!.reactionList[symbol] ?? 0) + changes;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
onComment: () {
|
||||||
|
if (widget.onComment != null) {
|
||||||
|
widget.onComment!();
|
||||||
|
}
|
||||||
|
},
|
||||||
).paddingOnly(
|
).paddingOnly(
|
||||||
top: (attachments.length == 1 && !AppTheme.isLargeScreen(context))
|
left: 14,
|
||||||
? 10
|
right: 14,
|
||||||
: 6,
|
|
||||||
left:
|
|
||||||
(attachments.length == 1 && !AppTheme.isLargeScreen(context))
|
|
||||||
? 24
|
|
||||||
: 60,
|
|
||||||
right: 16,
|
|
||||||
bottom: 10,
|
|
||||||
)
|
)
|
||||||
else
|
|
||||||
const Gap(10),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
openBuilder: (_, __) => TitleShell(
|
openBuilder: (_, __) => TitleShell(
|
||||||
@ -317,7 +276,6 @@ class _PostFeaturedReplyWidget extends StatelessWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final isLargeScreen = AppTheme.isLargeScreen(context);
|
|
||||||
final unFocusColor =
|
final unFocusColor =
|
||||||
Theme.of(context).colorScheme.onSurface.withOpacity(0.75);
|
Theme.of(context).colorScheme.onSurface.withOpacity(0.75);
|
||||||
|
|
||||||
@ -325,13 +283,10 @@ class _PostFeaturedReplyWidget extends StatelessWidget {
|
|||||||
return const SizedBox.shrink();
|
return const SizedBox.shrink();
|
||||||
}
|
}
|
||||||
|
|
||||||
final List<String> attachments = item.body['attachments'] is List
|
|
||||||
? List.from(item.body['attachments']?.whereType<String>())
|
|
||||||
: List.empty();
|
|
||||||
|
|
||||||
return FutureBuilder(
|
return FutureBuilder(
|
||||||
future:
|
future: Get.find<PostProvider>().listPostFeaturedReply(
|
||||||
Get.find<PostProvider>().listPostFeaturedReply(item.id.toString()),
|
item.id.toString(),
|
||||||
|
),
|
||||||
builder: (context, snapshot) {
|
builder: (context, snapshot) {
|
||||||
if (!snapshot.hasData || snapshot.data!.isEmpty) {
|
if (!snapshot.hasData || snapshot.data!.isEmpty) {
|
||||||
return const SizedBox.shrink();
|
return const SizedBox.shrink();
|
||||||
@ -351,7 +306,7 @@ class _PostFeaturedReplyWidget extends StatelessWidget {
|
|||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
AccountAvatar(
|
AttachedCircleAvatar(
|
||||||
content: reply.author.avatar,
|
content: reply.author.avatar,
|
||||||
radius: 10,
|
radius: 10,
|
||||||
),
|
),
|
||||||
@ -423,16 +378,9 @@ class _PostFeaturedReplyWidget extends StatelessWidget {
|
|||||||
.toList(),
|
.toList(),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
)
|
).animate().fadeIn(
|
||||||
.animate()
|
|
||||||
.fadeIn(
|
|
||||||
duration: 300.ms,
|
duration: 300.ms,
|
||||||
curve: Curves.easeIn,
|
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.from(item.body['attachments']?.whereType<String>())
|
||||||
: List.empty();
|
: 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(
|
return AttachmentList(
|
||||||
parentId: item.id.toString(),
|
parentId: item.id.toString(),
|
||||||
attachmentIds: item.preload == null ? attachments : null,
|
attachmentIds: item.preload == null ? attachments : null,
|
||||||
attachments: item.preload?.attachments,
|
attachments: item.preload?.attachments,
|
||||||
autoload: false,
|
autoload: false,
|
||||||
isGrid: true,
|
isGrid: true,
|
||||||
).paddingOnly(left: 36, top: 4, bottom: 4);
|
).paddingSymmetric(horizontal: 14, vertical: 8);
|
||||||
} 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);
|
|
||||||
} else {
|
} else {
|
||||||
return AttachmentList(
|
return AttachmentList(
|
||||||
flatMaxHeight: MediaQuery.of(context).size.width,
|
|
||||||
parentId: item.id.toString(),
|
parentId: item.id.toString(),
|
||||||
attachmentIds: item.preload == null ? attachments : null,
|
attachmentIds: item.preload == null ? attachments : null,
|
||||||
attachments: item.preload?.attachments,
|
attachments: item.preload?.attachments,
|
||||||
autoload: false,
|
autoload: false,
|
||||||
);
|
).paddingOnly(bottom: 8, top: 4);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -515,16 +466,17 @@ class _PostEmbedWidget extends StatelessWidget {
|
|||||||
size: 16,
|
size: 16,
|
||||||
color: unFocusColor,
|
color: unFocusColor,
|
||||||
),
|
),
|
||||||
|
const Gap(6),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Text(
|
child: Text(
|
||||||
hintText.trParams(
|
hintText.trParams(
|
||||||
{'username': '@$username'},
|
{'username': '@$username'},
|
||||||
),
|
),
|
||||||
style: TextStyle(color: unFocusColor),
|
style: TextStyle(color: unFocusColor),
|
||||||
).paddingOnly(left: 6),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
).paddingOnly(left: 12),
|
).paddingOnly(left: 2),
|
||||||
Card(
|
Card(
|
||||||
elevation: 1,
|
elevation: 1,
|
||||||
child: PostItem(
|
child: PostItem(
|
||||||
@ -560,9 +512,7 @@ class _PostHeaderDividerWidget extends StatelessWidget {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
if (item.body['description'] != null || item.body['title'] != null) {
|
if (item.body['description'] != null || item.body['title'] != null) {
|
||||||
return const Divider(thickness: 0.3, height: 1).paddingSymmetric(
|
return const Gap(8);
|
||||||
vertical: 8,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
return const SizedBox.shrink();
|
return const SizedBox.shrink();
|
||||||
}
|
}
|
||||||
@ -634,49 +584,59 @@ class _PostHeaderWidget extends StatelessWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Row(
|
return Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
if (isCompact)
|
|
||||||
AccountAvatar(
|
AccountAvatar(
|
||||||
content: item.author.avatar,
|
content: item.author.avatar,
|
||||||
radius: 10,
|
username: item.author.name,
|
||||||
).paddingOnly(left: 2, top: 1),
|
radius: isCompact ? 10 : null,
|
||||||
|
),
|
||||||
|
Gap(isCompact ? 6 : 8),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Row(
|
Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
item.author.nick,
|
item.author.nick,
|
||||||
style: const TextStyle(fontWeight: FontWeight.bold),
|
style: const TextStyle(fontWeight: FontWeight.bold),
|
||||||
),
|
),
|
||||||
RelativeDate(item.publishedAt?.toLocal() ?? DateTime.now())
|
if (isCompact) const Gap(4),
|
||||||
.paddingOnly(left: 4),
|
if (isCompact)
|
||||||
|
RelativeDate(
|
||||||
|
item.publishedAt?.toLocal() ?? DateTime.now(),
|
||||||
|
).paddingOnly(top: 1),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
if (item.body['title'] != null)
|
if (!isCompact)
|
||||||
Text(
|
RelativeDate(item.publishedAt?.toLocal() ?? DateTime.now()),
|
||||||
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,
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
).paddingOnly(left: isCompact ? 6 : 12),
|
),
|
||||||
),
|
),
|
||||||
if (item.type == 'article')
|
if (item.type == 'article')
|
||||||
Badge(
|
Badge(
|
||||||
label: Text('article'.tr),
|
label: Text('article'.tr),
|
||||||
).paddingOnly(top: 3),
|
).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,
|
||||||
|
),
|
||||||
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,8 @@ import 'package:get/get.dart';
|
|||||||
import 'package:infinite_scroll_pagination/infinite_scroll_pagination.dart';
|
import 'package:infinite_scroll_pagination/infinite_scroll_pagination.dart';
|
||||||
import 'package:solian/models/post.dart';
|
import 'package:solian/models/post.dart';
|
||||||
import 'package:solian/providers/auth.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_action.dart';
|
||||||
import 'package:solian/widgets/posts/post_item.dart';
|
import 'package:solian/widgets/posts/post_item.dart';
|
||||||
|
|
||||||
@ -12,6 +14,7 @@ class PostListWidget extends StatelessWidget {
|
|||||||
final bool isNestedClickable;
|
final bool isNestedClickable;
|
||||||
final PagingController<int, Post> controller;
|
final PagingController<int, Post> controller;
|
||||||
final Color? backgroundColor;
|
final Color? backgroundColor;
|
||||||
|
final EdgeInsets? padding;
|
||||||
|
|
||||||
const PostListWidget({
|
const PostListWidget({
|
||||||
super.key,
|
super.key,
|
||||||
@ -20,6 +23,7 @@ class PostListWidget extends StatelessWidget {
|
|||||||
this.isClickable = true,
|
this.isClickable = true,
|
||||||
this.isNestedClickable = true,
|
this.isNestedClickable = true,
|
||||||
this.backgroundColor,
|
this.backgroundColor,
|
||||||
|
this.padding,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -29,7 +33,9 @@ class PostListWidget extends StatelessWidget {
|
|||||||
pagingController: controller,
|
pagingController: controller,
|
||||||
builderDelegate: PagedChildBuilderDelegate<Post>(
|
builderDelegate: PagedChildBuilderDelegate<Post>(
|
||||||
itemBuilder: (context, item, index) {
|
itemBuilder: (context, item, index) {
|
||||||
return PostListEntryWidget(
|
return Padding(
|
||||||
|
padding: padding ?? EdgeInsets.zero,
|
||||||
|
child: PostListEntryWidget(
|
||||||
isShowEmbed: isShowEmbed,
|
isShowEmbed: isShowEmbed,
|
||||||
isNestedClickable: isNestedClickable,
|
isNestedClickable: isNestedClickable,
|
||||||
isClickable: isClickable,
|
isClickable: isClickable,
|
||||||
@ -39,6 +45,7 @@ class PostListWidget extends StatelessWidget {
|
|||||||
onUpdate: () {
|
onUpdate: () {
|
||||||
controller.refresh();
|
controller.refresh();
|
||||||
},
|
},
|
||||||
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
@ -77,6 +84,22 @@ class PostListEntryWidget extends StatelessWidget {
|
|||||||
isClickable: isNestedClickable,
|
isClickable: isNestedClickable,
|
||||||
showFeaturedReply: showFeaturedReply,
|
showFeaturedReply: showFeaturedReply,
|
||||||
backgroundColor: backgroundColor,
|
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),
|
).paddingSymmetric(vertical: 8),
|
||||||
onLongPress: () {
|
onLongPress: () {
|
||||||
final AuthProvider auth = Get.find();
|
final AuthProvider auth = Get.find();
|
||||||
|
@ -11,6 +11,7 @@ class PostQuickAction extends StatefulWidget {
|
|||||||
final Post item;
|
final Post item;
|
||||||
final bool isReactable;
|
final bool isReactable;
|
||||||
final bool isShowReply;
|
final bool isShowReply;
|
||||||
|
final Function onComment;
|
||||||
final void Function(String symbol, int num) onReact;
|
final void Function(String symbol, int num) onReact;
|
||||||
|
|
||||||
const PostQuickAction({
|
const PostQuickAction({
|
||||||
@ -18,6 +19,7 @@ class PostQuickAction extends StatefulWidget {
|
|||||||
required this.item,
|
required this.item,
|
||||||
this.isShowReply = true,
|
this.isShowReply = true,
|
||||||
this.isReactable = true,
|
this.isReactable = true,
|
||||||
|
required this.onComment,
|
||||||
required this.onReact,
|
required this.onReact,
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -106,7 +108,11 @@ class _PostQuickActionState extends State<PostQuickAction> {
|
|||||||
builder: (context) {
|
builder: (context) {
|
||||||
return PostReplyListPopup(item: widget.item);
|
return PostReplyListPopup(item: widget.item);
|
||||||
},
|
},
|
||||||
);
|
).then((signal) {
|
||||||
|
if (signal == true) {
|
||||||
|
widget.onComment();
|
||||||
|
}
|
||||||
|
});
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -53,6 +53,7 @@ class _PostReplyListState extends State<PostReplyList> {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return PostListWidget(
|
return PostListWidget(
|
||||||
|
padding: EdgeInsets.symmetric(horizontal: 10),
|
||||||
isShowEmbed: false,
|
isShowEmbed: false,
|
||||||
controller: _pagingController,
|
controller: _pagingController,
|
||||||
backgroundColor: widget.backgroundColor,
|
backgroundColor: widget.backgroundColor,
|
||||||
@ -70,16 +71,30 @@ class PostReplyListPopup extends StatelessWidget {
|
|||||||
return Column(
|
return Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Row(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: Text(
|
||||||
'postReplies'.tr,
|
'postReplies'.tr,
|
||||||
style: Theme.of(context).textTheme.headlineSmall,
|
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(
|
Expanded(
|
||||||
child: CustomScrollView(
|
child: CustomScrollView(
|
||||||
slivers: [
|
slivers: [
|
||||||
PostReplyList(
|
PostReplyList(
|
||||||
item: item,
|
item: item,
|
||||||
backgroundColor: Theme.of(context).colorScheme.surfaceContainerLow,
|
backgroundColor:
|
||||||
|
Theme.of(context).colorScheme.surfaceContainerLow,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
@ -149,7 +149,8 @@ class _RealmMemberListPopupState extends State<RealmMemberListPopup> {
|
|||||||
title: Text(element.account.nick),
|
title: Text(element.account.nick),
|
||||||
subtitle: Text(element.account.name),
|
subtitle: Text(element.account.name),
|
||||||
leading: GestureDetector(
|
leading: GestureDetector(
|
||||||
child: AccountAvatar(content: element.account.avatar),
|
child:
|
||||||
|
AttachedCircleAvatar(content: element.account.avatar),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
showModalBottomSheet(
|
showModalBottomSheet(
|
||||||
useRootNavigator: true,
|
useRootNavigator: true,
|
||||||
|
Loading…
Reference in New Issue
Block a user