✨ Send message with poll
This commit is contained in:
@@ -46,6 +46,18 @@ sealed class SnPoll with _$SnPoll {
|
||||
}) = _SnPoll;
|
||||
|
||||
factory SnPoll.fromJson(Map<String, dynamic> json) => _$SnPollFromJson(json);
|
||||
|
||||
factory SnPoll.fromPollWithStats(SnPollWithStats pollWithStats) => SnPoll(
|
||||
id: pollWithStats.id,
|
||||
questions: pollWithStats.questions,
|
||||
title: pollWithStats.title,
|
||||
description: pollWithStats.description,
|
||||
endedAt: pollWithStats.endedAt,
|
||||
publisherId: pollWithStats.publisherId,
|
||||
createdAt: pollWithStats.createdAt,
|
||||
updatedAt: pollWithStats.updatedAt,
|
||||
deletedAt: pollWithStats.deletedAt,
|
||||
);
|
||||
}
|
||||
|
||||
@freezed
|
||||
|
||||
@@ -8,6 +8,7 @@ import "package:island/database/drift_db.dart";
|
||||
import "package:island/database/message.dart";
|
||||
import "package:island/models/chat.dart";
|
||||
import "package:island/models/file.dart";
|
||||
import "package:island/models/poll.dart";
|
||||
import "package:island/pods/database.dart";
|
||||
import "package:island/pods/lifecycle.dart";
|
||||
import "package:island/pods/network.dart";
|
||||
@@ -437,6 +438,7 @@ class MessagesNotifier extends _$MessagesNotifier {
|
||||
WidgetRef ref,
|
||||
String content,
|
||||
List<UniversalFile> attachments, {
|
||||
SnPoll? poll,
|
||||
SnChatMessage? editingTo,
|
||||
SnChatMessage? forwardingTo,
|
||||
SnChatMessage? replyingTo,
|
||||
@@ -498,6 +500,7 @@ class MessagesNotifier extends _$MessagesNotifier {
|
||||
'attachments_id': cloudAttachments.map((e) => e.id).toList(),
|
||||
'replied_message_id': replyingTo?.id,
|
||||
'forwarded_message_id': forwardingTo?.id,
|
||||
'poll_id': poll?.id,
|
||||
'meta': {},
|
||||
'nonce': nonce,
|
||||
},
|
||||
|
||||
@@ -11,6 +11,7 @@ import "package:hooks_riverpod/hooks_riverpod.dart";
|
||||
import "package:island/database/message.dart";
|
||||
import "package:island/models/chat.dart";
|
||||
import "package:island/models/file.dart";
|
||||
import "package:island/models/poll.dart";
|
||||
import "package:island/pods/chat/chat_rooms.dart";
|
||||
import "package:island/pods/chat/chat_subscribe.dart";
|
||||
import "package:island/pods/chat/messages_notifier.dart";
|
||||
@@ -170,6 +171,7 @@ class ChatRoomScreen extends HookConsumerWidget {
|
||||
final messageReplyingTo = useState<SnChatMessage?>(null);
|
||||
final messageForwardingTo = useState<SnChatMessage?>(null);
|
||||
final messageEditingTo = useState<SnChatMessage?>(null);
|
||||
final selectedPoll = useState<SnPoll?>(null);
|
||||
final attachments = useState<List<UniversalFile>>([]);
|
||||
final attachmentProgress = useState<Map<String, Map<int, double?>>>({});
|
||||
|
||||
@@ -285,11 +287,13 @@ class ChatRoomScreen extends HookConsumerWidget {
|
||||
|
||||
void sendMessage() {
|
||||
if (messageController.text.trim().isNotEmpty ||
|
||||
attachments.value.isNotEmpty) {
|
||||
attachments.value.isNotEmpty ||
|
||||
selectedPoll.value != null) {
|
||||
messagesNotifier.sendMessage(
|
||||
ref,
|
||||
messageController.text.trim(),
|
||||
attachments.value,
|
||||
poll: selectedPoll.value,
|
||||
editingTo: messageEditingTo.value,
|
||||
forwardingTo: messageForwardingTo.value,
|
||||
replyingTo: messageReplyingTo.value,
|
||||
@@ -304,6 +308,7 @@ class ChatRoomScreen extends HookConsumerWidget {
|
||||
messageEditingTo.value = null;
|
||||
messageReplyingTo.value = null;
|
||||
messageForwardingTo.value = null;
|
||||
selectedPoll.value = null;
|
||||
attachments.value = [];
|
||||
}
|
||||
}
|
||||
@@ -1246,10 +1251,13 @@ class ChatRoomScreen extends HookConsumerWidget {
|
||||
messageEditingTo.value = null;
|
||||
messageReplyingTo.value = null;
|
||||
messageForwardingTo.value = null;
|
||||
selectedPoll.value = null;
|
||||
},
|
||||
messageEditingTo: messageEditingTo.value,
|
||||
messageReplyingTo: messageReplyingTo.value,
|
||||
messageForwardingTo: messageForwardingTo.value,
|
||||
selectedPoll: selectedPoll.value,
|
||||
onPollSelected: (poll) => selectedPoll.value = poll,
|
||||
onPickFile: (bool isPhoto) {
|
||||
if (isPhoto) {
|
||||
pickPhotoMedia();
|
||||
|
||||
@@ -11,6 +11,7 @@ import "package:island/models/account.dart";
|
||||
import "package:island/models/autocomplete_response.dart";
|
||||
import "package:island/models/chat.dart";
|
||||
import "package:island/models/file.dart";
|
||||
import "package:island/models/poll.dart";
|
||||
import "package:island/models/publisher.dart";
|
||||
import "package:island/models/realm.dart";
|
||||
import "package:island/models/sticker.dart";
|
||||
@@ -26,6 +27,7 @@ import "package:styled_widget/styled_widget.dart";
|
||||
import "package:material_symbols_icons/symbols.dart";
|
||||
import "package:island/widgets/stickers/sticker_picker.dart";
|
||||
import "package:island/pods/chat/chat_subscribe.dart";
|
||||
import "package:island/widgets/post/compose_poll.dart";
|
||||
|
||||
void _insertPlaceholder(TextEditingController controller, String placeholder) {
|
||||
final text = controller.text;
|
||||
@@ -43,8 +45,14 @@ const kInputDrawerExpandedHeight = 180.0;
|
||||
|
||||
class _ExpandedSection extends StatelessWidget {
|
||||
final TextEditingController messageController;
|
||||
final SnPoll? selectedPoll;
|
||||
final Function(SnPoll?) onPollSelected;
|
||||
|
||||
const _ExpandedSection({required this.messageController});
|
||||
const _ExpandedSection({
|
||||
required this.messageController,
|
||||
this.selectedPoll,
|
||||
required this.onPollSelected,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@@ -90,7 +98,16 @@ class _ExpandedSection extends StatelessWidget {
|
||||
borderRadius: const BorderRadius.all(
|
||||
Radius.circular(8),
|
||||
),
|
||||
onTap: () {},
|
||||
onTap: () async {
|
||||
final poll = await showModalBottomSheet<SnPoll>(
|
||||
context: context,
|
||||
isScrollControlled: true,
|
||||
builder: (context) => const ComposePollSheet(),
|
||||
);
|
||||
if (poll != null) {
|
||||
onPollSelected(poll);
|
||||
}
|
||||
},
|
||||
child: Card(
|
||||
margin: EdgeInsets.zero,
|
||||
color:
|
||||
@@ -176,6 +193,8 @@ class ChatInput extends HookConsumerWidget {
|
||||
final Function(int, int) onMoveAttachment;
|
||||
final Function(List<UniversalFile>) onAttachmentsChanged;
|
||||
final Map<String, Map<int, double?>> attachmentProgress;
|
||||
final SnPoll? selectedPoll;
|
||||
final Function(SnPoll?) onPollSelected;
|
||||
|
||||
const ChatInput({
|
||||
super.key,
|
||||
@@ -196,6 +215,8 @@ class ChatInput extends HookConsumerWidget {
|
||||
required this.onMoveAttachment,
|
||||
required this.onAttachmentsChanged,
|
||||
required this.attachmentProgress,
|
||||
this.selectedPoll,
|
||||
required this.onPollSelected,
|
||||
});
|
||||
|
||||
@override
|
||||
@@ -413,6 +434,87 @@ class ChatInput extends HookConsumerWidget {
|
||||
key: ValueKey('no-attachments'),
|
||||
),
|
||||
),
|
||||
AnimatedSwitcher(
|
||||
duration: const Duration(milliseconds: 200),
|
||||
switchInCurve: Curves.easeOutCubic,
|
||||
switchOutCurve: Curves.easeInCubic,
|
||||
transitionBuilder: (Widget child, Animation<double> animation) {
|
||||
return SlideTransition(
|
||||
position: Tween<Offset>(
|
||||
begin: const Offset(0, -0.25),
|
||||
end: Offset.zero,
|
||||
).animate(animation),
|
||||
child: FadeTransition(
|
||||
opacity: animation,
|
||||
child: SizeTransition(
|
||||
sizeFactor: animation,
|
||||
axisAlignment: -1.0,
|
||||
child: child,
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
child:
|
||||
selectedPoll != null
|
||||
? Container(
|
||||
key: const ValueKey('selected-poll'),
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 16,
|
||||
vertical: 8,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color:
|
||||
Theme.of(
|
||||
context,
|
||||
).colorScheme.surfaceContainerHigh,
|
||||
borderRadius: BorderRadius.circular(24),
|
||||
border: Border.all(
|
||||
color: Theme.of(
|
||||
context,
|
||||
).colorScheme.outline.withOpacity(0.2),
|
||||
width: 1,
|
||||
),
|
||||
),
|
||||
margin: const EdgeInsets.only(
|
||||
left: 8,
|
||||
right: 8,
|
||||
top: 8,
|
||||
bottom: 8,
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(
|
||||
Symbols.how_to_vote,
|
||||
size: 18,
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
const Gap(8),
|
||||
Expanded(
|
||||
child: Text(
|
||||
selectedPoll!.title ?? 'Poll',
|
||||
style: Theme.of(context).textTheme.bodySmall!
|
||||
.copyWith(fontWeight: FontWeight.w500),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
SizedBox(
|
||||
width: 24,
|
||||
height: 24,
|
||||
child: IconButton(
|
||||
padding: EdgeInsets.zero,
|
||||
icon: const Icon(Icons.close, size: 18),
|
||||
onPressed: () => onPollSelected(null),
|
||||
tooltip: 'clear'.tr(),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
)
|
||||
: const SizedBox.shrink(
|
||||
key: ValueKey('no-selected-poll'),
|
||||
),
|
||||
),
|
||||
AnimatedSwitcher(
|
||||
duration: const Duration(milliseconds: 200),
|
||||
switchInCurve: Curves.easeOutCubic,
|
||||
@@ -798,7 +900,11 @@ class ChatInput extends HookConsumerWidget {
|
||||
},
|
||||
child:
|
||||
isExpanded.value
|
||||
? _ExpandedSection(messageController: messageController)
|
||||
? _ExpandedSection(
|
||||
messageController: messageController,
|
||||
selectedPoll: selectedPoll,
|
||||
onPollSelected: onPollSelected,
|
||||
)
|
||||
: const SizedBox.shrink(key: ValueKey('collapsed')),
|
||||
),
|
||||
],
|
||||
|
||||
@@ -67,7 +67,9 @@ class ComposePollSheet extends HookConsumerWidget {
|
||||
title: Text(poll.title ?? 'untitled'.tr()),
|
||||
subtitle: _buildPollSubtitle(poll),
|
||||
onTap: () {
|
||||
Navigator.of(context).pop(poll);
|
||||
Navigator.of(
|
||||
context,
|
||||
).pop(SnPoll.fromPollWithStats(poll));
|
||||
},
|
||||
);
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user