Compare commits

...

4 Commits

Author SHA1 Message Date
25550ba197 💄 Changes to the showing of realm post 2025-03-09 00:11:01 +08:00
3defd3a593 💄 Modify explore appbar 2025-03-08 23:51:22 +08:00
d62ed4c375 Adjust explore categorized mode 2025-03-08 22:40:17 +08:00
857f3cc832 Post drafts 2025-03-08 22:32:38 +08:00
15 changed files with 371 additions and 118 deletions

View File

@ -769,5 +769,7 @@
"decryptingKeyNotFound": "Key not found or exchange failed, the other party may not be online", "decryptingKeyNotFound": "Key not found or exchange failed, the other party may not be online",
"messageUnablePreview": "Unable preview", "messageUnablePreview": "Unable preview",
"messageUnablePreviewEncrypted": "Unable preview encrypted message", "messageUnablePreviewEncrypted": "Unable preview encrypted message",
"postViewInGlobalDescription": "Do not view the post in the specific realm." "postViewInGlobalDescription": "Do not view the post in the specific realm.",
"postDraftSaved": "The draft has been saved.",
"postDraftBox": "Draft Box"
} }

View File

@ -767,5 +767,7 @@
"decryptingKeyNotFound": "未找到密钥对或交换失败,对方可能不在线", "decryptingKeyNotFound": "未找到密钥对或交换失败,对方可能不在线",
"messageUnablePreview": "无法预览消息", "messageUnablePreview": "无法预览消息",
"messageUnablePreviewEncrypted": "无法预览加密消息", "messageUnablePreviewEncrypted": "无法预览加密消息",
"postViewInGlobalDescription": "不查看特定领域的帖子。" "postViewInGlobalDescription": "不查看特定领域的帖子。",
"postDraftSaved": "已保存为草稿。",
"postDraftBox": "草稿箱"
} }

View File

@ -767,5 +767,7 @@
"decryptingKeyNotFound": "未找到密鑰對或交換失敗,對方可能不在線", "decryptingKeyNotFound": "未找到密鑰對或交換失敗,對方可能不在線",
"messageUnablePreview": "無法預覽消息", "messageUnablePreview": "無法預覽消息",
"messageUnablePreviewEncrypted": "無法預覽加密消息", "messageUnablePreviewEncrypted": "無法預覽加密消息",
"postViewInGlobalDescription": "不查看特定領域的帖子。" "postViewInGlobalDescription": "不查看特定領域的帖子。",
"postDraftSaved": "已保存為草稿。",
"postDraftBox": "草稿箱"
} }

View File

@ -767,5 +767,7 @@
"decryptingKeyNotFound": "未找到密鑰對或交換失敗,對方可能不在線", "decryptingKeyNotFound": "未找到密鑰對或交換失敗,對方可能不在線",
"messageUnablePreview": "無法預覽消息", "messageUnablePreview": "無法預覽消息",
"messageUnablePreviewEncrypted": "無法預覽加密消息", "messageUnablePreviewEncrypted": "無法預覽加密消息",
"postViewInGlobalDescription": "不查看特定領域的帖子。" "postViewInGlobalDescription": "不查看特定領域的帖子。",
"postDraftSaved": "已保存為草稿。",
"postDraftBox": "草稿箱"
} }

View File

