💄 Optimize attachment view
This commit is contained in:
parent
33a4bd7e71
commit
73777fe74e
@ -752,5 +752,6 @@
|
|||||||
"screenAccountBadges": "Badges",
|
"screenAccountBadges": "Badges",
|
||||||
"accountBadges": "Badges",
|
"accountBadges": "Badges",
|
||||||
"accountBadgesDescription": "View and manage your badges.",
|
"accountBadgesDescription": "View and manage your badges.",
|
||||||
"badgeActivated": "Activated badge {}."
|
"badgeActivated": "Activated badge {}.",
|
||||||
|
"viewDetailedAttachment": "Details"
|
||||||
}
|
}
|
||||||
|
@ -750,5 +750,6 @@
|
|||||||
"screenAccountBadges": "徽章",
|
"screenAccountBadges": "徽章",
|
||||||
"accountBadges": "徽章",
|
"accountBadges": "徽章",
|
||||||
"accountBadgesDescription": "查看并管理你的徽章。",
|
"accountBadgesDescription": "查看并管理你的徽章。",
|
||||||
"badgeActivated": "已佩戴徽章 {}。"
|
"badgeActivated": "已佩戴徽章 {}。",
|
||||||
|
"viewDetailedAttachment": "查看附件详情"
|
||||||
}
|
}
|
||||||
|
@ -750,5 +750,6 @@
|
|||||||
"screenAccountBadges": "徽章",
|
"screenAccountBadges": "徽章",
|
||||||
"accountBadges": "徽章",
|
"accountBadges": "徽章",
|
||||||
"accountBadgesDescription": "查看並管理你的徽章。",
|
"accountBadgesDescription": "查看並管理你的徽章。",
|
||||||
"badgeActivated": "已佩戴徽章 {}。"
|
"badgeActivated": "已佩戴徽章 {}。",
|
||||||
|
"viewDetailedAttachment": "查看附件詳情"
|
||||||
}
|
}
|
||||||
|
@ -750,5 +750,6 @@
|
|||||||
"screenAccountBadges": "徽章",
|
"screenAccountBadges": "徽章",
|
||||||
"accountBadges": "徽章",
|
"accountBadges": "徽章",
|
||||||
"accountBadgesDescription": "查看並管理你的徽章。",
|
"accountBadgesDescription": "查看並管理你的徽章。",
|
||||||
"badgeActivated": "已佩戴徽章 {}。"
|
"badgeActivated": "已佩戴徽章 {}。",
|
||||||
|
"viewDetailedAttachment": "查看附件詳情"
|
||||||
}
|
}
|
||||||
|
@ -72,34 +72,36 @@ class AccountPopoverCard extends StatelessWidget {
|
|||||||
const Gap(8)
|
const Gap(8)
|
||||||
],
|
],
|
||||||
).padding(horizontal: 16),
|
).padding(horizontal: 16),
|
||||||
const Gap(16),
|
if (data.badges.isNotEmpty) const Gap(12),
|
||||||
Wrap(
|
if (data.badges.isNotEmpty)
|
||||||
children: data.badges
|
Wrap(
|
||||||
.map(
|
spacing: 4,
|
||||||
(ele) => Tooltip(
|
children: data.badges
|
||||||
richMessage: TextSpan(
|
.map(
|
||||||
children: [
|
(ele) => Tooltip(
|
||||||
TextSpan(text: kBadgesMeta[ele.type]?.$1.tr() ?? 'unknown'.tr()),
|
richMessage: TextSpan(
|
||||||
if (ele.metadata['title'] != null)
|
children: [
|
||||||
TextSpan(
|
TextSpan(text: kBadgesMeta[ele.type]?.$1.tr() ?? 'unknown'.tr()),
|
||||||
text: '\n${ele.metadata['title']}',
|
if (ele.metadata['title'] != null)
|
||||||
style: const TextStyle(fontWeight: FontWeight.bold),
|
TextSpan(
|
||||||
|
text: '\n${ele.metadata['title']}',
|
||||||
|
style: const TextStyle(fontWeight: FontWeight.bold),
|
||||||
|
),
|
||||||
|
TextSpan(text: '\n'),
|
||||||
|
TextSpan(
|
||||||
|
text: DateFormat.yMEd().format(ele.createdAt),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
child: Icon(
|
||||||
|
kBadgesMeta[ele.type]?.$2 ?? Symbols.question_mark,
|
||||||
|
color: kBadgesMeta[ele.type]?.$3,
|
||||||
|
fill: 1,
|
||||||
),
|
),
|
||||||
TextSpan(text: '\n'),
|
|
||||||
TextSpan(
|
|
||||||
text: DateFormat.yMEd().format(ele.createdAt),
|
|
||||||
),
|
),
|
||||||
],
|
)
|
||||||
),
|
.toList(),
|
||||||
child: Icon(
|
).padding(horizontal: 24),
|
||||||
kBadgesMeta[ele.type]?.$2 ?? Symbols.question_mark,
|
|
||||||
color: kBadgesMeta[ele.type]?.$3,
|
|
||||||
fill: 1,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.toList(),
|
|
||||||
).padding(horizontal: 24),
|
|
||||||
const Gap(8),
|
const Gap(8),
|
||||||
Row(
|
Row(
|
||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
@ -127,33 +129,33 @@ class AccountPopoverCard extends StatelessWidget {
|
|||||||
final SnAccountStatusInfo? status =
|
final SnAccountStatusInfo? status =
|
||||||
snapshot.hasData ? SnAccountStatusInfo.fromJson(snapshot.data!.data) : null;
|
snapshot.hasData ? SnAccountStatusInfo.fromJson(snapshot.data!.data) : null;
|
||||||
return Row(
|
return Row(
|
||||||
children: [
|
children: [
|
||||||
Icon(
|
Icon(
|
||||||
Symbols.circle,
|
Symbols.circle,
|
||||||
fill: 1,
|
fill: 1,
|
||||||
size: 16,
|
size: 16,
|
||||||
color: (status?.isOnline ?? false) ? Colors.green : Colors.grey,
|
color: (status?.isOnline ?? false) ? Colors.green : Colors.grey,
|
||||||
).padding(all: 4),
|
).padding(all: 4),
|
||||||
const Gap(8),
|
const Gap(8),
|
||||||
|
Text(
|
||||||
|
status != null
|
||||||
|
? status.isOnline
|
||||||
|
? 'accountStatusOnline'.tr()
|
||||||
|
: 'accountStatusOffline'.tr()
|
||||||
|
: 'loading'.tr(),
|
||||||
|
),
|
||||||
|
if (status != null && !status.isOnline && status.lastSeenAt != null)
|
||||||
Text(
|
Text(
|
||||||
status != null
|
'accountStatusLastSeen'.tr(args: [
|
||||||
? status.isOnline
|
status.lastSeenAt != null
|
||||||
? 'accountStatusOnline'.tr()
|
? RelativeTime(context).format(
|
||||||
: 'accountStatusOffline'.tr()
|
status.lastSeenAt!.toLocal(),
|
||||||
: 'loading'.tr(),
|
)
|
||||||
),
|
: 'unknown',
|
||||||
if (status != null && !status.isOnline && status.lastSeenAt != null)
|
]),
|
||||||
Text(
|
).padding(left: 6).opacity(0.75),
|
||||||
'accountStatusLastSeen'.tr(args: [
|
],
|
||||||
status.lastSeenAt != null
|
).padding(horizontal: 24);
|
||||||
? RelativeTime(context).format(
|
|
||||||
status.lastSeenAt!.toLocal(),
|
|
||||||
)
|
|
||||||
: 'unknown',
|
|
||||||
]),
|
|
||||||
).padding(left: 6).opacity(0.75),
|
|
||||||
],
|
|
||||||
).padding(horizontal: 24);
|
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
// Bottom padding
|
// Bottom padding
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
import 'dart:math' show max;
|
||||||
|
|
||||||
import 'package:dio/dio.dart';
|
import 'package:dio/dio.dart';
|
||||||
import 'package:dismissible_page/dismissible_page.dart';
|
import 'package:dismissible_page/dismissible_page.dart';
|
||||||
@ -43,6 +44,9 @@ class AttachmentZoomView extends StatefulWidget {
|
|||||||
class _AttachmentZoomViewState extends State<AttachmentZoomView> {
|
class _AttachmentZoomViewState extends State<AttachmentZoomView> {
|
||||||
late final PageController _pageController = PageController(initialPage: widget.initialIndex ?? 0);
|
late final PageController _pageController = PageController(initialPage: widget.initialIndex ?? 0);
|
||||||
|
|
||||||
|
bool _showOverlay = true;
|
||||||
|
bool _dismissable = true;
|
||||||
|
|
||||||
void _updatePage() {
|
void _updatePage() {
|
||||||
setState(() {
|
setState(() {
|
||||||
if (_isCompletedDownload) {
|
if (_isCompletedDownload) {
|
||||||
@ -146,7 +150,7 @@ class _AttachmentZoomViewState extends State<AttachmentZoomView> {
|
|||||||
onDismissed: () {
|
onDismissed: () {
|
||||||
Navigator.of(context).pop();
|
Navigator.of(context).pop();
|
||||||
},
|
},
|
||||||
direction: DismissiblePageDismissDirection.none,
|
direction: _dismissable ? DismissiblePageDismissDirection.multi : DismissiblePageDismissDirection.none,
|
||||||
backgroundColor: Colors.transparent,
|
backgroundColor: Colors.transparent,
|
||||||
isFullScreen: true,
|
isFullScreen: true,
|
||||||
child: GestureDetector(
|
child: GestureDetector(
|
||||||
@ -163,6 +167,9 @@ class _AttachmentZoomViewState extends State<AttachmentZoomView> {
|
|||||||
child: PhotoView(
|
child: PhotoView(
|
||||||
key: Key('attachment-detail-${widget.data.first.rid}-$heroTag'),
|
key: Key('attachment-detail-${widget.data.first.rid}-$heroTag'),
|
||||||
backgroundDecoration: BoxDecoration(color: Colors.transparent),
|
backgroundDecoration: BoxDecoration(color: Colors.transparent),
|
||||||
|
scaleStateChangedCallback: (scaleState) {
|
||||||
|
setState(() => _dismissable = scaleState == PhotoViewScaleState.initial);
|
||||||
|
},
|
||||||
imageProvider: UniversalImage.provider(
|
imageProvider: UniversalImage.provider(
|
||||||
sn.getAttachmentUrl(widget.data.first.rid),
|
sn.getAttachmentUrl(widget.data.first.rid),
|
||||||
),
|
),
|
||||||
@ -172,7 +179,10 @@ class _AttachmentZoomViewState extends State<AttachmentZoomView> {
|
|||||||
|
|
||||||
return PhotoViewGallery.builder(
|
return PhotoViewGallery.builder(
|
||||||
pageController: _pageController,
|
pageController: _pageController,
|
||||||
scrollPhysics: const BouncingScrollPhysics(),
|
enableRotation: true,
|
||||||
|
scaleStateChangedCallback: (scaleState) {
|
||||||
|
setState(() => _dismissable = scaleState == PhotoViewScaleState.initial);
|
||||||
|
},
|
||||||
builder: (context, idx) {
|
builder: (context, idx) {
|
||||||
final heroTag = widget.heroTags?.elementAt(idx) ?? uuid.v4();
|
final heroTag = widget.heroTags?.elementAt(idx) ?? uuid.v4();
|
||||||
return PhotoViewGalleryPageOptions(
|
return PhotoViewGalleryPageOptions(
|
||||||
@ -197,6 +207,27 @@ class _AttachmentZoomViewState extends State<AttachmentZoomView> {
|
|||||||
backgroundDecoration: BoxDecoration(color: Colors.transparent),
|
backgroundDecoration: BoxDecoration(color: Colors.transparent),
|
||||||
);
|
);
|
||||||
}),
|
}),
|
||||||
|
Positioned(
|
||||||
|
top: max(MediaQuery.of(context).padding.top, 8),
|
||||||
|
left: 14,
|
||||||
|
child: IgnorePointer(
|
||||||
|
ignoring: !_showOverlay,
|
||||||
|
child: IconButton(
|
||||||
|
constraints: const BoxConstraints(),
|
||||||
|
icon: const Icon(Icons.close),
|
||||||
|
style: ButtonStyle(
|
||||||
|
backgroundColor: MaterialStateProperty.all(
|
||||||
|
Theme.of(context).colorScheme.surface.withOpacity(0.5),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.opacity(_showOverlay ? 1 : 0, animate: true)
|
||||||
|
.animate(const Duration(milliseconds: 300), Curves.easeInOut),
|
||||||
|
),
|
||||||
|
),
|
||||||
Align(
|
Align(
|
||||||
alignment: Alignment.bottomCenter,
|
alignment: Alignment.bottomCenter,
|
||||||
child: IgnorePointer(
|
child: IgnorePointer(
|
||||||
@ -214,7 +245,9 @@ class _AttachmentZoomViewState extends State<AttachmentZoomView> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
)
|
||||||
|
.opacity(_showOverlay ? 1 : 0, animate: true)
|
||||||
|
.animate(const Duration(milliseconds: 300), Curves.easeInOut),
|
||||||
Positioned(
|
Positioned(
|
||||||
left: 16,
|
left: 16,
|
||||||
right: 16,
|
right: 16,
|
||||||
@ -318,16 +351,6 @@ class _AttachmentZoomViewState extends State<AttachmentZoomView> {
|
|||||||
]),
|
]),
|
||||||
style: metaTextStyle,
|
style: metaTextStyle,
|
||||||
).padding(right: 2),
|
).padding(right: 2),
|
||||||
if (item.metadata['exif']?['ISO'] != null)
|
|
||||||
Text(
|
|
||||||
'ISO${item.metadata['exif']?['ISO']}',
|
|
||||||
style: metaTextStyle,
|
|
||||||
).padding(right: 2),
|
|
||||||
if (item.metadata['exif']?['Aperture'] != null)
|
|
||||||
Text(
|
|
||||||
'f/${item.metadata['exif']?['Aperture']}',
|
|
||||||
style: metaTextStyle,
|
|
||||||
).padding(right: 2),
|
|
||||||
if (item.metadata['exif']?['Megapixels'] != null &&
|
if (item.metadata['exif']?['Megapixels'] != null &&
|
||||||
item.metadata['exif']?['Model'] != null)
|
item.metadata['exif']?['Model'] != null)
|
||||||
Text(
|
Text(
|
||||||
@ -344,29 +367,44 @@ class _AttachmentZoomViewState extends State<AttachmentZoomView> {
|
|||||||
'${item.metadata['width']}x${item.metadata['height']}',
|
'${item.metadata['width']}x${item.metadata['height']}',
|
||||||
style: metaTextStyle,
|
style: metaTextStyle,
|
||||||
),
|
),
|
||||||
if (item.metadata['ratio'] != null)
|
|
||||||
Text(
|
|
||||||
(item.metadata['ratio'] as num).toStringAsFixed(2),
|
|
||||||
style: metaTextStyle,
|
|
||||||
),
|
|
||||||
Text(
|
|
||||||
item.mimetype,
|
|
||||||
style: metaTextStyle,
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
const Gap(4),
|
||||||
|
InkWell(
|
||||||
|
onTap: () {
|
||||||
|
_showDetail = true;
|
||||||
|
showModalBottomSheet(
|
||||||
|
context: context,
|
||||||
|
builder: (context) => _AttachmentZoomDetailPopup(
|
||||||
|
data: widget.data
|
||||||
|
.elementAt(widget.data.length > 1 ? _pageController.page?.round() ?? 0 : 0),
|
||||||
|
),
|
||||||
|
).then((_) {
|
||||||
|
_showDetail = false;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
child: Text(
|
||||||
|
'viewDetailedAttachment'.tr(),
|
||||||
|
style: metaTextStyle.copyWith(decoration: TextDecoration.underline),
|
||||||
|
),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}),
|
}),
|
||||||
),
|
)
|
||||||
|
.opacity(_showOverlay ? 1 : 0, animate: true)
|
||||||
|
.animate(const Duration(milliseconds: 300), Curves.easeInOut),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
onTap: () {
|
||||||
|
setState(() => _showOverlay = !_showOverlay);
|
||||||
|
},
|
||||||
onVerticalDragUpdate: (details) {
|
onVerticalDragUpdate: (details) {
|
||||||
if (_showDetail) return;
|
if (_showDetail) return;
|
||||||
if (details.delta.dy <= -40) {
|
if (details.delta.dy <= -20) {
|
||||||
_showDetail = true;
|
_showDetail = true;
|
||||||
showModalBottomSheet(
|
showModalBottomSheet(
|
||||||
context: context,
|
context: context,
|
||||||
@ -378,9 +416,6 @@ class _AttachmentZoomViewState extends State<AttachmentZoomView> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onTap: () {
|
|
||||||
Navigator.of(context).pop();
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -480,14 +515,14 @@ class _AttachmentZoomDetailPopup extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
tableGap,
|
tableGap,
|
||||||
...(data.metadata['exif']?.keys.map((k) => TableRow(
|
...(data.metadata['exif']?.keys.map((k) => TableRow(
|
||||||
children: [
|
children: [
|
||||||
TableCell(child: Text(k).padding(right: 16)),
|
TableCell(child: Text(k).padding(right: 16)),
|
||||||
TableCell(child: Text(data.metadata['exif'][k].toString())),
|
TableCell(child: Text(data.metadata['exif'][k].toString())),
|
||||||
],
|
],
|
||||||
)) ??
|
)) ??
|
||||||
[]),
|
[]),
|
||||||
],
|
],
|
||||||
).padding(horizontal: 20, vertical: 8),
|
).padding(horizontal: 20, vertical: 8, bottom: MediaQuery.of(context).padding.bottom),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
@ -28,8 +28,7 @@ class ChatMessageInput extends StatefulWidget {
|
|||||||
final ChatMessageController controller;
|
final ChatMessageController controller;
|
||||||
final SnChannelMember? otherMember;
|
final SnChannelMember? otherMember;
|
||||||
|
|
||||||
const ChatMessageInput(
|
const ChatMessageInput({super.key, required this.controller, this.otherMember});
|
||||||
{super.key, required this.controller, this.otherMember});
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<ChatMessageInput> createState() => ChatMessageInputState();
|
State<ChatMessageInput> createState() => ChatMessageInputState();
|
||||||
@ -46,20 +45,12 @@ class ChatMessageInputState extends State<ChatMessageInput> {
|
|||||||
|
|
||||||
final HotKey _pasteHotKey = HotKey(
|
final HotKey _pasteHotKey = HotKey(
|
||||||
key: PhysicalKeyboardKey.keyV,
|
key: PhysicalKeyboardKey.keyV,
|
||||||
modifiers: [
|
modifiers: [(!kIsWeb && Platform.isMacOS) ? HotKeyModifier.meta : HotKeyModifier.control],
|
||||||
(!kIsWeb && Platform.isMacOS)
|
|
||||||
? HotKeyModifier.meta
|
|
||||||
: HotKeyModifier.control
|
|
||||||
],
|
|
||||||
scope: HotKeyScope.inapp,
|
scope: HotKeyScope.inapp,
|
||||||
);
|
);
|
||||||
final HotKey _newLineHotKey = HotKey(
|
final HotKey _newLineHotKey = HotKey(
|
||||||
key: PhysicalKeyboardKey.enter,
|
key: PhysicalKeyboardKey.enter,
|
||||||
modifiers: [
|
modifiers: [(!kIsWeb && Platform.isMacOS) ? HotKeyModifier.meta : HotKeyModifier.control],
|
||||||
(!kIsWeb && Platform.isMacOS)
|
|
||||||
? HotKeyModifier.meta
|
|
||||||
: HotKeyModifier.control
|
|
||||||
],
|
|
||||||
scope: HotKeyScope.inapp,
|
scope: HotKeyScope.inapp,
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -109,8 +100,7 @@ class ChatMessageInputState extends State<ChatMessageInput> {
|
|||||||
void setEdit(SnChatMessage? value) {
|
void setEdit(SnChatMessage? value) {
|
||||||
_contentController.text = value?.body['text'] ?? '';
|
_contentController.text = value?.body['text'] ?? '';
|
||||||
_attachments.clear();
|
_attachments.clear();
|
||||||
_attachments.addAll(
|
_attachments.addAll(value?.preload?.attachments?.map((e) => PostWriteMedia(e)) ?? []);
|
||||||
value?.preload?.attachments?.map((e) => PostWriteMedia(e)) ?? []);
|
|
||||||
setState(() => _editingMessage = value);
|
setState(() => _editingMessage = value);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -149,9 +139,7 @@ class ChatMessageInputState extends State<ChatMessageInput> {
|
|||||||
media.name,
|
media.name,
|
||||||
'messaging',
|
'messaging',
|
||||||
null,
|
null,
|
||||||
mimetype: media.raw != null && media.type == SnMediaType.image
|
mimetype: media.raw != null && media.type == SnMediaType.image ? 'image/png' : null,
|
||||||
? 'image/png'
|
|
||||||
: null,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
final item = await attach.chunkedUploadParts(
|
final item = await attach.chunkedUploadParts(
|
||||||
@ -183,10 +171,7 @@ class ChatMessageInputState extends State<ChatMessageInput> {
|
|||||||
widget.controller.sendMessage(
|
widget.controller.sendMessage(
|
||||||
_editingMessage != null ? 'messages.edit' : 'messages.new',
|
_editingMessage != null ? 'messages.edit' : 'messages.new',
|
||||||
_contentController.text,
|
_contentController.text,
|
||||||
attachments: _attachments
|
attachments: _attachments.where((e) => e.attachment != null).map((e) => e.attachment!.rid).toList(),
|
||||||
.where((e) => e.attachment != null)
|
|
||||||
.map((e) => e.attachment!.rid)
|
|
||||||
.toList(),
|
|
||||||
relatedId: _editingMessage?.id,
|
relatedId: _editingMessage?.id,
|
||||||
quoteId: _replyingMessage?.id,
|
quoteId: _replyingMessage?.id,
|
||||||
editingMessage: _editingMessage,
|
editingMessage: _editingMessage,
|
||||||
@ -247,15 +232,12 @@ class ChatMessageInputState extends State<ChatMessageInput> {
|
|||||||
TweenAnimationBuilder<double>(
|
TweenAnimationBuilder<double>(
|
||||||
tween: Tween(begin: 0, end: _progress),
|
tween: Tween(begin: 0, end: _progress),
|
||||||
duration: Duration(milliseconds: 300),
|
duration: Duration(milliseconds: 300),
|
||||||
builder: (context, value, _) =>
|
builder: (context, value, _) => LinearProgressIndicator(value: value, minHeight: 2),
|
||||||
LinearProgressIndicator(value: value, minHeight: 2),
|
|
||||||
)
|
)
|
||||||
else if (_isBusy)
|
else if (_isBusy)
|
||||||
const LinearProgressIndicator(value: null, minHeight: 2),
|
const LinearProgressIndicator(value: null, minHeight: 2),
|
||||||
Padding(
|
Padding(
|
||||||
padding: _attachments.isNotEmpty
|
padding: _attachments.isNotEmpty ? const EdgeInsets.only(top: 8) : EdgeInsets.zero,
|
||||||
? const EdgeInsets.only(top: 8)
|
|
||||||
: EdgeInsets.zero,
|
|
||||||
child: PostMediaPendingList(
|
child: PostMediaPendingList(
|
||||||
attachments: _attachments,
|
attachments: _attachments,
|
||||||
isBusy: _isBusy,
|
isBusy: _isBusy,
|
||||||
@ -267,8 +249,9 @@ class ChatMessageInputState extends State<ChatMessageInput> {
|
|||||||
},
|
},
|
||||||
onUpdateBusy: (state) => setState(() => _isBusy = state),
|
onUpdateBusy: (state) => setState(() => _isBusy = state),
|
||||||
),
|
),
|
||||||
).height(_attachments.isNotEmpty ? 80 + 8 : 0, animate: true).animate(
|
)
|
||||||
const Duration(milliseconds: 300), Curves.fastEaseInToSlowEaseOut),
|
.height(_attachments.isNotEmpty ? 80 + 8 : 0, animate: true)
|
||||||
|
.animate(const Duration(milliseconds: 300), Curves.fastEaseInToSlowEaseOut),
|
||||||
SingleChildScrollView(
|
SingleChildScrollView(
|
||||||
physics: const NeverScrollableScrollPhysics(),
|
physics: const NeverScrollableScrollPhysics(),
|
||||||
child: _replyingMessage != null
|
child: _replyingMessage != null
|
||||||
@ -289,8 +272,7 @@ class ChatMessageInputState extends State<ChatMessageInput> {
|
|||||||
const Gap(8),
|
const Gap(8),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Text(
|
child: Text(
|
||||||
_replyingMessage?.body['text'] ??
|
_replyingMessage?.body['text'] ?? '${_replyingMessage?.sender.nick}',
|
||||||
'${_replyingMessage?.sender.nick}',
|
|
||||||
maxLines: 1,
|
maxLines: 1,
|
||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
),
|
),
|
||||||
@ -307,8 +289,9 @@ class ChatMessageInputState extends State<ChatMessageInput> {
|
|||||||
).padding(vertical: 8),
|
).padding(vertical: 8),
|
||||||
)
|
)
|
||||||
: const SizedBox.shrink(),
|
: const SizedBox.shrink(),
|
||||||
).height(_replyingMessage != null ? 38 : 0, animate: true).animate(
|
)
|
||||||
const Duration(milliseconds: 300), Curves.fastEaseInToSlowEaseOut),
|
.height(_replyingMessage != null ? 38 : 0, animate: true)
|
||||||
|
.animate(const Duration(milliseconds: 300), Curves.fastEaseInToSlowEaseOut),
|
||||||
SingleChildScrollView(
|
SingleChildScrollView(
|
||||||
physics: const NeverScrollableScrollPhysics(),
|
physics: const NeverScrollableScrollPhysics(),
|
||||||
child: _editingMessage != null
|
child: _editingMessage != null
|
||||||
@ -329,8 +312,7 @@ class ChatMessageInputState extends State<ChatMessageInput> {
|
|||||||
const Gap(8),
|
const Gap(8),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Text(
|
child: Text(
|
||||||
_editingMessage?.body['text'] ??
|
_editingMessage?.body['text'] ?? '${_editingMessage?.sender.nick}',
|
||||||
'${_editingMessage?.sender.nick}',
|
|
||||||
maxLines: 1,
|
maxLines: 1,
|
||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
),
|
),
|
||||||
@ -348,8 +330,9 @@ class ChatMessageInputState extends State<ChatMessageInput> {
|
|||||||
).padding(vertical: 8),
|
).padding(vertical: 8),
|
||||||
)
|
)
|
||||||
: const SizedBox.shrink(),
|
: const SizedBox.shrink(),
|
||||||
).height(_editingMessage != null ? 38 : 0, animate: true).animate(
|
)
|
||||||
const Duration(milliseconds: 300), Curves.fastEaseInToSlowEaseOut),
|
.height(_editingMessage != null ? 38 : 0, animate: true)
|
||||||
|
.animate(const Duration(milliseconds: 300), Curves.fastEaseInToSlowEaseOut),
|
||||||
Container(
|
Container(
|
||||||
padding: EdgeInsets.symmetric(horizontal: 16),
|
padding: EdgeInsets.symmetric(horizontal: 16),
|
||||||
constraints: BoxConstraints(minHeight: 56, maxHeight: 240),
|
constraints: BoxConstraints(minHeight: 56, maxHeight: 240),
|
||||||
@ -366,14 +349,11 @@ class ChatMessageInputState extends State<ChatMessageInput> {
|
|||||||
? 'fieldChatMessageDirect'.tr(args: [
|
? 'fieldChatMessageDirect'.tr(args: [
|
||||||
'@${ud.getAccountFromCache(widget.otherMember?.accountId)?.name}',
|
'@${ud.getAccountFromCache(widget.otherMember?.accountId)?.name}',
|
||||||
])
|
])
|
||||||
: 'fieldChatMessage'.tr(args: [
|
: 'fieldChatMessage'.tr(args: [widget.controller.channel?.name ?? 'loading'.tr()]),
|
||||||
widget.controller.channel?.name ?? 'loading'.tr()
|
|
||||||
]),
|
|
||||||
border: InputBorder.none,
|
border: InputBorder.none,
|
||||||
),
|
),
|
||||||
textInputAction: TextInputAction.send,
|
textInputAction: TextInputAction.send,
|
||||||
onTapOutside: (_) =>
|
onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(),
|
||||||
FocusManager.instance.primaryFocus?.unfocus(),
|
|
||||||
onSubmitted: (_) {
|
onSubmitted: (_) {
|
||||||
if (_isBusy) return;
|
if (_isBusy) return;
|
||||||
_sendMessage();
|
_sendMessage();
|
||||||
@ -388,8 +368,7 @@ class ChatMessageInputState extends State<ChatMessageInput> {
|
|||||||
Symbols.mood,
|
Symbols.mood,
|
||||||
color: Theme.of(context).colorScheme.primary,
|
color: Theme.of(context).colorScheme.primary,
|
||||||
),
|
),
|
||||||
visualDensity:
|
visualDensity: const VisualDensity(horizontal: -4, vertical: -4),
|
||||||
const VisualDensity(horizontal: -4, vertical: -4),
|
|
||||||
padding: EdgeInsets.zero,
|
padding: EdgeInsets.zero,
|
||||||
constraints: const BoxConstraints(),
|
constraints: const BoxConstraints(),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
@ -409,8 +388,7 @@ class ChatMessageInputState extends State<ChatMessageInput> {
|
|||||||
Symbols.send,
|
Symbols.send,
|
||||||
color: Theme.of(context).colorScheme.primary,
|
color: Theme.of(context).colorScheme.primary,
|
||||||
),
|
),
|
||||||
visualDensity:
|
visualDensity: const VisualDensity(horizontal: -4, vertical: -4),
|
||||||
const VisualDensity(horizontal: -4, vertical: -4),
|
|
||||||
padding: EdgeInsets.zero,
|
padding: EdgeInsets.zero,
|
||||||
constraints: const BoxConstraints(),
|
constraints: const BoxConstraints(),
|
||||||
),
|
),
|
||||||
@ -427,8 +405,7 @@ class _StickerPicker extends StatelessWidget {
|
|||||||
final Function? onDismiss;
|
final Function? onDismiss;
|
||||||
final Function(String)? onInsert;
|
final Function(String)? onInsert;
|
||||||
|
|
||||||
const _StickerPicker(
|
const _StickerPicker({this.onDismiss, required this.originalText, this.onInsert});
|
||||||
{this.onDismiss, required this.originalText, this.onInsert});
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@ -439,8 +416,9 @@ class _StickerPicker extends StatelessWidget {
|
|||||||
},
|
},
|
||||||
child: Container(
|
child: Container(
|
||||||
constraints: BoxConstraints(
|
constraints: BoxConstraints(
|
||||||
maxWidth: min(360, MediaQuery.of(context).size.width),
|
maxWidth: min(360, MediaQuery.of(context).size.width - 40),
|
||||||
maxHeight: 240),
|
maxHeight: 240,
|
||||||
|
),
|
||||||
child: Material(
|
child: Material(
|
||||||
elevation: 8,
|
elevation: 8,
|
||||||
borderRadius: const BorderRadius.all(Radius.circular(8)),
|
borderRadius: const BorderRadius.all(Radius.circular(8)),
|
||||||
@ -453,10 +431,8 @@ class _StickerPicker extends StatelessWidget {
|
|||||||
return <Widget>[
|
return <Widget>[
|
||||||
Container(
|
Container(
|
||||||
margin: EdgeInsets.only(bottom: 8),
|
margin: EdgeInsets.only(bottom: 8),
|
||||||
padding:
|
padding: EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
||||||
EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
color: Theme.of(context).colorScheme.surfaceContainerHigh,
|
||||||
color:
|
|
||||||
Theme.of(context).colorScheme.surfaceContainerHigh,
|
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
@ -468,8 +444,7 @@ class _StickerPicker extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
GridView.builder(
|
GridView.builder(
|
||||||
physics: const NeverScrollableScrollPhysics(),
|
physics: const NeverScrollableScrollPhysics(),
|
||||||
padding:
|
padding: const EdgeInsets.only(left: 8, right: 8, bottom: 8),
|
||||||
const EdgeInsets.only(left: 8, right: 8, bottom: 8),
|
|
||||||
shrinkWrap: true,
|
shrinkWrap: true,
|
||||||
gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent(
|
gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent(
|
||||||
maxCrossAxisExtent: 48,
|
maxCrossAxisExtent: 48,
|
||||||
@ -492,8 +467,7 @@ class _StickerPicker extends StatelessWidget {
|
|||||||
richMessage: TextSpan(
|
richMessage: TextSpan(
|
||||||
children: [
|
children: [
|
||||||
TextSpan(
|
TextSpan(
|
||||||
text:
|
text: ':${element.pack.prefix}${element.alias}:\n',
|
||||||
':${element.pack.prefix}${element.alias}:\n',
|
|
||||||
style: GoogleFonts.robotoMono()),
|
style: GoogleFonts.robotoMono()),
|
||||||
TextSpan(text: element.name).bold(),
|
TextSpan(text: element.name).bold(),
|
||||||
],
|
],
|
||||||
@ -502,15 +476,11 @@ class _StickerPicker extends StatelessWidget {
|
|||||||
width: 48,
|
width: 48,
|
||||||
height: 48,
|
height: 48,
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
borderRadius: const BorderRadius.all(
|
borderRadius: const BorderRadius.all(Radius.circular(8)),
|
||||||
Radius.circular(8)),
|
color: Theme.of(context).colorScheme.surfaceContainerHigh,
|
||||||
color: Theme.of(context)
|
|
||||||
.colorScheme
|
|
||||||
.surfaceContainerHigh,
|
|
||||||
),
|
),
|
||||||
child: ClipRRect(
|
child: ClipRRect(
|
||||||
borderRadius: const BorderRadius.all(
|
borderRadius: const BorderRadius.all(Radius.circular(8)),
|
||||||
Radius.circular(8)),
|
|
||||||
child: UniversalImage(
|
child: UniversalImage(
|
||||||
sn.getAttachmentUrl(element.attachment.rid),
|
sn.getAttachmentUrl(element.attachment.rid),
|
||||||
width: 48,
|
width: 48,
|
||||||
|
@ -6,17 +6,24 @@ import 'package:material_symbols_icons/symbols.dart';
|
|||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'package:styled_widget/styled_widget.dart';
|
import 'package:styled_widget/styled_widget.dart';
|
||||||
import 'package:surface/providers/sn_network.dart';
|
import 'package:surface/providers/sn_network.dart';
|
||||||
|
import 'package:surface/providers/user_directory.dart';
|
||||||
import 'package:surface/types/post.dart';
|
import 'package:surface/types/post.dart';
|
||||||
import 'package:surface/widgets/account/account_image.dart';
|
import 'package:surface/widgets/account/account_image.dart';
|
||||||
import 'package:surface/widgets/universal_image.dart';
|
import 'package:surface/widgets/universal_image.dart';
|
||||||
|
|
||||||
|
import '../../screens/account/profile_page.dart' show kBadgesMeta;
|
||||||
|
|
||||||
class PublisherPopoverCard extends StatelessWidget {
|
class PublisherPopoverCard extends StatelessWidget {
|
||||||
final SnPublisher data;
|
final SnPublisher data;
|
||||||
|
|
||||||
const PublisherPopoverCard({super.key, required this.data});
|
const PublisherPopoverCard({super.key, required this.data});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final sn = context.read<SnNetworkProvider>();
|
final sn = context.read<SnNetworkProvider>();
|
||||||
|
final ud = context.read<UserDirectoryProvider>();
|
||||||
|
|
||||||
|
final user = data.type == 0 ? ud.getAccountFromCache(data.accountId) : null;
|
||||||
|
|
||||||
return Column(
|
return Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
@ -41,6 +48,7 @@ class PublisherPopoverCard extends StatelessWidget {
|
|||||||
AccountImage(
|
AccountImage(
|
||||||
content: data.avatar,
|
content: data.avatar,
|
||||||
radius: 20,
|
radius: 20,
|
||||||
|
borderRadius: data.type == 1 ? 8 : 20,
|
||||||
),
|
),
|
||||||
Gap(16),
|
Gap(16),
|
||||||
Expanded(
|
Expanded(
|
||||||
@ -68,6 +76,36 @@ class PublisherPopoverCard extends StatelessWidget {
|
|||||||
const Gap(8)
|
const Gap(8)
|
||||||
],
|
],
|
||||||
).padding(horizontal: 16),
|
).padding(horizontal: 16),
|
||||||
|
if (user != null && user.badges.isNotEmpty) const Gap(16),
|
||||||
|
if (user != null && user.badges.isNotEmpty)
|
||||||
|
Wrap(
|
||||||
|
spacing: 4,
|
||||||
|
children: user.badges
|
||||||
|
.map(
|
||||||
|
(ele) => Tooltip(
|
||||||
|
richMessage: TextSpan(
|
||||||
|
children: [
|
||||||
|
TextSpan(text: kBadgesMeta[ele.type]?.$1.tr() ?? 'unknown'.tr()),
|
||||||
|
if (ele.metadata['title'] != null)
|
||||||
|
TextSpan(
|
||||||
|
text: '\n${ele.metadata['title']}',
|
||||||
|
style: const TextStyle(fontWeight: FontWeight.bold),
|
||||||
|
),
|
||||||
|
TextSpan(text: '\n'),
|
||||||
|
TextSpan(
|
||||||
|
text: DateFormat.yMEd().format(ele.createdAt),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
child: Icon(
|
||||||
|
kBadgesMeta[ele.type]?.$2 ?? Symbols.question_mark,
|
||||||
|
color: kBadgesMeta[ele.type]?.$3,
|
||||||
|
fill: 1,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.toList(),
|
||||||
|
).padding(horizontal: 24),
|
||||||
const Gap(16),
|
const Gap(16),
|
||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
@ -108,10 +146,7 @@ class PublisherPopoverCard extends StatelessWidget {
|
|||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
Text('publisherTotalDownvote')
|
Text('publisherTotalDownvote').tr().fontSize(13).opacity(0.75),
|
||||||
.tr()
|
|
||||||
.fontSize(13)
|
|
||||||
.opacity(0.75),
|
|
||||||
Text(data.totalDownvote.toString()),
|
Text(data.totalDownvote.toString()),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
Loading…
x
Reference in New Issue
Block a user