✨ Chat message attachments
This commit is contained in:
26
lib/widgets/chat/chat_new.dart
Normal file
26
lib/widgets/chat/chat_new.dart
Normal file
@ -0,0 +1,26 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
|
||||
class ChatNewAction extends StatelessWidget {
|
||||
const ChatNewAction({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SizedBox(
|
||||
height: 320,
|
||||
width: double.infinity,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Container(
|
||||
padding: const EdgeInsets.only(left: 20, top: 20, bottom: 8),
|
||||
child: Text(
|
||||
AppLocalizations.of(context)!.chatNew,
|
||||
style: Theme.of(context).textTheme.headlineSmall,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -1,6 +1,4 @@
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:solian/models/message.dart';
|
||||
import 'package:solian/widgets/chat/content.dart';
|
||||
import 'package:solian/widgets/posts/content/attachment.dart';
|
||||
@ -25,14 +23,11 @@ class ChatMessage extends StatelessWidget {
|
||||
}
|
||||
|
||||
Widget renderReply() {
|
||||
final padding =
|
||||
underMerged ? const EdgeInsets.only(left: 14, right: 8, top: 4) : const EdgeInsets.only(left: 8, right: 8);
|
||||
|
||||
if (item.replyTo != null) {
|
||||
return Row(
|
||||
children: [
|
||||
Container(
|
||||
padding: padding,
|
||||
padding: const EdgeInsets.only(left: 8, right: 8),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
|
@ -5,8 +5,11 @@ import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
import 'package:http/http.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:solian/models/message.dart';
|
||||
import 'package:solian/models/post.dart';
|
||||
import 'package:solian/providers/auth.dart';
|
||||
import 'package:solian/utils/service_url.dart';
|
||||
import 'package:solian/widgets/posts/attachment_editor.dart';
|
||||
import 'package:badges/badges.dart' as badge;
|
||||
|
||||
class ChatMessageEditor extends StatefulWidget {
|
||||
final String channel;
|
||||
@ -25,6 +28,19 @@ class _ChatMessageEditorState extends State<ChatMessageEditor> {
|
||||
|
||||
bool _isSubmitting = false;
|
||||
|
||||
List<Attachment> _attachments = List.empty(growable: true);
|
||||
|
||||
void viewAttachments(BuildContext context) {
|
||||
showModalBottomSheet(
|
||||
context: context,
|
||||
builder: (context) => AttachmentEditor(
|
||||
provider: 'messaging',
|
||||
current: _attachments,
|
||||
onUpdate: (value) => _attachments = value,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> sendMessage(BuildContext context) async {
|
||||
if (_isSubmitting) return;
|
||||
|
||||
@ -39,6 +55,7 @@ class _ChatMessageEditorState extends State<ChatMessageEditor> {
|
||||
req.headers['Content-Type'] = 'application/json';
|
||||
req.body = jsonEncode(<String, dynamic>{
|
||||
'content': _textController.value.text,
|
||||
'attachments': _attachments,
|
||||
'reply_to': widget.replying?.id,
|
||||
});
|
||||
|
||||
@ -57,6 +74,7 @@ class _ChatMessageEditorState extends State<ChatMessageEditor> {
|
||||
|
||||
void reset() {
|
||||
_textController.clear();
|
||||
_attachments.clear();
|
||||
|
||||
if (widget.onReset != null) widget.onReset!();
|
||||
}
|
||||
@ -65,6 +83,7 @@ class _ChatMessageEditorState extends State<ChatMessageEditor> {
|
||||
if (widget.editing != null) {
|
||||
setState(() {
|
||||
_textController.text = widget.editing!.content;
|
||||
_attachments = widget.editing!.attachments ?? List.empty(growable: true);
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -116,7 +135,7 @@ class _ChatMessageEditorState extends State<ChatMessageEditor> {
|
||||
widget.replying != null ? replyingBanner : Container(),
|
||||
Container(
|
||||
height: 56,
|
||||
padding: const EdgeInsets.only(top: 4, left: 16, right: 8),
|
||||
padding: const EdgeInsets.only(top: 4, bottom: 4, right: 8),
|
||||
decoration: const BoxDecoration(
|
||||
border: Border(
|
||||
top: BorderSide(width: 0.3, color: Color(0xffdedede)),
|
||||
@ -125,6 +144,16 @@ class _ChatMessageEditorState extends State<ChatMessageEditor> {
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
children: [
|
||||
badge.Badge(
|
||||
showBadge: _attachments.isNotEmpty,
|
||||
badgeContent: Text(_attachments.length.toString(), style: const TextStyle(color: Colors.white)),
|
||||
position: badge.BadgePosition.custom(top: -2, end: 8),
|
||||
child: TextButton(
|
||||
style: TextButton.styleFrom(shape: const CircleBorder(), padding: const EdgeInsets.all(4)),
|
||||
onPressed: !_isSubmitting ? () => viewAttachments(context) : null,
|
||||
child: const Icon(Icons.attach_file),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: TextField(
|
||||
controller: _textController,
|
||||
@ -136,6 +165,7 @@ class _ChatMessageEditorState extends State<ChatMessageEditor> {
|
||||
hintText: AppLocalizations.of(context)!.chatMessagePlaceholder,
|
||||
),
|
||||
onSubmitted: (_) => sendMessage(context),
|
||||
onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(),
|
||||
),
|
||||
),
|
||||
TextButton(
|
||||
|
@ -13,10 +13,16 @@ import 'package:solian/utils/service_url.dart';
|
||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
|
||||
class AttachmentEditor extends StatefulWidget {
|
||||
final String provider;
|
||||
final List<Attachment> current;
|
||||
final void Function(List<Attachment> data) onUpdate;
|
||||
|
||||
const AttachmentEditor({super.key, required this.current, required this.onUpdate});
|
||||
const AttachmentEditor({
|
||||
super.key,
|
||||
required this.provider,
|
||||
required this.current,
|
||||
required this.onUpdate,
|
||||
});
|
||||
|
||||
@override
|
||||
State<AttachmentEditor> createState() => _AttachmentEditorState();
|
||||
@ -97,10 +103,8 @@ class _AttachmentEditorState extends State<AttachmentEditor> {
|
||||
|
||||
Future<void> uploadAttachment(File file, String hashcode) async {
|
||||
final auth = context.read<AuthProvider>();
|
||||
final req = MultipartRequest(
|
||||
'POST',
|
||||
getRequestUri('interactive', '/api/attachments'),
|
||||
);
|
||||
|
||||
final req = MultipartRequest('POST', getRequestUri(widget.provider, '/api/attachments'));
|
||||
req.files.add(await MultipartFile.fromPath('attachment', file.path));
|
||||
req.fields['hashcode'] = hashcode;
|
||||
|
||||
@ -119,10 +123,7 @@ class _AttachmentEditorState extends State<AttachmentEditor> {
|
||||
Future<void> disposeAttachment(BuildContext context, Attachment item, int index) async {
|
||||
final auth = context.read<AuthProvider>();
|
||||
|
||||
final req = MultipartRequest(
|
||||
'DELETE',
|
||||
getRequestUri('interactive', '/api/attachments/${item.id}'),
|
||||
);
|
||||
final req = MultipartRequest('DELETE', getRequestUri(widget.provider, '/api/attachments/${item.id}'));
|
||||
|
||||
setState(() => _isSubmitting = true);
|
||||
var res = await auth.client!.send(req);
|
||||
@ -293,14 +294,16 @@ class AttachmentEditorMethodPopup extends StatelessWidget {
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||
child: GridView.count(
|
||||
primary: false,
|
||||
crossAxisSpacing: 10,
|
||||
mainAxisSpacing: 10,
|
||||
crossAxisCount: 4,
|
||||
children: [
|
||||
InkWell(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
onTap: () => pickImage(),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(8),
|
||||
child: Center(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
@ -314,8 +317,7 @@ class AttachmentEditorMethodPopup extends StatelessWidget {
|
||||
InkWell(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
onTap: () => takeImage(),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(8),
|
||||
child: Center(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
@ -329,12 +331,11 @@ class AttachmentEditorMethodPopup extends StatelessWidget {
|
||||
InkWell(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
onTap: () => pickVideo(),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(8),
|
||||
child: Center(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const Icon(Icons.camera, color: Colors.indigo),
|
||||
const Icon(Icons.camera, color: Colors.teal),
|
||||
const SizedBox(height: 8),
|
||||
Text(AppLocalizations.of(context)!.pickVideo),
|
||||
],
|
||||
@ -344,12 +345,11 @@ class AttachmentEditorMethodPopup extends StatelessWidget {
|
||||
InkWell(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
onTap: () => takeVideo(),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(8),
|
||||
child: Center(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const Icon(Icons.video_call, color: Colors.indigo),
|
||||
const Icon(Icons.video_call, color: Colors.teal),
|
||||
const SizedBox(height: 8),
|
||||
Text(AppLocalizations.of(context)!.takeVideo),
|
||||
],
|
||||
|
Reference in New Issue
Block a user