@ -208,6 +208,7 @@ class PostWriteController extends ChangeNotifier {
SnRealm? realm; SnRealm? realm;
SnPublisher? publisher; SnPublisher? publisher;
SnPost? editingPost, repostingPost, replyingPost; SnPost? editingPost, repostingPost, replyingPost;
bool editingDraft = false;
int visibility = 0; int visibility = 0;
List<int> visibleUsers = List.empty(); List<int> visibleUsers = List.empty();
@ -254,6 +255,8 @@ class PostWriteController extends ChangeNotifier {
post.preload?.attachments?.map((ele) => PostWriteMedia(ele)) ?? []); post.preload?.attachments?.map((ele) => PostWriteMedia(ele)) ?? []);
poll = post.preload?.poll; poll = post.preload?.poll;
editingDraft = post.isDraft;
if (post.preload?.thumbnail != null && if (post.preload?.thumbnail != null &&
(post.preload?.thumbnail?.rid.isNotEmpty ?? false)) { (post.preload?.thumbnail?.rid.isNotEmpty ?? false)) {
thumbnail = PostWriteMedia(post.preload!.thumbnail); thumbnail = PostWriteMedia(post.preload!.thumbnail);
@ -474,7 +477,10 @@ class PostWriteController extends ChangeNotifier {
notifyListeners(); notifyListeners();
} }
Future<void> sendPost(BuildContext context) async { Future<void> sendPost(
BuildContext context, {
bool saveAsDraft = false,
}) async {
if (isBusy || publisher == null) return; if (isBusy || publisher == null) return;
final sn = context.read<SnNetworkProvider>(); final sn = context.read<SnNetworkProvider>();
@ -552,7 +558,7 @@ class PostWriteController extends ChangeNotifier {
// Posting the content // Posting the content
try { try {
final baseProgressVal = progress!; final baseProgressVal = progress!;
await sn.client.request( final resp = await sn.client.request(
[ [
'/cgi/co/$mode', '/cgi/co/$mode',
if (editingPost != null) '${editingPost!.id}', if (editingPost != null) '${editingPost!.id}',
@ -585,6 +591,7 @@ class PostWriteController extends ChangeNotifier {
if (videoAttachment != null) 'video': videoAttachment!.rid, if (videoAttachment != null) 'video': videoAttachment!.rid,
if (poll != null) 'poll': poll!.id, if (poll != null) 'poll': poll!.id,
if (realm != null) 'realm': realm!.id, if (realm != null) 'realm': realm!.id,
'is_draft': saveAsDraft,
}, },
onSendProgress: (count, total) { onSendProgress: (count, total) {
progress = progress =
@ -601,7 +608,16 @@ class PostWriteController extends ChangeNotifier {
method: editingPost != null ? 'PUT' : 'POST', method: editingPost != null ? 'PUT' : 'POST',
), ),
); );
reset(); if (saveAsDraft) {
if (!context.mounted) return;
editingDraft = true;
final out = SnPost.fromJson(resp.data);
final pt = context.read<SnPostContentProvider>();
editingPost = await pt.completePostData(out);
notifyListeners();
} else {
reset();
}
} catch (err) { } catch (err) {
if (!context.mounted) return; if (!context.mounted) return;
context.showErrorDialog(err); context.showErrorDialog(err);

View File

@ -60,16 +60,24 @@ class SnPostContentProvider {
out[i] = out[i].copyWith( out[i] = out[i].copyWith(
preload: SnPostPreload( preload: SnPostPreload(
thumbnail: attachments.where((ele) => ele?.rid == out[i].body['thumbnail']).firstOrNull, thumbnail: attachments
attachments: attachments.where((ele) => out[i].body['attachments']?.contains(ele?.rid) ?? false).toList(), .where((ele) => ele?.rid == out[i].body['thumbnail'])
video: attachments.where((ele) => ele?.rid == out[i].body['video']).firstOrNull, .firstOrNull,
attachments: attachments
.where((ele) =>
out[i].body['attachments']?.contains(ele?.rid) ?? false)
.toList(),
video: attachments
.where((ele) => ele?.rid == out[i].body['video'])
.firstOrNull,
poll: poll, poll: poll,
realm: realm, realm: realm,
), ),
); );
} }
uids.addAll(attachments.where((ele) => ele != null).map((ele) => ele!.accountId)); uids.addAll(
attachments.where((ele) => ele != null).map((ele) => ele!.accountId));
await _ud.listAccount(uids); await _ud.listAccount(uids);
return out; return out;
@ -107,15 +115,23 @@ class SnPostContentProvider {
out = out.copyWith( out = out.copyWith(
preload: SnPostPreload( preload: SnPostPreload(
thumbnail: attachments.where((ele) => ele?.rid == out.body['thumbnail']).firstOrNull, thumbnail: attachments
attachments: attachments.where((ele) => out.body['attachments']?.contains(ele?.rid) ?? false).toList(), .where((ele) => ele?.rid == out.body['thumbnail'])
video: attachments.where((ele) => ele?.rid == out.body['video']).firstOrNull, .firstOrNull,
attachments: attachments
.where(
(ele) => out.body['attachments']?.contains(ele?.rid) ?? false)
.toList(),
video: attachments
.where((ele) => ele?.rid == out.body['video'])
.firstOrNull,
poll: poll, poll: poll,
realm: realm, realm: realm,
), ),
); );
uids.addAll(attachments.where((ele) => ele != null).map((ele) => ele!.accountId)); uids.addAll(
attachments.where((ele) => ele != null).map((ele) => ele!.accountId));
await _ud.listAccount(uids); await _ud.listAccount(uids);
return out; return out;
@ -138,17 +154,22 @@ class SnPostContentProvider {
Iterable<String>? tags, Iterable<String>? tags,
String? realm, String? realm,
String? channel, String? channel,
bool isDraft = false,
}) async { }) async {
final resp = await _sn.client.get('/cgi/co/posts', queryParameters: { final resp = await _sn.client.get(
'take': take, '/cgi/co/posts${isDraft ? '/drafts' : ''}',
'offset': offset, queryParameters: {
if (type != null) 'type': type, 'take': take,
if (author != null) 'author': author, 'offset': offset,
if (tags?.isNotEmpty ?? false) 'tags': tags!.join(','), if (type != null) 'type': type,
if (categories?.isNotEmpty ?? false) 'categories': categories!.join(','), if (author != null) 'author': author,
if (realm != null) 'realm': realm, if (tags?.isNotEmpty ?? false) 'tags': tags!.join(','),
if (channel != null) 'channel': channel, if (categories?.isNotEmpty ?? false)
}); 'categories': categories!.join(','),
if (realm != null) 'realm': realm,
if (channel != null) 'channel': channel,
},
);
final List<SnPost> out = await _preloadRelatedDataInBatch( final List<SnPost> out = await _preloadRelatedDataInBatch(
List.from(resp.data['data']?.map((e) => SnPost.fromJson(e)) ?? []), List.from(resp.data['data']?.map((e) => SnPost.fromJson(e)) ?? []),
); );
@ -161,7 +182,8 @@ class SnPostContentProvider {
int take = 10, int take = 10,
int offset = 0, int offset = 0,
}) async { }) async {
final resp = await _sn.client.get('/cgi/co/posts/$parentId/replies', queryParameters: { final resp = await _sn.client
.get('/cgi/co/posts/$parentId/replies', queryParameters: {
'take': take, 'take': take,
'offset': offset, 'offset': offset,
}); });
@ -200,4 +222,9 @@ class SnPostContentProvider {
); );
return out; return out;
} }
Future<SnPost> completePostData(SnPost post) async {
final out = await _preloadRelatedDataSingle(post);
return out;
}
} }

View File

@ -28,6 +28,7 @@ import 'package:surface/screens/news/news_detail.dart';
import 'package:surface/screens/news/news_list.dart'; import 'package:surface/screens/news/news_list.dart';
import 'package:surface/screens/notification.dart'; import 'package:surface/screens/notification.dart';
import 'package:surface/screens/post/post_detail.dart'; import 'package:surface/screens/post/post_detail.dart';
import 'package:surface/screens/post/post_draft.dart';
import 'package:surface/screens/post/post_editor.dart'; import 'package:surface/screens/post/post_editor.dart';
import 'package:surface/screens/post/publisher_page.dart'; import 'package:surface/screens/post/publisher_page.dart';
import 'package:surface/screens/post/post_search.dart'; import 'package:surface/screens/post/post_search.dart';
@ -65,6 +66,11 @@ final _appRoutes = [
name: 'explore', name: 'explore',
builder: (context, state) => const ExploreScreen(), builder: (context, state) => const ExploreScreen(),
routes: [ routes: [
GoRoute(
path: '/draft',
name: 'postDraftBox',
builder: (context, state) => const PostDraftBox(),
),
GoRoute( GoRoute(
path: '/write', path: '/write',
name: 'postEditor', name: 'postEditor',

View File

@ -91,8 +91,12 @@ class _ExploreScreenState extends State<ExploreScreen>
_showCategories = !_showCategories; _showCategories = !_showCategories;
if (_showCategories) { if (_showCategories) {
_tabController = TabController(length: _categories.length, vsync: this); _tabController = TabController(length: _categories.length, vsync: this);
_listKey.currentState?.setCategory(_categories[_tabController.index]);
_listKey.currentState?.refreshPosts();
} else { } else {
_tabController = TabController(length: kPostChannels.length, vsync: this); _tabController = TabController(length: kPostChannels.length, vsync: this);
_listKey.currentState?.setCategory(null);
_listKey.currentState?.refreshPosts();
} }
_tabListen(); _tabListen();
setState(() {}); setState(() {});
@ -192,6 +196,21 @@ class _ExploreScreenState extends State<ExploreScreen>
), ),
], ],
), ),
Row(
children: [
Text('postDraftBox').tr(),
const Gap(20),
FloatingActionButton(
heroTag: null,
tooltip: 'postDraftBox'.tr(),
onPressed: () {
GoRouter.of(context).pushNamed('postDraftBox');
_fabKey.currentState!.toggle();
},
child: const Icon(Symbols.box_edit),
),
],
),
], ],
), ),
body: NestedScrollView( body: NestedScrollView(
@ -205,37 +224,47 @@ class _ExploreScreenState extends State<ExploreScreen>
title: Row( title: Row(
children: [ children: [
IconButton( IconButton(
padding: EdgeInsets.zero, icon: const Icon(Symbols.shuffle),
constraints: const BoxConstraints(), onPressed: () {},
visualDensity: VisualDensity.compact,
icon: _listKey.currentState?.realm != null
? AccountImage(
content: _listKey.currentState!.realm!.avatar,
radius: 14,
)
: const Icon(Symbols.group),
onPressed: () {
showModalBottomSheet(
context: context,
builder: (context) => _PostListRealmPopup(
realms: _realms,
onUpdate: (realm) {
_listKey.currentState?.setRealm(realm);
_listKey.currentState?.refreshPosts();
Future.delayed(const Duration(milliseconds: 100),
() {
if (mounted) {
setState(() {});
}
});
},
),
);
},
), ),
Expanded( Expanded(
child: Center( child: Center(
child: Text('screenExplore').tr(), child: IconButton(
padding: EdgeInsets.zero,
constraints: const BoxConstraints(),
visualDensity: VisualDensity.compact,
icon: _listKey.currentState?.realm != null
? AccountImage(
content: _listKey.currentState!.realm!.avatar,
radius: 18,
)
: Image.asset(
'assets/icon/icon-dark.png',
width: 36,
height: 36,
color: Theme.of(context)
.appBarTheme
.foregroundColor,
),
onPressed: () {
showModalBottomSheet(
context: context,
builder: (context) => _PostListRealmPopup(
realms: _realms,
onUpdate: (realm) {
_listKey.currentState?.setRealm(realm);
_listKey.currentState?.refreshPosts();
Future.delayed(
const Duration(milliseconds: 100), () {
if (mounted) {
setState(() {});
}
});
},
),
);
},
),
), ),
), ),
], ],
@ -279,11 +308,12 @@ class _ExploreScreenState extends State<ExploreScreen>
crossAxisAlignment: CrossAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center,
children: [ children: [
Icon( Icon(
kCategoryIcons[category.alias] ?? kCategoryIcons[category.alias] ??
Symbols.question_mark, Symbols.question_mark,
color: Theme.of(context) color: Theme.of(context)
.appBarTheme .appBarTheme
.foregroundColor!), .foregroundColor!,
),
const Gap(8), const Gap(8),
Flexible( Flexible(
child: Text( child: Text(
@ -293,9 +323,11 @@ class _ExploreScreenState extends State<ExploreScreen>
.tr() .tr()
: category.name, : category.name,
maxLines: 1, maxLines: 1,
).textColor(Theme.of(context) ).textColor(
.appBarTheme Theme.of(context)
.foregroundColor!), .appBarTheme
.foregroundColor!,
),
), ),
], ],
), ),
@ -321,9 +353,11 @@ class _ExploreScreenState extends State<ExploreScreen>
child: Text( child: Text(
'postChannel$channel', 'postChannel$channel',
maxLines: 1, maxLines: 1,
).tr().textColor(Theme.of(context) ).tr().textColor(
.appBarTheme Theme.of(context)
.foregroundColor), .appBarTheme
.foregroundColor,
),
), ),
], ],
), ),

