Browse Source

聊天复制粘贴弹框;

动态详情ui优化
dart3_last
wurong 2 years ago
parent
commit
0e113b117e
  1. BIN
      assets/image/2x/icon_copy_j.webp
  2. BIN
      assets/image/3x/多边形 1@3x.webp
  3. BIN
      assets/image/icon_copy_j.webp
  4. 200
      lib/message/im/chat_details_page.dart
  5. 53
      lib/message/im/im_view/emoji_text.dart
  6. 466
      lib/message/im/im_view/magic_pop.dart
  7. 55
      lib/message/im/im_view/text_item_container.dart
  8. 33
      lib/message/im/im_view/text_span_builder.dart
  9. 6
      lib/web/web_view/comment_list.dart
  10. 28
      pubspec.lock
  11. 4
      pubspec.yaml

BIN
assets/image/2x/icon_copy_j.webp

Binary file not shown.

After

Width:  |  Height:  |  Size: 786 B

BIN
assets/image/3x/多边形 1@3x.webp

Binary file not shown.

After

Width:  |  Height:  |  Size: 946 B

BIN
assets/image/icon_copy_j.webp

Binary file not shown.

After

Width:  |  Height:  |  Size: 640 B

200
lib/message/im/chat_details_page.dart

@ -16,7 +16,6 @@ import '../../generated/l10n.dart';
import '../../utils/font_weight.dart';
import 'im_view/on_chat_message.dart';
import 'im_view/on_chat_msg_instance.dart';
import 'im_view/text_item_container.dart';
class ChatDetailsPage extends StatefulWidget {
@override
@ -47,6 +46,7 @@ class _ChatDetailsPage extends State<ChatDetailsPage>
OnChatMsgInstance.instance.onChatMessage;
final ScrollController scrollController = ScrollController();
String tex = "";
int copyIndex = 0;
@override
void onMessage(txt) {
@ -132,6 +132,7 @@ class _ChatDetailsPage extends State<ChatDetailsPage>
}*/
}
///
_onTextFieldTap() {
if (emojiShowing) {
needHideSmiley = true;
@ -141,6 +142,7 @@ class _ChatDetailsPage extends State<ChatDetailsPage>
}
}
///
_onSmileyTap() {
if (!emojiShowing && commentFocus.hasFocus && isKeyBoardShow) {
needShowSmiley = true;
@ -154,6 +156,7 @@ class _ChatDetailsPage extends State<ChatDetailsPage>
}
}
///
_onMoreTap() {
if (!moreShow && commentFocus.hasFocus && isKeyBoardShow) {
needShowMore = true;
@ -224,6 +227,7 @@ class _ChatDetailsPage extends State<ChatDetailsPage>
onTap: () {
setState(() {
Navigator.of(context).pushNamed('/router/chat_setting');
copyIndex = 0;
});
},
child: Container(
@ -280,10 +284,11 @@ class _ChatDetailsPage extends State<ChatDetailsPage>
physics: NeverScrollableScrollPhysics(),
itemBuilder: (context, position) {
return GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: () {
},
onLongPress: (){
showCustomDialog(context,position);
setState((){
copyIndex = 0;
});
},
child: chatDetailsItem(),
);
@ -320,6 +325,83 @@ class _ChatDetailsPage extends State<ChatDetailsPage>
SizedBox(
height: 16.h,
),
if(copyIndex == 1)
Stack(
alignment: Alignment.bottomCenter,
children: [
Container(
padding: EdgeInsets.only(bottom:13.h),
child: Container(
width: 180.w,
decoration: BoxDecoration(
color: Color(0xFF2A2A2A),
borderRadius: BorderRadius.circular(6),
),
padding: EdgeInsets.symmetric(horizontal: 32.w,vertical: 7.5.h),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
GestureDetector(
onTap: (){
setState((){
copyIndex = 0;
this.copy(tex);
});
},
child:Column(
children: [
Image.asset(
"assets/image/icon_chat_copy.webp",
height:16,
width: 16,
),
SizedBox(height: 2.h,),
Text(
"复制",
textAlign: TextAlign.center,
style: TextStyle(
color: Colors.white,
fontSize: 12.sp,
fontWeight: MyFontWeight.regular,
),
),
],
) ,
),
GestureDetector(
onTap: (){},
child: Column(
children: [
Image.asset(
"assets/image/icon_chat_delete.webp",
height:16,
width: 16,
),
SizedBox(height: 2.h,),
Text(
S.of(context).shanchu,
textAlign: TextAlign.center,
style: TextStyle(
color: Colors.white,
fontSize: 12.sp,
fontWeight: MyFontWeight.regular,
),
),
],
),
)
],
),
),
),
Image.asset(
"assets/image/icon_copy_j.webp",
height: 17,
width: 17,
),
],
),
Padding(padding:EdgeInsets.only(left: 17.w,right: 39.w),
child: Row(
children: [
@ -348,9 +430,14 @@ class _ChatDetailsPage extends State<ChatDetailsPage>
),
padding: EdgeInsets.symmetric(vertical: 8.h, horizontal: 12.w),
child:GestureDetector(
onTap:this.copy(tex),
child: SelectableText(
tex = "凭本事买的为啥要给你钱啊伙计",
onLongPress:(){
setState((){
copyIndex =1;
});
},
child:
Text(
tex = "上次你在我这里买的水果钱是不是忘记付了?一共18块钱做点生意也是真的不容易啊。",
textAlign: TextAlign.left,
style: TextStyle(
height: 1.2.h,
@ -364,6 +451,83 @@ class _ChatDetailsPage extends State<ChatDetailsPage>
],
),),
SizedBox(height: 40.h,),
if(copyIndex == 1)
Stack(
alignment: Alignment.bottomCenter,
children: [
Container(
padding: EdgeInsets.only(bottom:13.h),
child: Container(
width: 180.w,
decoration: BoxDecoration(
color: Color(0xFF2A2A2A),
borderRadius: BorderRadius.circular(6),
),
padding: EdgeInsets.symmetric(horizontal: 32.w,vertical: 7.5.h),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
GestureDetector(
onTap: (){
setState((){
copyIndex = 0;
this.copy(tex);
});
},
child:Column(
children: [
Image.asset(
"assets/image/icon_chat_copy.webp",
height:16,
width: 16,
),
SizedBox(height: 2.h,),
Text(
"复制",
textAlign: TextAlign.center,
style: TextStyle(
color: Colors.white,
fontSize: 12.sp,
fontWeight: MyFontWeight.regular,
),
),
],
) ,
),
GestureDetector(
onTap: (){},
child: Column(
children: [
Image.asset(
"assets/image/icon_chat_delete.webp",
height:16,
width: 16,
),
SizedBox(height: 2.h,),
Text(
S.of(context).shanchu,
textAlign: TextAlign.center,
style: TextStyle(
color: Colors.white,
fontSize: 12.sp,
fontWeight: MyFontWeight.regular,
),
),
],
),
)
],
),
),
),
Image.asset(
"assets/image/icon_copy_j.webp",
height: 17,
width: 17,
),
],
),
Padding(padding:EdgeInsets.only(left:36.w,right: 16.w),
child: Row(
children: [
@ -395,10 +559,24 @@ class _ChatDetailsPage extends State<ChatDetailsPage>
color: Color(0xFF32A060),
),
padding: EdgeInsets.symmetric(vertical: 8.h, horizontal: 12.w),
child: TextItemContainer(
text: "上次你在我这里买的水果钱是不是忘记付了?一共18块钱做点生意也是真的不容易啊。",
action: '',
isMyself: true,),
child: GestureDetector(
onLongPress:(){
setState((){
copyIndex = 1;
});
},
child:
Text(
tex = "凭本事买的为啥要给你钱啊伙计",
textAlign: TextAlign.left,
style: TextStyle(
height: 1.2.h,
color: Colors.black,
fontSize: 17.sp,
fontWeight: MyFontWeight.regular,
),
),
),
)),
SizedBox(
width: 12.w,

53
lib/message/im/im_view/emoji_text.dart

@ -1,53 +0,0 @@
import 'package:extended_text/extended_text.dart';
import 'package:flutter/material.dart';
///emoji/image text
class EmojiText extends SpecialText {
static const String flag = "[";
final int start;
EmojiText(TextStyle textStyle, {this.start})
: super(EmojiText.flag, "]", textStyle);
@override
InlineSpan finishText() {
var key = toString();
if (EmojiUitl.instance.emojiMap.containsKey(key)) {
//fontsize id define image height
//size = 30.0/26.0 * fontSize
final double size = 20.0;
///fontSize 26 and text height =30.0
//final double fontSize = 26.0;
return ImageSpan(AssetImage(EmojiUitl.instance.emojiMap[key]),
actualText: key,
imageWidth: size,
imageHeight: size,
start: start,
fit: BoxFit.fill,
margin: EdgeInsets.only(left: 2.0, right: 2.0));
}
return TextSpan(text: toString(), style: textStyle);
}
}
class EmojiUitl {
final Map<String, String> _emojiMap = new Map<String, String>();
Map<String, String> get emojiMap => _emojiMap;
final String _emojiFilePath = "assets/images/emoji";
static EmojiUitl _instance;
static EmojiUitl get instance {
if (_instance == null) _instance = new EmojiUitl._();
return _instance;
}
EmojiUitl._() {
for (int i = 1; i < 100; i++) {
_emojiMap["[$i]"] = "$_emojiFilePath/sg$i.png";
}
}
}

466
lib/message/im/im_view/magic_pop.dart

@ -1,466 +0,0 @@
import 'package:flutter/material.dart';
import 'package:huixiang/message/im/im_view/triangle_painter.dart';
const double _kMenuScreenPadding = 8.0;
class MagicPop extends StatefulWidget {
MagicPop({
@required this.onValueChanged,
@required this.actions,
@required this.child,
this.pressType = PressType.longPress,
this.pageMaxChildCount = 5,
this.backgroundColor = Colors.black,
this.menuWidth = 250,
this.menuHeight = 42,
}) : assert(onValueChanged != null),
assert(actions != null && actions.length > 0),
assert(child != null);
final ValueChanged<int> onValueChanged;
final List<String> actions;
final Widget child;
final PressType pressType; //
final int pageMaxChildCount;
final Color backgroundColor;
final double menuWidth;
final double menuHeight;
@override
_WPopupMenuState createState() => _WPopupMenuState();
}
class _WPopupMenuState extends State<MagicPop> {
@override
Widget build(BuildContext context) {
return GestureDetector(
child: widget.child,
onTap: () {
if (widget.pressType == PressType.singleClick) {
onTap();
}
},
onLongPress: () {
if (widget.pressType == PressType.longPress) {
onTap();
}
},
);
}
void onTap() {
Navigator.push(
context,
_PopupMenuRoute(context, widget.actions, widget.pageMaxChildCount,
widget.backgroundColor, widget.menuWidth, widget.menuHeight))
.then((index) {
widget.onValueChanged(index);
});
}
}
enum PressType {
//
longPress,
//
singleClick,
}
class _PopupMenuRoute extends PopupRoute {
final BuildContext btnContext;
double _height;
double _width;
final List<String> actions;
final int _pageMaxChildCount;
final Color backgroundColor;
final double menuWidth;
final double menuHeight;
_PopupMenuRoute(this.btnContext, this.actions, this._pageMaxChildCount,
this.backgroundColor, this.menuWidth, this.menuHeight) {
_height = btnContext.size.height;
_width = btnContext.size.width;
}
@override
Animation<double> createAnimation() {
return CurvedAnimation(
parent: super.createAnimation(),
curve: Curves.linear,
reverseCurve: const Interval(0.0, 2.0 / 3.0),
);
}
@override
Color get barrierColor => null;
@override
bool get barrierDismissible => true;
@override
String get barrierLabel => null;
@override
Widget buildPage(BuildContext context, Animation<double> animation,
Animation<double> secondaryAnimation) {
return _MenuPopWidget(this.btnContext, _height, _width, actions,
_pageMaxChildCount, backgroundColor, menuWidth, menuHeight);
}
@override
Duration get transitionDuration => Duration(milliseconds: 300);
}
class _MenuPopWidget extends StatefulWidget {
final BuildContext btnContext;
final double _height;
final double _width;
final List<String> actions;
final int _pageMaxChildCount;
final Color backgroundColor;
final double menuWidth;
final double menuHeight;
_MenuPopWidget(
this.btnContext,
this._height,
this._width,
this.actions,
this._pageMaxChildCount,
this.backgroundColor,
this.menuWidth,
this.menuHeight);
@override
__MenuPopWidgetState createState() => __MenuPopWidgetState();
}
class __MenuPopWidgetState extends State<_MenuPopWidget> {
int _curPage = 0;
final double _arrowWidth = 40;
final double _separatorWidth = 1;
final double _triangleHeight = 10;
RenderBox button;
RenderBox overlay;
RelativeRect position;
@override
void initState() {
super.initState();
button = widget.btnContext.findRenderObject();
overlay = Overlay.of(widget.btnContext).context.findRenderObject();
position = RelativeRect.fromRect(
Rect.fromPoints(
button.localToGlobal(Offset.zero, ancestor: overlay),
button.localToGlobal(Offset.zero, ancestor: overlay),
),
Offset.zero & overlay.size,
);
}
@override
Widget build(BuildContext context) {
// child
int _curPageChildCount =
(_curPage + 1) * widget._pageMaxChildCount > widget.actions.length
? widget.actions.length % widget._pageMaxChildCount
: widget._pageMaxChildCount;
double _curArrowWidth = 0;
int _curArrowCount = 0; //
if (widget.actions.length > widget._pageMaxChildCount) {
// widget._pageMaxChildCount
if (_curPage == 0) {
//
_curArrowWidth = _arrowWidth;
_curArrowCount = 1;
} else {
//
_curArrowWidth = _arrowWidth * 2;
_curArrowCount = 2;
}
}
double _curPageWidth = widget.menuWidth +
(_curPageChildCount - 1 + _curArrowCount) * _separatorWidth +
_curArrowWidth;
// ignore: unused_element
Widget view() {
var isInverted = (position.top +
(MediaQuery.of(context).size.height -
position.top -
position.bottom) /
2.0 -
(widget.menuHeight + _triangleHeight)) <
(widget.menuHeight + _triangleHeight) * 2;
var pain = CustomPaint(
size: Size(_curPageWidth, _triangleHeight),
painter: TrianglePainter(
color: widget.backgroundColor,
position: position,
isInverted: true,
size: button.size),
);
var row = Row(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
//
_curPage == 0
? Container(
height: widget.menuHeight,
)
: InkWell(
onTap: () {
setState(() {
_curPage--;
});
},
child: Container(
width: _arrowWidth,
height: widget.menuHeight,
child: Image.asset(
'images/left_white.png',
fit: BoxFit.none,
),
),
),
//
_curPage == 0
? Container(
height: widget.menuHeight,
)
: Container(
width: 1,
height: widget.menuHeight,
color: Colors.grey,
),
// ListView
_buildList(_curPageChildCount, _curPageWidth, _curArrowWidth,
_curArrowCount),
//
_curArrowCount > 0
? Container(
width: 1,
color: Colors.grey,
height: widget.menuHeight,
)
: Container(
height: widget.menuHeight,
),
_curArrowCount > 0
? InkWell(
onTap: () {
if ((_curPage + 1) * widget._pageMaxChildCount <
widget.actions.length)
setState(() {
_curPage++;
});
},
child: Container(
width: _arrowWidth,
height: widget.menuHeight,
child: Image.asset(
(_curPage + 1) * widget._pageMaxChildCount >=
widget.actions.length
? 'images/right_gray.png'
: 'images/right_white.png',
fit: BoxFit.none,
),
),
)
: Container(
height: widget.menuHeight,
),
],
);
return Material(
color: Colors.transparent,
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
isInverted ? pain : Container(),
Expanded(
child: Stack(
children: <Widget>[
ClipRRect(
borderRadius: BorderRadius.all(Radius.circular(5)),
child: Container(
color: widget.backgroundColor,
height: widget.menuHeight,
),
),
row,
],
),
),
isInverted
? Container()
: CustomPaint(
size: Size(_curPageWidth, _triangleHeight),
painter: TrianglePainter(
color: widget.backgroundColor,
position: position,
size: button.size),
),
],
),
);
}
return MediaQuery.removePadding(
context: context,
removeTop: true,
removeBottom: true,
removeLeft: true,
removeRight: true,
child: Builder(
builder: (BuildContext context) {
return CustomSingleChildLayout(
//
delegate: _PopupMenuRouteLayout(
position,
widget.menuHeight + _triangleHeight,
Directionality.of(widget.btnContext),
widget._width,
widget.menuWidth),
child: SizedBox(
height: widget.menuHeight + _triangleHeight,
width: _curPageWidth,
child: view()),
);
},
),
);
}
Widget _buildList(int _curPageChildCount, double _curPageWidth,
double _curArrowWidth, int _curArrowCount) {
return ListView.separated(
shrinkWrap: true,
physics: NeverScrollableScrollPhysics(),
scrollDirection: Axis.horizontal,
itemCount: _curPageChildCount,
itemBuilder: (BuildContext context, int index) {
return InkWell(
onTap: () {
Navigator.pop(
context, _curPage * widget._pageMaxChildCount + index);
},
child: SizedBox(
width: (_curPageWidth -
_curArrowWidth -
(_curPageChildCount - 1 + _curArrowCount) *
_separatorWidth) /
_curPageChildCount,
height: widget.menuHeight,
child: Center(
child: Text(
widget.actions[_curPage * widget._pageMaxChildCount + index],
style: TextStyle(color: Colors.white, fontSize: 16),
),
),
),
);
},
separatorBuilder: (BuildContext context, int index) {
return Container(
width: 1,
height: widget.menuHeight,
color: Colors.grey,
);
},
);
}
}
// Positioning of the menu on the screen.
class _PopupMenuRouteLayout extends SingleChildLayoutDelegate {
_PopupMenuRouteLayout(this.position, this.selectedItemOffset,
this.textDirection, this.width, this.menuWidth);
// Rectangle of underlying button, relative to the overlay's dimensions.
final RelativeRect position;
// The distance from the top of the menu to the middle of selected item.
//
// This will be null if there's no item to position in this way.
final double selectedItemOffset;
// Whether to prefer going to the left or to the right.
final TextDirection textDirection;
final double width;
final double menuWidth;
// We put the child wherever position specifies, so long as it will fit within
// the specified parent size padded (inset) by 8. If necessary, we adjust the
// child's position so that it fits.
@override
BoxConstraints getConstraintsForChild(BoxConstraints constraints) {
// The menu can be at most the size of the overlay minus 8.0 pixels in each
// direction.
return BoxConstraints.loose(constraints.biggest -
const Offset(_kMenuScreenPadding * 2.0, _kMenuScreenPadding * 2.0));
}
@override
Offset getPositionForChild(Size size, Size childSize) {
// size: The size of the overlay.
// childSize: The size of the menu, when fully open, as determined by
// getConstraintsForChild.
// Find the ideal vertical position.
double y;
if (selectedItemOffset == null) {
y = position.top;
} else {
y = position.top +
(size.height - position.top - position.bottom) / 2.0 -
selectedItemOffset;
}
// Find the ideal horizontal position.
double x;
if (position.left > position.right) {
// Menu button is closer to the right edge, so grow to the left, aligned to the right edge.
// x = childSize.width - (size.width - position.right);
x = position.left + width - childSize.width;
} else if (position.left < position.right) {
// Menu button is closer to the left edge, so grow to the right, aligned to the left edge.
if (width > childSize.width) {
x = position.left + (childSize.width - menuWidth) / 2;
} else
x = position.left;
} else {
x = position.right - width / 2 - childSize.width / 2;
}
// Avoid going outside an area defined as the rectangle 8.0 pixels from the
// edge of the screen in every direction.
if (x < _kMenuScreenPadding)
x = _kMenuScreenPadding;
else if (x + childSize.width > size.width - _kMenuScreenPadding)
x = size.width - childSize.width - _kMenuScreenPadding;
if (y < _kMenuScreenPadding)
y = _kMenuScreenPadding;
else if (y + childSize.height > size.height - _kMenuScreenPadding)
y = size.height - childSize.height;
else if (y < childSize.height * 2) {
y = position.top + childSize.height;
}
return Offset(x, y);
}
@override
bool shouldRelayout(_PopupMenuRouteLayout oldDelegate) {
return position != oldDelegate.position;
}
}

55
lib/message/im/im_view/text_item_container.dart

@ -1,55 +0,0 @@
import 'package:extended_text/extended_text.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';import 'package:huixiang/message/im/im_view/text_span_builder.dart';
import 'magic_pop.dart';
class TextItemContainer extends StatefulWidget {
final String text;
final String action;
final bool isMyself;
TextItemContainer({this.text, this.action, this.isMyself = true});
@override
_TextItemContainerState createState() => _TextItemContainerState();
}
class _TextItemContainerState extends State<TextItemContainer> {
TextSpanBuilder _spanBuilder = TextSpanBuilder();
@override
Widget build(BuildContext context) {
return MagicPop(
onValueChanged: (int value) {
switch (value) {
case 0:
Clipboard.setData(new ClipboardData(text: widget.text));
break;
case 3:
break;
}
},
pressType: PressType.longPress,
actions: ['复制', '转发', '收藏', '撤回', '删除'],
child: new Container(
// width: widget.text.length > 24 ? (MediaQuery.of(context).size.width - 66) - 100 : null,
// padding: EdgeInsets.all(5.0),
// decoration: BoxDecoration(
// color: widget.isMyself ? Color(0xff98E165) : Colors.white,
// borderRadius: BorderRadius.all(Radius.circular(5.0)),
// ),
// margin: EdgeInsets.only(right: 7.0),
child: ExtendedText(
widget.text ?? '文字为空',
maxLines: 99,
overflow: TextOverflow.ellipsis,
specialTextSpanBuilder: _spanBuilder,
style: TextStyle(fontSize: 17,color: Colors.white),
),
),
);
}
}

33
lib/message/im/im_view/text_span_builder.dart

@ -1,33 +0,0 @@
import 'package:extended_text_library/extended_text_library.dart';
import 'package:flutter/material.dart';
import 'emoji_text.dart';
class TextSpanBuilder extends SpecialTextSpanBuilder {
final bool showAtBackground;
TextSpanBuilder({
this.showAtBackground: false,
});
@override
TextSpan build(String data, {TextStyle textStyle, onTap}) {
TextSpan result = super.build(data, textStyle: textStyle, onTap: onTap);
return result;
}
@override
SpecialText createSpecialText(String flag,
{TextStyle textStyle, SpecialTextGestureTapCallback onTap, int index}) {
if (flag == null || flag == "") return null;
if (isStart(flag, EmojiText.flag)) {
return EmojiText(textStyle, start: index - (EmojiText.flag.length - 1));
}
return null;
}
}
class SpecialTextStyle {
TextRange textRange;
}

6
lib/web/web_view/comment_list.dart

@ -73,9 +73,7 @@ class CommentListState extends State<CommentList> {
@override
Widget build(BuildContext context) {
return Column(
children: [
Container(
return Container(
decoration: BoxDecoration(
color: Colors.white,
),
@ -170,8 +168,6 @@ class CommentListState extends State<CommentList> {
// ),
],
),
),
],
);
}

28
pubspec.lock

@ -92,13 +92,6 @@ packages:
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.16.0"
crypto:
dependency: transitive
description:
name: crypto
url: "https://pub.flutter-io.cn"
source: hosted
version: "3.0.2"
csslib:
dependency: transitive
description:
@ -134,20 +127,6 @@ packages:
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.0.0"
extended_text:
dependency: "direct main"
description:
name: extended_text
url: "https://pub.flutter-io.cn"
source: hosted
version: "9.1.2"
extended_text_library:
dependency: transitive
description:
name: extended_text_library
url: "https://pub.flutter-io.cn"
source: hosted
version: "9.1.1"
fake_async:
dependency: transitive
description:
@ -434,13 +413,6 @@ packages:
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.7.0"
mqtt_client:
dependency: "direct main"
description:
name: mqtt_client
url: "https://pub.flutter-io.cn"
source: hosted
version: "9.6.8"
nested:
dependency: transitive
description:

4
pubspec.yaml

@ -103,10 +103,6 @@ dependencies:
emoji_picker_flutter: ^1.4.1
mqtt_client: ^9.6.8
extended_text: ^9.0.0
dev_dependencies:
flutter_test:
sdk: flutter

Loading…
Cancel
Save