✨ Chat message attachments
This commit is contained in:
parent
82fc191bad
commit
ac280f2d40
@ -37,6 +37,7 @@
|
|||||||
"postEditNotify": "You are about editing a post that already published.",
|
"postEditNotify": "You are about editing a post that already published.",
|
||||||
"reactionAdded": "Your reaction has been added.",
|
"reactionAdded": "Your reaction has been added.",
|
||||||
"reactionRemoved": "Your reaction has been removed.",
|
"reactionRemoved": "Your reaction has been removed.",
|
||||||
|
"chatNew": "New Chat",
|
||||||
"chatMessagePlaceholder": "Write a message...",
|
"chatMessagePlaceholder": "Write a message...",
|
||||||
"chatMessageEditNotify": "You are about editing a message.",
|
"chatMessageEditNotify": "You are about editing a message.",
|
||||||
"chatMessageReplyNotify": "You are about replying a message.",
|
"chatMessageReplyNotify": "You are about replying a message.",
|
||||||
|
@ -37,6 +37,7 @@
|
|||||||
"postEditNotify": "你正在修改一个已经发布了的帖子。",
|
"postEditNotify": "你正在修改一个已经发布了的帖子。",
|
||||||
"reactionAdded": "你的反应已被添加。",
|
"reactionAdded": "你的反应已被添加。",
|
||||||
"reactionRemoved": "你的反应已被移除。",
|
"reactionRemoved": "你的反应已被移除。",
|
||||||
|
"chatNew": "新聊天",
|
||||||
"chatMessagePlaceholder": "发条消息……",
|
"chatMessagePlaceholder": "发条消息……",
|
||||||
"chatMessageEditNotify": "你正在编辑信息中……",
|
"chatMessageEditNotify": "你正在编辑信息中……",
|
||||||
"chatMessageReplyNotify": "你正在回复消息中……",
|
"chatMessageReplyNotify": "你正在回复消息中……",
|
||||||
|
@ -74,6 +74,7 @@ class _ChatScreenState extends State<ChatScreen> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool getMessageMergeable(Message? a, Message? b) {
|
bool getMessageMergeable(Message? a, Message? b) {
|
||||||
|
if (a?.replyTo != null || b?.replyTo != null) return false;
|
||||||
if (a == null || b == null) return false;
|
if (a == null || b == null) return false;
|
||||||
if (a.senderId != b.senderId) return false;
|
if (a.senderId != b.senderId) return false;
|
||||||
return a.createdAt.difference(b.createdAt).inMinutes <= 5;
|
return a.createdAt.difference(b.createdAt).inMinutes <= 5;
|
||||||
|
@ -6,6 +6,7 @@ import 'package:solian/models/channel.dart';
|
|||||||
import 'package:solian/providers/auth.dart';
|
import 'package:solian/providers/auth.dart';
|
||||||
import 'package:solian/router.dart';
|
import 'package:solian/router.dart';
|
||||||
import 'package:solian/utils/service_url.dart';
|
import 'package:solian/utils/service_url.dart';
|
||||||
|
import 'package:solian/widgets/chat/chat_new.dart';
|
||||||
import 'package:solian/widgets/indent_wrapper.dart';
|
import 'package:solian/widgets/indent_wrapper.dart';
|
||||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||||
import 'package:solian/widgets/signin_required.dart';
|
import 'package:solian/widgets/signin_required.dart';
|
||||||
@ -40,6 +41,13 @@ class _ChatIndexScreenState extends State<ChatIndexScreen> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void viewNewChatAction() {
|
||||||
|
showModalBottomSheet(
|
||||||
|
context: context,
|
||||||
|
builder: (context) => const ChatNewAction(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
Future.delayed(Duration.zero, () {
|
Future.delayed(Duration.zero, () {
|
||||||
@ -55,6 +63,20 @@ class _ChatIndexScreenState extends State<ChatIndexScreen> {
|
|||||||
|
|
||||||
return IndentWrapper(
|
return IndentWrapper(
|
||||||
title: AppLocalizations.of(context)!.chat,
|
title: AppLocalizations.of(context)!.chat,
|
||||||
|
floatingActionButton: FutureBuilder(
|
||||||
|
future: auth.isAuthorized(),
|
||||||
|
builder: (context, snapshot) {
|
||||||
|
if (snapshot.hasData && snapshot.data!) {
|
||||||
|
return FloatingActionButton.extended(
|
||||||
|
icon: const Icon(Icons.add),
|
||||||
|
label: Text(AppLocalizations.of(context)!.chatNew),
|
||||||
|
onPressed: () => viewNewChatAction(),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return Container();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
child: FutureBuilder(
|
child: FutureBuilder(
|
||||||
future: auth.isAuthorized(),
|
future: auth.isAuthorized(),
|
||||||
builder: (context, snapshot) {
|
builder: (context, snapshot) {
|
||||||
|
@ -40,6 +40,7 @@ class _CommentEditorScreenState extends State<CommentEditorScreen> {
|
|||||||
showModalBottomSheet(
|
showModalBottomSheet(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (context) => AttachmentEditor(
|
builder: (context) => AttachmentEditor(
|
||||||
|
provider: 'interactive',
|
||||||
current: _attachments,
|
current: _attachments,
|
||||||
onUpdate: (value) => _attachments = value,
|
onUpdate: (value) => _attachments = value,
|
||||||
),
|
),
|
||||||
@ -151,8 +152,7 @@ class _CommentEditorScreenState extends State<CommentEditorScreen> {
|
|||||||
const Divider(thickness: 0.3),
|
const Divider(thickness: 0.3),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Container(
|
child: Container(
|
||||||
padding:
|
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
||||||
const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
|
||||||
child: TextField(
|
child: TextField(
|
||||||
maxLines: null,
|
maxLines: null,
|
||||||
autofocus: true,
|
autofocus: true,
|
||||||
@ -160,9 +160,9 @@ class _CommentEditorScreenState extends State<CommentEditorScreen> {
|
|||||||
keyboardType: TextInputType.multiline,
|
keyboardType: TextInputType.multiline,
|
||||||
controller: _textController,
|
controller: _textController,
|
||||||
decoration: InputDecoration.collapsed(
|
decoration: InputDecoration.collapsed(
|
||||||
hintText:
|
hintText: AppLocalizations.of(context)!.postContentPlaceholder,
|
||||||
AppLocalizations.of(context)!.postContentPlaceholder,
|
|
||||||
),
|
),
|
||||||
|
onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -32,6 +32,7 @@ class _MomentEditorScreenState extends State<MomentEditorScreen> {
|
|||||||
showModalBottomSheet(
|
showModalBottomSheet(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (context) => AttachmentEditor(
|
builder: (context) => AttachmentEditor(
|
||||||
|
provider: 'interactive',
|
||||||
current: _attachments,
|
current: _attachments,
|
||||||
onUpdate: (value) => _attachments = value,
|
onUpdate: (value) => _attachments = value,
|
||||||
),
|
),
|
||||||
@ -151,6 +152,7 @@ class _MomentEditorScreenState extends State<MomentEditorScreen> {
|
|||||||
decoration: InputDecoration.collapsed(
|
decoration: InputDecoration.collapsed(
|
||||||
hintText: AppLocalizations.of(context)!.postContentPlaceholder,
|
hintText: AppLocalizations.of(context)!.postContentPlaceholder,
|
||||||
),
|
),
|
||||||
|
onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
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/material.dart';
|
||||||
import 'package:flutter/widgets.dart';
|
|
||||||
import 'package:solian/models/message.dart';
|
import 'package:solian/models/message.dart';
|
||||||
import 'package:solian/widgets/chat/content.dart';
|
import 'package:solian/widgets/chat/content.dart';
|
||||||
import 'package:solian/widgets/posts/content/attachment.dart';
|
import 'package:solian/widgets/posts/content/attachment.dart';
|
||||||
@ -25,14 +23,11 @@ class ChatMessage extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Widget renderReply() {
|
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) {
|
if (item.replyTo != null) {
|
||||||
return Row(
|
return Row(
|
||||||
children: [
|
children: [
|
||||||
Container(
|
Container(
|
||||||
padding: padding,
|
padding: const EdgeInsets.only(left: 8, right: 8),
|
||||||
child: Row(
|
child: Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
|
@ -5,8 +5,11 @@ import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
|||||||
import 'package:http/http.dart';
|
import 'package:http/http.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'package:solian/models/message.dart';
|
import 'package:solian/models/message.dart';
|
||||||
|
import 'package:solian/models/post.dart';
|
||||||
import 'package:solian/providers/auth.dart';
|
import 'package:solian/providers/auth.dart';
|
||||||
import 'package:solian/utils/service_url.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 {
|
class ChatMessageEditor extends StatefulWidget {
|
||||||
final String channel;
|
final String channel;
|
||||||
@ -25,6 +28,19 @@ class _ChatMessageEditorState extends State<ChatMessageEditor> {
|
|||||||
|
|
||||||
bool _isSubmitting = false;
|
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 {
|
Future<void> sendMessage(BuildContext context) async {
|
||||||
if (_isSubmitting) return;
|
if (_isSubmitting) return;
|
||||||
|
|
||||||
@ -39,6 +55,7 @@ class _ChatMessageEditorState extends State<ChatMessageEditor> {
|
|||||||
req.headers['Content-Type'] = 'application/json';
|
req.headers['Content-Type'] = 'application/json';
|
||||||
req.body = jsonEncode(<String, dynamic>{
|
req.body = jsonEncode(<String, dynamic>{
|
||||||
'content': _textController.value.text,
|
'content': _textController.value.text,
|
||||||
|
'attachments': _attachments,
|
||||||
'reply_to': widget.replying?.id,
|
'reply_to': widget.replying?.id,
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -57,6 +74,7 @@ class _ChatMessageEditorState extends State<ChatMessageEditor> {
|
|||||||
|
|
||||||
void reset() {
|
void reset() {
|
||||||
_textController.clear();
|
_textController.clear();
|
||||||
|
_attachments.clear();
|
||||||
|
|
||||||
if (widget.onReset != null) widget.onReset!();
|
if (widget.onReset != null) widget.onReset!();
|
||||||
}
|
}
|
||||||
@ -65,6 +83,7 @@ class _ChatMessageEditorState extends State<ChatMessageEditor> {
|
|||||||
if (widget.editing != null) {
|
if (widget.editing != null) {
|
||||||
setState(() {
|
setState(() {
|
||||||
_textController.text = widget.editing!.content;
|
_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(),
|
widget.replying != null ? replyingBanner : Container(),
|
||||||
Container(
|
Container(
|
||||||
height: 56,
|
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(
|
decoration: const BoxDecoration(
|
||||||
border: Border(
|
border: Border(
|
||||||
top: BorderSide(width: 0.3, color: Color(0xffdedede)),
|
top: BorderSide(width: 0.3, color: Color(0xffdedede)),
|
||||||
@ -125,6 +144,16 @@ class _ChatMessageEditorState extends State<ChatMessageEditor> {
|
|||||||
child: Row(
|
child: Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.start,
|
mainAxisAlignment: MainAxisAlignment.start,
|
||||||
children: [
|
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(
|
Expanded(
|
||||||
child: TextField(
|
child: TextField(
|
||||||
controller: _textController,
|
controller: _textController,
|
||||||
@ -136,6 +165,7 @@ class _ChatMessageEditorState extends State<ChatMessageEditor> {
|
|||||||
hintText: AppLocalizations.of(context)!.chatMessagePlaceholder,
|
hintText: AppLocalizations.of(context)!.chatMessagePlaceholder,
|
||||||
),
|
),
|
||||||
onSubmitted: (_) => sendMessage(context),
|
onSubmitted: (_) => sendMessage(context),
|
||||||
|
onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
TextButton(
|
TextButton(
|
||||||
|
@ -13,10 +13,16 @@ import 'package:solian/utils/service_url.dart';
|
|||||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||||
|
|
||||||
class AttachmentEditor extends StatefulWidget {
|
class AttachmentEditor extends StatefulWidget {
|
||||||
|
final String provider;
|
||||||
final List<Attachment> current;
|
final List<Attachment> current;
|
||||||
final void Function(List<Attachment> data) onUpdate;
|
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
|
@override
|
||||||
State<AttachmentEditor> createState() => _AttachmentEditorState();
|
State<AttachmentEditor> createState() => _AttachmentEditorState();
|
||||||
@ -97,10 +103,8 @@ class _AttachmentEditorState extends State<AttachmentEditor> {
|
|||||||
|
|
||||||
Future<void> uploadAttachment(File file, String hashcode) async {
|
Future<void> uploadAttachment(File file, String hashcode) async {
|
||||||
final auth = context.read<AuthProvider>();
|
final auth = context.read<AuthProvider>();
|
||||||
final req = MultipartRequest(
|
|
||||||
'POST',
|
final req = MultipartRequest('POST', getRequestUri(widget.provider, '/api/attachments'));
|
||||||
getRequestUri('interactive', '/api/attachments'),
|
|
||||||
);
|
|
||||||
req.files.add(await MultipartFile.fromPath('attachment', file.path));
|
req.files.add(await MultipartFile.fromPath('attachment', file.path));
|
||||||
req.fields['hashcode'] = hashcode;
|
req.fields['hashcode'] = hashcode;
|
||||||
|
|
||||||
@ -119,10 +123,7 @@ class _AttachmentEditorState extends State<AttachmentEditor> {
|
|||||||
Future<void> disposeAttachment(BuildContext context, Attachment item, int index) async {
|
Future<void> disposeAttachment(BuildContext context, Attachment item, int index) async {
|
||||||
final auth = context.read<AuthProvider>();
|
final auth = context.read<AuthProvider>();
|
||||||
|
|
||||||
final req = MultipartRequest(
|
final req = MultipartRequest('DELETE', getRequestUri(widget.provider, '/api/attachments/${item.id}'));
|
||||||
'DELETE',
|
|
||||||
getRequestUri('interactive', '/api/attachments/${item.id}'),
|
|
||||||
);
|
|
||||||
|
|
||||||
setState(() => _isSubmitting = true);
|
setState(() => _isSubmitting = true);
|
||||||
var res = await auth.client!.send(req);
|
var res = await auth.client!.send(req);
|
||||||
@ -293,14 +294,16 @@ class AttachmentEditorMethodPopup extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Row(
|
child: GridView.count(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
primary: false,
|
||||||
|
crossAxisSpacing: 10,
|
||||||
|
mainAxisSpacing: 10,
|
||||||
|
crossAxisCount: 4,
|
||||||
children: [
|
children: [
|
||||||
InkWell(
|
InkWell(
|
||||||
borderRadius: BorderRadius.circular(8),
|
borderRadius: BorderRadius.circular(8),
|
||||||
onTap: () => pickImage(),
|
onTap: () => pickImage(),
|
||||||
child: Padding(
|
child: Center(
|
||||||
padding: const EdgeInsets.all(8),
|
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
@ -314,8 +317,7 @@ class AttachmentEditorMethodPopup extends StatelessWidget {
|
|||||||
InkWell(
|
InkWell(
|
||||||
borderRadius: BorderRadius.circular(8),
|
borderRadius: BorderRadius.circular(8),
|
||||||
onTap: () => takeImage(),
|
onTap: () => takeImage(),
|
||||||
child: Padding(
|
child: Center(
|
||||||
padding: const EdgeInsets.all(8),
|
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
@ -329,12 +331,11 @@ class AttachmentEditorMethodPopup extends StatelessWidget {
|
|||||||
InkWell(
|
InkWell(
|
||||||
borderRadius: BorderRadius.circular(8),
|
borderRadius: BorderRadius.circular(8),
|
||||||
onTap: () => pickVideo(),
|
onTap: () => pickVideo(),
|
||||||
child: Padding(
|
child: Center(
|
||||||
padding: const EdgeInsets.all(8),
|
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
const Icon(Icons.camera, color: Colors.indigo),
|
const Icon(Icons.camera, color: Colors.teal),
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
Text(AppLocalizations.of(context)!.pickVideo),
|
Text(AppLocalizations.of(context)!.pickVideo),
|
||||||
],
|
],
|
||||||
@ -344,12 +345,11 @@ class AttachmentEditorMethodPopup extends StatelessWidget {
|
|||||||
InkWell(
|
InkWell(
|
||||||
borderRadius: BorderRadius.circular(8),
|
borderRadius: BorderRadius.circular(8),
|
||||||
onTap: () => takeVideo(),
|
onTap: () => takeVideo(),
|
||||||
child: Padding(
|
child: Center(
|
||||||
padding: const EdgeInsets.all(8),
|
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
const Icon(Icons.video_call, color: Colors.indigo),
|
const Icon(Icons.video_call, color: Colors.teal),
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
Text(AppLocalizations.of(context)!.takeVideo),
|
Text(AppLocalizations.of(context)!.takeVideo),
|
||||||
],
|
],
|
||||||
|
12
pubspec.lock
12
pubspec.lock
@ -25,6 +25,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.11.0"
|
version: "2.11.0"
|
||||||
|
badges:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: badges
|
||||||
|
sha256: a7b6bbd60dce418df0db3058b53f9d083c22cdb5132a052145dc267494df0b84
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "3.1.2"
|
||||||
boolean_selector:
|
boolean_selector:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -697,10 +705,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: pointycastle
|
name: pointycastle
|
||||||
sha256: "70fe966348fe08c34bf929582f1d8247d9d9408130723206472b4687227e4333"
|
sha256: "79fbafed02cfdbe85ef3fd06c7f4bc2cbcba0177e61b765264853d4253b21744"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.8.0"
|
version: "3.9.0"
|
||||||
provider:
|
provider:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
@ -59,6 +59,7 @@ dependencies:
|
|||||||
hive_flutter: ^1.1.0
|
hive_flutter: ^1.1.0
|
||||||
flutter_launcher_icons: ^0.13.1
|
flutter_launcher_icons: ^0.13.1
|
||||||
web_socket_channel: ^2.4.5
|
web_socket_channel: ^2.4.5
|
||||||
|
badges: ^3.1.2
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
|
Loading…
Reference in New Issue
Block a user