View File

@ -0,0 +1,88 @@
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:gap/gap.dart';
import 'package:provider/provider.dart';
import 'package:surface/providers/post.dart';
import 'package:surface/types/post.dart';
import 'package:surface/widgets/dialog.dart';
import 'package:surface/widgets/loading_indicator.dart';
import 'package:surface/widgets/navigation/app_scaffold.dart';
import 'package:surface/widgets/post/post_item.dart';
import 'package:very_good_infinite_list/very_good_infinite_list.dart';
class PostDraftBox extends StatefulWidget {
const PostDraftBox({super.key});
@override
State<PostDraftBox> createState() => _PostDraftBoxState();
}
class _PostDraftBoxState extends State<PostDraftBox> {
bool _isBusy = false;
final List<SnPost> _posts = List.empty(growable: true);
int? _totalCount;
Future<void> _fetchPosts() async {
setState(() => _isBusy = true);
try {
final pt = context.read<SnPostContentProvider>();
final resp = await pt.listPosts(
take: 10,
offset: _posts.length,
isDraft: true,
);
final out = resp.$1;
_totalCount = resp.$2;
if (!mounted) return;
_posts.addAll(out);
} catch (err) {
if (!mounted) return;
context.showErrorDialog(err);
} finally {
setState(() => _isBusy = false);
}
}
@override
Widget build(BuildContext context) {
return AppScaffold(
appBar: AppBar(
title: Text('postDraftBox').tr(),
),
body: Column(
children: [
LoadingIndicator(isActive: _isBusy),
Expanded(
child: RefreshIndicator(
onRefresh: () {
_posts.clear();
return _fetchPosts();
},
child: InfiniteList(
padding: EdgeInsets.only(top: 8),
hasReachedMax:
_totalCount != null && _posts.length >= _totalCount!,
itemCount: _posts.length,
onFetchData: () => _fetchPosts(),
itemBuilder: (context, idx) {
final ele = _posts[idx];
return OpenablePostItem(
data: ele,
onChanged: (data) {
_posts[idx] = data;
},
onDeleted: () {
_posts.clear();
_fetchPosts();
},
);
},
separatorBuilder: (_, __) => const Gap(8),
),
),
),
],
),
);
}
}

