Browse Source

聊天文本复制粘贴删除功能处理;

wr_202303
wurong 4 months ago
parent
commit
92064b15b7
  1. 452
      lib/im/chat_details_page.dart
  2. 4
      lib/im/chat_friend_group.dart
  3. 3
      lib/im/chat_setting.dart
  4. 2
      lib/im/contact_share.dart
  5. 1
      lib/im/im_search.dart

452
lib/im/chat_details_page.dart

@ -138,7 +138,8 @@ class _ChatDetailsPage extends State<ChatDetailsPage>
queryUser() async {
sharedPre = await SharedPreferences.getInstance();
if (apiService == null)
apiService = ApiService(Dio(), context: context, token: sharedPre.getString("token"));
apiService = ApiService(Dio(),
context: context, token: sharedPre.getString("token"));
if (sharedPre.containsKey('user') &&
sharedPre.getString('user') != null &&
sharedPre.getString('user') != "") {
@ -179,8 +180,7 @@ class _ChatDetailsPage extends State<ChatDetailsPage>
void jumpToBottom() {
Future.delayed(const Duration(milliseconds: 100), () {
scrollController.position
.jumpTo(0);
scrollController.position.jumpTo(0);
});
}
@ -319,7 +319,10 @@ class _ChatDetailsPage extends State<ChatDetailsPage>
List<String> filePath = mediaPaths.map((e) => e.path).toList();
Future.forEach(filePath.toSet(), (path) async {
String fileUrl = await qiniu.uploadFile(apiService, path);
socketClient.sendMessage(_toUser.mid, fileUrl, attach: path, msgType: galleryMode == GalleryMode.image ? 2 : 4).then((value) {
socketClient
.sendMessage(_toUser.mid, fileUrl,
attach: path, msgType: galleryMode == GalleryMode.image ? 2 : 4)
.then((value) {
Message message = value;
messages.insert(0, message);
chatController.clear();
@ -329,7 +332,6 @@ class _ChatDetailsPage extends State<ChatDetailsPage>
});
});
});
}
setState(() {});
}
@ -345,7 +347,7 @@ class _ChatDetailsPage extends State<ChatDetailsPage>
S.of(context).ninxiangjiquanxianweikaiqi,
S.of(context).weilekaipaizhaoxuanzhetouxiang,
S.of(context).kaiqiquanxian,
(result) async {
(result) async {
if (result) {
await openAppSettings();
}
@ -353,7 +355,7 @@ class _ChatDetailsPage extends State<ChatDetailsPage>
heightRatioWithWidth: 0.82,
);
});
}else if (await Permission.camera.isGranted) {
} else if (await Permission.camera.isGranted) {
if (await Permission.storage.isPermanentlyDenied) {
showCupertinoDialog(
context: context,
@ -363,14 +365,15 @@ class _ChatDetailsPage extends State<ChatDetailsPage>
"您未开启存储权限,请点击开启",
"为了您可以在使用过程中访问您设备上的照片、媒体内容和文件,请您开启存储使用权限",
S.of(context).kaiqiquanxian,
(result) async {
(result) async {
if (result) {
await openAppSettings();
}
},
heightRatioWithWidth: 0.82,
);
});}else if(await Permission.storage.isGranted){
});
} else if (await Permission.storage.isGranted) {
Media medias = await ImagePickers.openCamera(
cameraMimeType: CameraMimeType.photo,
cropConfig: CropConfig(
@ -383,7 +386,9 @@ class _ChatDetailsPage extends State<ChatDetailsPage>
if (medias == null) return;
String path = medias.path;
String fileUrl = await qiniu.uploadFile(apiService, path);
socketClient.sendMessage(_toUser.mid, fileUrl, attach: path, msgType: 2).then((value) {
socketClient
.sendMessage(_toUser.mid, fileUrl, attach: path, msgType: 2)
.then((value) {
Message message = value;
messages.insert(0, message);
chatController.clear();
@ -392,7 +397,7 @@ class _ChatDetailsPage extends State<ChatDetailsPage>
jumpToBottom();
});
});
}else{
} else {
showCameraTipsAlertDialog(context);
return false;
}
@ -423,7 +428,10 @@ class _ChatDetailsPage extends State<ChatDetailsPage>
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(padding:EdgeInsets.only(right: 10.w,),
Padding(
padding: EdgeInsets.only(
right: 10.w,
),
child: Icon(
Icons.add_photo_alternate_outlined,
color: Colors.black,
@ -467,7 +475,7 @@ class _ChatDetailsPage extends State<ChatDetailsPage>
);
await Permission.camera.request();
Navigator.of(context).pop();
if(await Permission.camera.isGranted){
if (await Permission.camera.isGranted) {
openCamera();
}
}
@ -635,7 +643,6 @@ class _ChatDetailsPage extends State<ChatDetailsPage>
// height: 16.h,
// ),
/// not self
if (!isSelf && isText)
SizedBox(
@ -649,25 +656,25 @@ class _ChatDetailsPage extends State<ChatDetailsPage>
mainAxisAlignment: MainAxisAlignment.center,
children: [
GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: () {
Navigator.of(context).pushNamed(
'/router/personal_page',
arguments: {
"memberId": _toUser?.mid ?? "",
"inletType": 1
},
);
},
child: MImage(
_toUser.avatar,
isCircle: true,
height: 44.h,
width: 44.h,
fit: BoxFit.cover,
errorSrc: "assets/image/default_1.webp",
fadeSrc: "assets/image/default_1.webp",
),
behavior: HitTestBehavior.opaque,
onTap: () {
Navigator.of(context).pushNamed(
'/router/personal_page',
arguments: {
"memberId": _toUser?.mid ?? "",
"inletType": 1
},
);
},
child: MImage(
_toUser.avatar,
isCircle: true,
height: 44.h,
width: 44.h,
fit: BoxFit.cover,
errorSrc: "assets/image/default_1.webp",
fadeSrc: "assets/image/default_1.webp",
),
),
SizedBox(
width: 12.w,
@ -675,10 +682,12 @@ class _ChatDetailsPage extends State<ChatDetailsPage>
Expanded(
child: Container(
alignment: Alignment.centerLeft,
child:GestureDetector(
child: GestureDetector(
onLongPress: () {
RenderBox renderBox = _buttonKey.currentContext.findRenderObject() as RenderBox;
Offset buttonPosition = renderBox.localToGlobal(Offset.zero);
RenderBox renderBox = _buttonKey.currentContext
.findRenderObject() as RenderBox;
Offset buttonPosition =
renderBox.localToGlobal(Offset.zero);
Size buttonSize = renderBox.size;
showMenu(
@ -686,17 +695,19 @@ class _ChatDetailsPage extends State<ChatDetailsPage>
//
elevation: 0,
color: Colors.transparent,
position: RelativeRect.fromLTRB(
buttonPosition.dx + (buttonSize.width - 180.w) / 2, //
position: RelativeRect.fromLTRB(
buttonPosition.dx +
(buttonSize.width - 180.w) / 2, //
buttonPosition.dy - 68,
buttonPosition.dx + buttonSize.width,
buttonPosition.dy
), ///
buttonPosition.dy),
///
items: [
PopupMenuItem(
value: 1,
padding: EdgeInsets.zero,
child:Stack(
child: Stack(
alignment: Alignment.bottomCenter,
children: [
Container(
@ -705,18 +716,22 @@ class _ChatDetailsPage extends State<ChatDetailsPage>
width: 180.w,
decoration: BoxDecoration(
color: Color(0xFF2A2A2A),
borderRadius: BorderRadius.circular(6),
borderRadius:
BorderRadius.circular(6),
),
padding:
EdgeInsets.symmetric(horizontal: 32.w, vertical: 7.5.h),
padding: EdgeInsets.symmetric(
horizontal: 32.w, vertical: 7.5.h),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment:
MainAxisAlignment.spaceBetween,
crossAxisAlignment:
CrossAxisAlignment.center,
children: [
GestureDetector(
onTap: () {
setState(() {
this.copy(messages[position].content);
this.copy(messages[position]
.content);
Navigator.pop(context);
});
},
@ -736,15 +751,19 @@ class _ChatDetailsPage extends State<ChatDetailsPage>
style: TextStyle(
color: Colors.white,
fontSize: 12.sp,
fontWeight: MyFontWeight.regular,
fontWeight:
MyFontWeight.regular,
),
),
],
),
),
GestureDetector(
onTap: () async{
await hxDatabase.deleteByMsgId(messages[position].id.toString());
onTap: () async {
await hxDatabase.deleteByMsgId(
messages[position]
.id
.toString());
messages.removeAt(position);
Navigator.pop(context);
},
@ -764,7 +783,8 @@ class _ChatDetailsPage extends State<ChatDetailsPage>
style: TextStyle(
color: Colors.white,
fontSize: 12.sp,
fontWeight: MyFontWeight.regular,
fontWeight:
MyFontWeight.regular,
),
),
],
@ -803,7 +823,7 @@ class _ChatDetailsPage extends State<ChatDetailsPage>
vertical: 8.h,
horizontal: 12.w,
),
child: Text(
child: Text(
tex = messages[position].content,
textAlign: TextAlign.left,
style: TextStyle(
@ -858,11 +878,13 @@ class _ChatDetailsPage extends State<ChatDetailsPage>
),
Expanded(
child: Container(
alignment: Alignment.centerRight,
child: InkWell(
onLongPress: () {
RenderBox renderBox = _buttonKey.currentContext.findRenderObject() as RenderBox;
Offset buttonPosition = renderBox.localToGlobal(Offset.zero);
alignment: Alignment.centerRight,
child: InkWell(
onLongPress: () {
RenderBox renderBox = _buttonKey.currentContext
.findRenderObject() as RenderBox;
Offset buttonPosition =
renderBox.localToGlobal(Offset.zero);
Size buttonSize = renderBox.size;
showMenu(
@ -870,17 +892,19 @@ class _ChatDetailsPage extends State<ChatDetailsPage>
//
elevation: 0,
color: Colors.transparent,
position: RelativeRect.fromLTRB(
buttonPosition.dx + (buttonSize.width - 180.w) / 2, //
position: RelativeRect.fromLTRB(
buttonPosition.dx +
(buttonSize.width - 180.w) / 2, //
buttonPosition.dy - 68,
buttonPosition.dx + buttonSize.width,
buttonPosition.dy
), ///
buttonPosition.dy),
///
items: [
PopupMenuItem(
value: 1,
padding: EdgeInsets.zero,
child:Stack(
child: Stack(
alignment: Alignment.bottomCenter,
children: [
Container(
@ -889,18 +913,23 @@ class _ChatDetailsPage extends State<ChatDetailsPage>
width: 180.w,
decoration: BoxDecoration(
color: Color(0xFF2A2A2A),
borderRadius: BorderRadius.circular(6),
borderRadius:
BorderRadius.circular(6),
),
padding:
EdgeInsets.symmetric(horizontal: 32.w, vertical: 7.5.h),
padding: EdgeInsets.symmetric(
horizontal: 32.w,
vertical: 7.5.h),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment:
MainAxisAlignment.spaceBetween,
crossAxisAlignment:
CrossAxisAlignment.center,
children: [
GestureDetector(
onTap: () {
setState(() {
this.copy(messages[position].content);
this.copy(messages[position]
.content);
Navigator.pop(context);
});
},
@ -916,19 +945,25 @@ class _ChatDetailsPage extends State<ChatDetailsPage>
),
Text(
"复制",
textAlign: TextAlign.center,
textAlign:
TextAlign.center,
style: TextStyle(
color: Colors.white,
fontSize: 12.sp,
fontWeight: MyFontWeight.regular,
fontWeight: MyFontWeight
.regular,
),
),
],
),
),
GestureDetector(
onTap: () async{
await hxDatabase.deleteByMsgId(messages[position].id.toString());
onTap: () async {
await hxDatabase
.deleteByMsgId(
messages[position]
.id
.toString());
messages.removeAt(position);
Navigator.pop(context);
},
@ -944,11 +979,13 @@ class _ChatDetailsPage extends State<ChatDetailsPage>
),
Text(
S.of(context).shanchu,
textAlign: TextAlign.center,
textAlign:
TextAlign.center,
style: TextStyle(
color: Colors.white,
fontSize: 12.sp,
fontWeight: MyFontWeight.regular,
fontWeight: MyFontWeight
.regular,
),
),
],
@ -968,61 +1005,61 @@ class _ChatDetailsPage extends State<ChatDetailsPage>
),
],
);
},
child: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(6),
color: Color(0xFF32A060),
boxShadow: [
BoxShadow(
color: Color(0xFFA8A3A3).withAlpha(12),
offset: Offset(0, 4),
blurRadius: 4,
spreadRadius: 0,
},
child: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(6),
color: Color(0xFF32A060),
boxShadow: [
BoxShadow(
color: Color(0xFFA8A3A3).withAlpha(12),
offset: Offset(0, 4),
blurRadius: 4,
spreadRadius: 0,
),
],
),
key: _buttonKey,
padding: EdgeInsets.symmetric(
vertical: 8.h,
horizontal: 12.w,
),
child: Text(
tex = messages[position].content,
textAlign: TextAlign.left,
style: TextStyle(
height: 1.2.h,
color: Colors.white,
fontSize: 17.sp,
fontWeight: MyFontWeight.medium,
),
),
],
),
key: _buttonKey,
padding: EdgeInsets.symmetric(
vertical: 8.h,
horizontal: 12.w,
),
child: Text(
tex = messages[position].content,
textAlign: TextAlign.left,
style: TextStyle(
height: 1.2.h,
color: Colors.white,
fontSize: 17.sp,
fontWeight: MyFontWeight.medium,
),
),
),
)),
)),
),
SizedBox(
width: 12.w,
),
GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: () {
Navigator.of(context).pushNamed(
'/router/personal_page',
arguments: {
"memberId": "0",
"inletType": 1,
},
);
},
child: MImage(
userInfo?.headimg ?? "",
isCircle: true,
height: 44.h,
width: 44.h,
fit: BoxFit.cover,
errorSrc: "assets/image/default_1.webp",
fadeSrc: "assets/image/default_1.webp",
),
behavior: HitTestBehavior.opaque,
onTap: () {
Navigator.of(context).pushNamed(
'/router/personal_page',
arguments: {
"memberId": "0",
"inletType": 1,
},
);
},
child: MImage(
userInfo?.headimg ?? "",
isCircle: true,
height: 44.h,
width: 44.h,
fit: BoxFit.cover,
errorSrc: "assets/image/default_1.webp",
fadeSrc: "assets/image/default_1.webp",
),
),
],
),
@ -1050,11 +1087,13 @@ class _ChatDetailsPage extends State<ChatDetailsPage>
onLongPress: () {
setState(() {});
},
onTap:(){},
onTap: () {},
child: FutureBuilder(
future: fetchImageSize(messages[position]),
builder: (BuildContext context, AsyncSnapshot snapshot) {
if (snapshot.connectionState == ConnectionState.waiting || snapshot.hasError) {
if (snapshot.connectionState ==
ConnectionState.waiting ||
snapshot.hasError) {
return Image.file(
File(messages[position].attach),
width: 180.w,
@ -1088,11 +1127,13 @@ class _ChatDetailsPage extends State<ChatDetailsPage>
onLongPress: () {
setState(() {});
},
onTap:(){},
onTap: () {},
child: FutureBuilder(
future: fetchImageSize(messages[position]),
builder: (BuildContext context, AsyncSnapshot snapshot) {
if (snapshot.connectionState == ConnectionState.waiting || snapshot.hasError) {
if (snapshot.connectionState ==
ConnectionState.waiting ||
snapshot.hasError) {
return Image.file(
File(messages[position].attach),
width: 180.w,
@ -1168,9 +1209,12 @@ class _ChatDetailsPage extends State<ChatDetailsPage>
}
Completer<Size> completer = Completer();
Image.file(file).image.resolve(ImageConfiguration())
Image.file(file)
.image
.resolve(ImageConfiguration())
.addListener(ImageStreamListener((image, synchronousCall) {
size = Size((image.image.width ?? 0).toDouble(), (image.image.height ?? 0).toDouble());
size = Size((image.image.width ?? 0).toDouble(),
(image.image.height ?? 0).toDouble());
size = resizeImage(size);
Map<String, double> sizeMap = {
"width": size.width,
@ -1229,7 +1273,8 @@ class _ChatDetailsPage extends State<ChatDetailsPage>
if (commentText.trim() == "") {
return;
}
socketClient.sendMessage(_toUser.mid, commentText)
socketClient
.sendMessage(_toUser.mid, commentText)
.then((value) {
Message message = value;
messages.insert(0, message);
@ -1263,12 +1308,12 @@ class _ChatDetailsPage extends State<ChatDetailsPage>
jumpToBottom();
},
child: Container(
padding: EdgeInsets.only(left: 15.w, right: 8.w),
child: Image.asset(
"assets/image/icon_chat_emo.webp",
height: 26.h,
width: 26.h,
),
padding: EdgeInsets.only(left: 15.w, right: 8.w),
child: Image.asset(
"assets/image/icon_chat_emo.webp",
height: 26.h,
width: 26.h,
),
),
),
GestureDetector(
@ -1297,40 +1342,40 @@ class _ChatDetailsPage extends State<ChatDetailsPage>
Offstage(
offstage: !emojiShowing,
child: SizedBox(
height: keyboard == -1 ? 270 : keyboard,
width: MediaQuery.of(context).size.width,
child: EmojiPicker(
textEditingController: chatController,
config: Config(
columns: 7,
emojiSizeMax: 32 * (Platform.isIOS ? 1.10 : 1.0),
verticalSpacing: 0,
horizontalSpacing: 0,
gridPadding: EdgeInsets.zero,
initCategory: Category.RECENT,
bgColor: const Color(0xFFF2F2F2),
// indicatorColor: Colors.blue,
iconColor: Colors.grey,
iconColorSelected: Colors.blue,
backspaceColor: Colors.blue,
skinToneDialogBgColor: Colors.white,
skinToneIndicatorColor: Colors.grey,
enableSkinTones: true,
showRecentsTab: true,
recentsLimit: 28,
replaceEmojiOnLimitExceed: false,
noRecents: Text(
"最近使用",
style: TextStyle(fontSize: 20, color: Colors.black26),
textAlign: TextAlign.center,
),
loadingIndicator: const SizedBox.shrink(),
tabIndicatorAnimDuration: Duration(milliseconds: 0),
categoryIcons: const CategoryIcons(),
buttonMode: ButtonMode.MATERIAL,
checkPlatformCompatibility: true,
height: keyboard == -1 ? 270 : keyboard,
width: MediaQuery.of(context).size.width,
child: EmojiPicker(
textEditingController: chatController,
config: Config(
columns: 7,
emojiSizeMax: 32 * (Platform.isIOS ? 1.10 : 1.0),
verticalSpacing: 0,
horizontalSpacing: 0,
gridPadding: EdgeInsets.zero,
initCategory: Category.RECENT,
bgColor: const Color(0xFFF2F2F2),
// indicatorColor: Colors.blue,
iconColor: Colors.grey,
iconColorSelected: Colors.blue,
backspaceColor: Colors.blue,
skinToneDialogBgColor: Colors.white,
skinToneIndicatorColor: Colors.grey,
enableSkinTones: true,
showRecentsTab: true,
recentsLimit: 28,
replaceEmojiOnLimitExceed: false,
noRecents: Text(
"最近使用",
style: TextStyle(fontSize: 20, color: Colors.black26),
textAlign: TextAlign.center,
),
loadingIndicator: const SizedBox.shrink(),
tabIndicatorAnimDuration: Duration(milliseconds: 0),
categoryIcons: const CategoryIcons(),
buttonMode: ButtonMode.MATERIAL,
checkPlatformCompatibility: true,
),
),
),
),
@ -1392,45 +1437,45 @@ class _ChatDetailsPage extends State<ChatDetailsPage>
),
),
GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: () {
getImageOrVideo(GalleryMode.image);
},
child: Column(
children: [
Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(6),
color: Color(0xFFFFFFFF),
boxShadow: [
BoxShadow(
color: Color(0xFFD5D5D5).withAlpha(25),
offset: Offset(4, 2),
blurRadius: 4,
spreadRadius: 0,
)
],
),
padding: EdgeInsets.all(12),
child: Image.asset(
"assets/image/icon_chat_photo.webp",
height: 24,
width: 24,
),
behavior: HitTestBehavior.opaque,
onTap: () {
getImageOrVideo(GalleryMode.image);
},
child: Column(
children: [
Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(6),
color: Color(0xFFFFFFFF),
boxShadow: [
BoxShadow(
color: Color(0xFFD5D5D5).withAlpha(25),
offset: Offset(4, 2),
blurRadius: 4,
spreadRadius: 0,
)
],
),
Padding(
padding: EdgeInsets.only(top: 8.h),
child: Text(
"相册",
style: TextStyle(
fontSize: 12.sp,
color: Color(0xFFA29E9E),
fontWeight: MyFontWeight.regular,
),
padding: EdgeInsets.all(12),
child: Image.asset(
"assets/image/icon_chat_photo.webp",
height: 24,
width: 24,
),
),
Padding(
padding: EdgeInsets.only(top: 8.h),
child: Text(
"相册",
style: TextStyle(
fontSize: 12.sp,
color: Color(0xFFA29E9E),
fontWeight: MyFontWeight.regular,
),
),
],
),
),
],
),
),
],
),
@ -1448,5 +1493,4 @@ class _ChatDetailsPage extends State<ChatDetailsPage>
print(tex);
Clipboard.setData(ClipboardData(text: tex));
}
}

4
lib/im/chat_friend_group.dart

@ -4,18 +4,14 @@ import 'package:dio/dio.dart';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter/services.dart';
import 'package:flutter_easyloading/flutter_easyloading.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:huixiang/retrofit/retrofit_api.dart';
import 'package:huixiang/view_widget/my_appbar.dart';
import 'package:flutter/cupertino.dart';
import 'package:shared_preferences/shared_preferences.dart';
import '../../generated/l10n.dart';
import '../../utils/font_weight.dart';
import '../main.dart';
import '../retrofit/data/base_data.dart';
import '../retrofit/data/social_info.dart';
import '../utils/event_type.dart';
import '../view_widget/my_tab.dart';
import 'im_view/custom_underline_tabIndicator.dart';
import 'im_view/friend_groip_list.dart';

3
lib/im/chat_setting.dart

@ -4,13 +4,10 @@ import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter/services.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:huixiang/im/database/message.dart';
import 'package:huixiang/retrofit/retrofit_api.dart';
import 'package:huixiang/view_widget/my_appbar.dart';
import 'package:flutter/cupertino.dart';
import 'package:shared_preferences/shared_preferences.dart';
import '../../generated/l10n.dart';
import '../../utils/font_weight.dart';
import '../main.dart';
import '../retrofit/data/im_user.dart';

2
lib/im/contact_share.dart

@ -8,8 +8,6 @@ import 'package:huixiang/retrofit/retrofit_api.dart';
import 'package:huixiang/view_widget/my_appbar.dart';
import 'package:flutter/cupertino.dart';
import '../../generated/l10n.dart';
import '../../retrofit/data/base_data.dart';
import '../../utils/font_weight.dart';
class ContactsShare extends StatefulWidget {

1
lib/im/im_search.dart

@ -3,7 +3,6 @@ import 'dart:ui';
import 'package:dio/dio.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_easyloading/flutter_easyloading.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:shared_preferences/shared_preferences.dart';

Loading…
Cancel
Save