View File

@ -138,6 +138,15 @@ class _PostEditorScreenState extends State<PostEditorScreen>
], ],
scope: HotKeyScope.inapp, scope: HotKeyScope.inapp,
); );
final HotKey _saveDraftHotKey = HotKey(
key: PhysicalKeyboardKey.keyS,
modifiers: [
(!kIsWeb && Platform.isMacOS)
? HotKeyModifier.meta
: HotKeyModifier.control
],
scope: HotKeyScope.inapp,
);
void _registerHotKey() { void _registerHotKey() {
if (kIsWeb || Platform.isAndroid || Platform.isIOS) return; if (kIsWeb || Platform.isAndroid || Platform.isIOS) return;
@ -153,6 +162,11 @@ class _PostEditorScreenState extends State<PostEditorScreen>
]); ]);
setState(() {}); setState(() {});
}); });
hotKeyManager.register(_saveDraftHotKey, keyDownHandler: (_) async {
if (mounted) {
_writeController.sendPost(context);
}
});
} }
void _showPublisherPopup() { void _showPublisherPopup() {
@ -218,6 +232,7 @@ class _PostEditorScreenState extends State<PostEditorScreen>
_writeController.dispose(); _writeController.dispose();
if (!kIsWeb && !(Platform.isAndroid || Platform.isIOS)) { if (!kIsWeb && !(Platform.isAndroid || Platform.isIOS)) {
hotKeyManager.unregister(_pasteHotKey); hotKeyManager.unregister(_pasteHotKey);
hotKeyManager.unregister(_saveDraftHotKey);
} }
super.dispose(); super.dispose();
} }
@ -269,6 +284,20 @@ class _PostEditorScreenState extends State<PostEditorScreen>
: 'untitled'.tr(), : 'untitled'.tr(),
), ),
actions: [ actions: [
IconButton(
icon: _writeController.editingDraft
? const Icon(Icons.save)
: const Icon(Symbols.save_as),
onPressed: () {
_writeController.sendPost(context, saveAsDraft: true).then(
(_) {
if (!context.mounted) return;
context.showSnackbar('postDraftSaved'.tr());
HapticFeedback.mediumImpact();
},
);
},
),
IconButton( IconButton(
icon: const Icon(Symbols.tune), icon: const Icon(Symbols.tune),
onPressed: _writeController.isBusy ? null : _updateMeta, onPressed: _writeController.isBusy ? null : _updateMeta,
@ -296,7 +325,8 @@ class _PostEditorScreenState extends State<PostEditorScreen>
), ),
body: Column( body: Column(
children: [ children: [
if (_writeController.editingPost != null) if (_writeController.editingPost != null &&
!_writeController.editingDraft)
Container( Container(
padding: const EdgeInsets.only( padding: const EdgeInsets.only(
top: 4, bottom: 4, left: 20, right: 20), top: 4, bottom: 4, left: 20, right: 20),

View File

@ -13,6 +13,7 @@ class AccountImage extends StatelessWidget {
final double? borderRadius; final double? borderRadius;
final Widget? fallbackWidget; final Widget? fallbackWidget;
final Widget? badge; final Widget? badge;
final Offset? badgeOffset;
const AccountImage({ const AccountImage({
super.key, super.key,
@ -23,6 +24,7 @@ class AccountImage extends StatelessWidget {
this.borderRadius, this.borderRadius,
this.fallbackWidget, this.fallbackWidget,
this.badge, this.badge,
this.badgeOffset,
}); });
@override @override
@ -40,7 +42,8 @@ class AccountImage extends StatelessWidget {
borderRadius: BorderRadius.circular(borderRadius ?? radius ?? 20), borderRadius: BorderRadius.circular(borderRadius ?? radius ?? 20),
child: (content?.isEmpty ?? true) child: (content?.isEmpty ?? true)
? Container( ? Container(
color: backgroundColor ?? Theme.of(context).colorScheme.primaryContainer, color: backgroundColor ??
Theme.of(context).colorScheme.primaryContainer,
child: (fallbackWidget ?? child: (fallbackWidget ??
Icon( Icon(
Symbols.account_circle, Symbols.account_circle,
@ -58,8 +61,8 @@ class AccountImage extends StatelessWidget {
), ),
if (badge != null) if (badge != null)
Positioned( Positioned(
right: -4, right: badgeOffset?.dx ?? -4,
bottom: -2, bottom: badgeOffset?.dy ?? -2,
child: badge!, child: badge!,
), ),
], ],

View File

@ -214,7 +214,8 @@ class ChatMessage extends StatelessWidget {
data.type == 'messages.new' && data.type == 'messages.new' &&
(data.body['text']?.isNotEmpty ?? false) && (data.body['text']?.isNotEmpty ?? false) &&
(cfg.prefs.getBool(kAppExpandChatLink) ?? true)) (cfg.prefs.getBool(kAppExpandChatLink) ?? true))
LinkPreviewWidget(text: data.body['text']!).padding(left: 48), LinkPreviewWidget(text: data.body['text']!)
.padding(left: isCompact ? 0 : 48),
if (data.preload?.attachments?.isNotEmpty ?? false) if (data.preload?.attachments?.isNotEmpty ?? false)
AttachmentList( AttachmentList(
data: data.preload!.attachments!, data: data.preload!.attachments!,

View File

@ -223,7 +223,7 @@ class PostItem extends StatelessWidget {
onShareImage: () => _doShareViaPicture(context), onShareImage: () => _doShareViaPicture(context),
onSelectAnswer: onSelectAnswer, onSelectAnswer: onSelectAnswer,
onDeleted: () { onDeleted: () {
if (onDeleted != null) {} onDeleted?.call();
}, },
).padding(bottom: 8), ).padding(bottom: 8),
if (data.preload?.video != null) if (data.preload?.video != null)
@ -272,7 +272,7 @@ class PostItem extends StatelessWidget {
onShareImage: () => _doShareViaPicture(context), onShareImage: () => _doShareViaPicture(context),
onSelectAnswer: onSelectAnswer, onSelectAnswer: onSelectAnswer,
onDeleted: () { onDeleted: () {
if (onDeleted != null) {} onDeleted?.call();
}, },
).padding(horizontal: 12, top: 8, bottom: 8), ).padding(horizontal: 12, top: 8, bottom: 8),
if (data.preload?.video != null) if (data.preload?.video != null)
@ -363,7 +363,7 @@ class PostItem extends StatelessWidget {
onShareImage: () => _doShareViaPicture(context), onShareImage: () => _doShareViaPicture(context),
onSelectAnswer: onSelectAnswer, onSelectAnswer: onSelectAnswer,
onDeleted: () { onDeleted: () {
if (onDeleted != null) onDeleted!(); onDeleted?.call();
}, },
).padding(horizontal: 12, vertical: 8), ).padding(horizontal: 12, vertical: 8),
if (data.preload?.video != null) if (data.preload?.video != null)
@ -923,27 +923,48 @@ class _PostContentHeader extends StatelessWidget {
return Row( return Row(
children: [ children: [
GestureDetector( GestureDetector(
child: AccountImage( child: data.preload?.realm == null
content: data.publisher.avatar, ? AccountImage(
radius: isCompact ? 12 : 20, content: data.publisher.avatar,
borderRadius: data.publisher.type == 1 ? (isCompact ? 4 : 8) : 20, radius: isCompact ? 12 : 20,
badge: (user?.badges.isNotEmpty ?? false) borderRadius:
? Icon( data.publisher.type == 1 ? (isCompact ? 4 : 8) : 20,
kBadgesMeta[user!.badges.first.type]?.$2 ?? badge: (user?.badges.isNotEmpty ?? false)
Symbols.question_mark, ? Icon(
color: kBadgesMeta[user.badges.first.type]?.$3, kBadgesMeta[user!.badges.first.type]?.$2 ??
fill: 1, Symbols.question_mark,
size: 18, color: kBadgesMeta[user.badges.first.type]?.$3,
shadows: [ fill: 1,
Shadow( size: 18,
offset: Offset(1, 1), shadows: [
blurRadius: 5.0, Shadow(
color: Color.fromARGB(200, 0, 0, 0), offset: Offset(1, 1),
blurRadius: 5.0,
color: Color.fromARGB(200, 0, 0, 0),
),
],
)
: null,
)
: AccountImage(
content: data.preload!.realm!.avatar,
radius: isCompact ? 12 : 20,
borderRadius: isCompact ? 4 : 8,
badgeOffset: Offset(-6, -4),
badge: Container(
decoration: BoxDecoration(
border: Border.all(
color: Theme.of(context).colorScheme.surface,
width: 2,
), ),
], borderRadius: BorderRadius.circular(10),
) ),
: null, child: AccountImage(
), content: data.publisher.avatar,
radius: 10,
),
),
),
onTap: () { onTap: () {
showPopover( showPopover(
backgroundColor: Theme.of(context).colorScheme.surface, backgroundColor: Theme.of(context).colorScheme.surface,
@ -988,7 +1009,17 @@ class _PostContentHeader extends StatelessWidget {
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text(data.publisher.nick).bold(), Row(
children: [
Text(data.publisher.nick).bold(),
if (data.preload?.realm != null)
const Icon(Symbols.arrow_right, size: 16)
.padding(horizontal: 2)
.opacity(0.5),
if (data.preload?.realm != null)
Text(data.preload!.realm!.name),
],
),
Row( Row(
children: [ children: [
Text('@${data.publisher.name}').fontSize(13), Text('@${data.publisher.name}').fontSize(13),

View File

@ -173,10 +173,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: built_value name: built_value
sha256: "8b158ab94ec6913e480dc3f752418348b5ae099eb75868b5f4775f0572999c61" sha256: ea90e81dc4a25a043d9bee692d20ed6d1c4a1662a28c03a96417446c093ed6b4
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "8.9.4" version: "8.9.5"
cached_network_image: cached_network_image:
dependency: "direct main" dependency: "direct main"
description: description:
@ -301,10 +301,10 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: croppy name: croppy
sha256: bf99b00023df0d7d047e04d27d496d87cbefd968f578d0bd30f342ff75570a12 sha256: "99f4fbb4a4b44d2712e8dcd61c57c1acac151bd53cab11de3babec80407ed266"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.3.3" version: "1.3.5"
cross_file: cross_file:
dependency: "direct main" dependency: "direct main"
description: description:
@ -429,18 +429,18 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: drift name: drift
sha256: "97d5832657d49f26e7a8e07de397ddc63790b039372878d5117af816d0fdb5cb" sha256: "14a61af39d4584faf1d73b5b35e4b758a43008cf4c0fdb0576ec8e7032c0d9a5"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.25.1" version: "2.26.0"
drift_dev: drift_dev:
dependency: "direct dev" dependency: "direct dev"
description: description:
name: drift_dev name: drift_dev
sha256: f1db88482dbb016b9bbddddf746d5d0a6938b156ff20e07320052981f97388cc sha256: "0d3f8b33b76cf1c6a82ee34d9511c40957549c4674b8f1688609e6d6c7306588"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.25.2" version: "2.26.0"
drift_flutter: drift_flutter:
dependency: "direct main" dependency: "direct main"
description: description:
@ -710,6 +710,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.4.1" version: "3.4.1"
flutter_card_swiper:
dependency: "direct main"
description:
name: flutter_card_swiper
sha256: "1eacbfab31b572223042e03409726553aec431abe48af48c8d591d376d070d3d"
url: "https://pub.dev"
source: hosted
version: "7.0.2"
flutter_colorpicker: flutter_colorpicker:
dependency: "direct main" dependency: "direct main"
description: description:
@ -831,10 +839,10 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: flutter_map name: flutter_map
sha256: bbf145e8220531f2f727608c431871c7457f3b134e513543913afd00fdc1cd47 sha256: f7d0379477274f323c3f3bc12d369a2b42eb86d1e7bd2970ae1ea3cff782449a
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "8.1.0" version: "8.1.1"
flutter_markdown: flutter_markdown:
dependency: "direct main" dependency: "direct main"
description: description:
@ -1177,10 +1185,10 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: image_picker_android name: image_picker_android
sha256: "82652a75e3dd667a91187769a6a2cc81bd8c111bbead698d8e938d2b63e5e89a" sha256: "8bd392ba8b0c8957a157ae0dc9fcf48c58e6c20908d5880aea1d79734df090e9"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.8.12+21" version: "0.8.12+22"
image_picker_for_web: image_picker_for_web:
dependency: transitive dependency: transitive
description: description:
@ -1409,10 +1417,10 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: material_symbols_icons name: material_symbols_icons
sha256: "1403944c2a68dbdf934fca2b2115a372e70a4a185c136ab71a9d16e2108d0b14" sha256: ca30ccbd97763353bde6bb1076aa4f4d17a40db0804384da77df142102aa225d
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "4.2805.1" version: "4.2808.0"
media_kit: media_kit:
dependency: "direct main" dependency: "direct main"
description: description:
@ -1601,10 +1609,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: path_provider_android name: path_provider_android
sha256: "4adf4fd5423ec60a29506c76581bc05854c55e3a0b72d35bb28d661c9686edf2" sha256: "0ca7359dad67fd7063cb2892ab0c0737b2daafd807cf1acecd62374c8fae6c12"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.2.15" version: "2.2.16"
path_provider_foundation: path_provider_foundation:
dependency: transitive dependency: transitive
description: description:
@ -1785,10 +1793,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: pub_semver name: pub_semver
sha256: "7b3cfbf654f3edd0c6298ecd5be782ce997ddf0e00531b9464b55245185bbbbd" sha256: "5bfcf68ca79ef689f8990d1160781b4bad40a3bd5e5218ad4076ddb7f4081585"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.1.5" version: "2.2.0"
pubspec_parse: pubspec_parse:
dependency: transitive dependency: transitive
description: description:
@ -1953,10 +1961,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: shared_preferences_android name: shared_preferences_android
sha256: a768fc8ede5f0c8e6150476e14f38e2417c0864ca36bb4582be8e21925a03c22 sha256: "3ec7210872c4ba945e3244982918e502fa2bfb5230dff6832459ca0e1879b7ad"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.4.6" version: "2.4.8"
shared_preferences_foundation: shared_preferences_foundation:
dependency: transitive dependency: transitive
description: description:
@ -2118,10 +2126,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: sqlite3 name: sqlite3
sha256: "32b632dda27d664f85520093ed6f735ae5c49b5b75345afb8b19411bc59bb53d" sha256: "310af39c40dd0bb2058538333c9d9840a2725ae0b9f77e4fd09ad6696aa8f66e"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.7.4" version: "2.7.5"
sqlite3_flutter_libs: sqlite3_flutter_libs:
dependency: transitive dependency: transitive
description: description:
@ -2326,10 +2334,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: url_launcher_android name: url_launcher_android
sha256: "6fc2f56536ee873eeb867ad176ae15f304ccccc357848b351f6f0d8d4a40d193" sha256: "1d0eae19bd7606ef60fe69ef3b312a437a16549476c42321d5dc1506c9ca3bf4"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "6.3.14" version: "6.3.15"
url_launcher_ios: url_launcher_ios:
dependency: transitive dependency: transitive
description: description:

View File

@ -138,6 +138,7 @@ dependencies:
flutter_map: ^8.1.0 flutter_map: ^8.1.0
geolocator: ^13.0.2 geolocator: ^13.0.2
fast_rsa: ^3.8.0 fast_rsa: ^3.8.0
flutter_card_swiper: ^7.0.2
dev_dependencies: dev_dependencies:
flutter_test: flutter_test: