Implement file picker web
This commit is contained in:
parent
bcda0bbd5f
commit
2d83df34c0
|
@ -2,6 +2,7 @@
|
||||||
### Features:
|
### Features:
|
||||||
- New room list app bar design
|
- New room list app bar design
|
||||||
- Chat app bar transparent
|
- Chat app bar transparent
|
||||||
|
- Implement web file picker
|
||||||
### Changes:
|
### Changes:
|
||||||
- Show presences of users sharing a direct chat
|
- Show presences of users sharing a direct chat
|
||||||
- Big refactoring
|
- Big refactoring
|
||||||
|
|
|
@ -3,7 +3,6 @@ import 'dart:io';
|
||||||
import 'dart:math';
|
import 'dart:math';
|
||||||
|
|
||||||
import 'package:famedlysdk/famedlysdk.dart';
|
import 'package:famedlysdk/famedlysdk.dart';
|
||||||
import 'package:file_picker/file_picker.dart';
|
|
||||||
import 'package:fluffychat/components/adaptive_page_layout.dart';
|
import 'package:fluffychat/components/adaptive_page_layout.dart';
|
||||||
import 'package:fluffychat/components/avatar.dart';
|
import 'package:fluffychat/components/avatar.dart';
|
||||||
import 'package:fluffychat/components/chat_settings_popup_menu.dart';
|
import 'package:fluffychat/components/chat_settings_popup_menu.dart';
|
||||||
|
@ -20,9 +19,9 @@ import 'package:fluffychat/utils/room_status_extension.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:bot_toast/bot_toast.dart';
|
import 'package:memoryfilepicker/memoryfilepicker.dart';
|
||||||
import 'package:image_picker/image_picker.dart';
|
|
||||||
import 'package:pedantic/pedantic.dart';
|
import 'package:pedantic/pedantic.dart';
|
||||||
|
import 'package:image_picker/image_picker.dart';
|
||||||
|
|
||||||
import 'chat_details.dart';
|
import 'chat_details.dart';
|
||||||
import 'chat_list.dart';
|
import 'chat_list.dart';
|
||||||
|
@ -187,25 +186,17 @@ class _ChatState extends State<_Chat> {
|
||||||
}
|
}
|
||||||
|
|
||||||
void sendFileAction(BuildContext context) async {
|
void sendFileAction(BuildContext context) async {
|
||||||
if (kIsWeb) {
|
var file = await MemoryFilePicker.getFile();
|
||||||
BotToast.showText(text: L10n.of(context).notSupportedInWeb);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
var file = await FilePicker.getFile();
|
|
||||||
if (file == null) return;
|
if (file == null) return;
|
||||||
await SimpleDialogs(context).tryRequestWithLoadingDialog(
|
await SimpleDialogs(context).tryRequestWithLoadingDialog(
|
||||||
room.sendFileEvent(
|
room.sendFileEvent(
|
||||||
MatrixFile(bytes: await file.readAsBytes(), path: file.path),
|
MatrixFile(bytes: file.bytes, path: file.path),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void sendImageAction(BuildContext context) async {
|
void sendImageAction(BuildContext context) async {
|
||||||
if (kIsWeb) {
|
var file = await MemoryFilePicker.getImage(
|
||||||
BotToast.showText(text: L10n.of(context).notSupportedInWeb);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
var file = await ImagePicker.pickImage(
|
|
||||||
source: ImageSource.gallery,
|
source: ImageSource.gallery,
|
||||||
imageQuality: 50,
|
imageQuality: 50,
|
||||||
maxWidth: 1600,
|
maxWidth: 1600,
|
||||||
|
@ -213,17 +204,13 @@ class _ChatState extends State<_Chat> {
|
||||||
if (file == null) return;
|
if (file == null) return;
|
||||||
await SimpleDialogs(context).tryRequestWithLoadingDialog(
|
await SimpleDialogs(context).tryRequestWithLoadingDialog(
|
||||||
room.sendImageEvent(
|
room.sendImageEvent(
|
||||||
MatrixFile(bytes: await file.readAsBytes(), path: file.path),
|
MatrixFile(bytes: await file.bytes, path: file.path),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void openCameraAction(BuildContext context) async {
|
void openCameraAction(BuildContext context) async {
|
||||||
if (kIsWeb) {
|
var file = await MemoryFilePicker.getImage(
|
||||||
BotToast.showText(text: L10n.of(context).notSupportedInWeb);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
var file = await ImagePicker.pickImage(
|
|
||||||
source: ImageSource.camera,
|
source: ImageSource.camera,
|
||||||
imageQuality: 50,
|
imageQuality: 50,
|
||||||
maxWidth: 1600,
|
maxWidth: 1600,
|
||||||
|
@ -231,7 +218,7 @@ class _ChatState extends State<_Chat> {
|
||||||
if (file == null) return;
|
if (file == null) return;
|
||||||
await SimpleDialogs(context).tryRequestWithLoadingDialog(
|
await SimpleDialogs(context).tryRequestWithLoadingDialog(
|
||||||
room.sendImageEvent(
|
room.sendImageEvent(
|
||||||
MatrixFile(bytes: await file.readAsBytes(), path: file.path),
|
MatrixFile(bytes: file.bytes, path: file.path),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -650,7 +637,7 @@ class _ChatState extends State<_Chat> {
|
||||||
: Container(),
|
: Container(),
|
||||||
]
|
]
|
||||||
: <Widget>[
|
: <Widget>[
|
||||||
if (!kIsWeb && inputText.isEmpty)
|
if (inputText.isEmpty)
|
||||||
PopupMenuButton<String>(
|
PopupMenuButton<String>(
|
||||||
icon: Icon(Icons.add),
|
icon: Icon(Icons.add),
|
||||||
onSelected: (String choice) async {
|
onSelected: (String choice) async {
|
||||||
|
@ -694,6 +681,7 @@ class _ChatState extends State<_Chat> {
|
||||||
contentPadding: EdgeInsets.all(0),
|
contentPadding: EdgeInsets.all(0),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
if (!kIsWeb)
|
||||||
PopupMenuItem<String>(
|
PopupMenuItem<String>(
|
||||||
value: 'camera',
|
value: 'camera',
|
||||||
child: ListTile(
|
child: ListTile(
|
||||||
|
@ -702,11 +690,12 @@ class _ChatState extends State<_Chat> {
|
||||||
foregroundColor: Colors.white,
|
foregroundColor: Colors.white,
|
||||||
child: Icon(Icons.camera_alt),
|
child: Icon(Icons.camera_alt),
|
||||||
),
|
),
|
||||||
title:
|
title: Text(
|
||||||
Text(L10n.of(context).openCamera),
|
L10n.of(context).openCamera),
|
||||||
contentPadding: EdgeInsets.all(0),
|
contentPadding: EdgeInsets.all(0),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
if (!kIsWeb)
|
||||||
PopupMenuItem<String>(
|
PopupMenuItem<String>(
|
||||||
value: 'voice',
|
value: 'voice',
|
||||||
child: ListTile(
|
child: ListTile(
|
||||||
|
|
|
@ -15,6 +15,7 @@ import 'package:flutter/services.dart';
|
||||||
import 'package:bot_toast/bot_toast.dart';
|
import 'package:bot_toast/bot_toast.dart';
|
||||||
import 'package:image_picker/image_picker.dart';
|
import 'package:image_picker/image_picker.dart';
|
||||||
import 'package:link_text/link_text.dart';
|
import 'package:link_text/link_text.dart';
|
||||||
|
import 'package:memoryfilepicker/memoryfilepicker.dart';
|
||||||
import './settings_emotes.dart';
|
import './settings_emotes.dart';
|
||||||
|
|
||||||
class ChatDetails extends StatefulWidget {
|
class ChatDetails extends StatefulWidget {
|
||||||
|
@ -100,7 +101,7 @@ class _ChatDetailsState extends State<ChatDetails> {
|
||||||
}
|
}
|
||||||
|
|
||||||
void setAvatarAction(BuildContext context) async {
|
void setAvatarAction(BuildContext context) async {
|
||||||
final tempFile = await ImagePicker.pickImage(
|
final tempFile = await MemoryFilePicker.getImage(
|
||||||
source: ImageSource.gallery,
|
source: ImageSource.gallery,
|
||||||
imageQuality: 50,
|
imageQuality: 50,
|
||||||
maxWidth: 1600,
|
maxWidth: 1600,
|
||||||
|
@ -109,7 +110,7 @@ class _ChatDetailsState extends State<ChatDetails> {
|
||||||
final success = await SimpleDialogs(context).tryRequestWithLoadingDialog(
|
final success = await SimpleDialogs(context).tryRequestWithLoadingDialog(
|
||||||
widget.room.setAvatar(
|
widget.room.setAvatar(
|
||||||
MatrixFile(
|
MatrixFile(
|
||||||
bytes: await tempFile.readAsBytes(),
|
bytes: tempFile.bytes,
|
||||||
path: tempFile.path,
|
path: tempFile.path,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
|
@ -1,9 +1,12 @@
|
||||||
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:famedlysdk/famedlysdk.dart';
|
import 'package:famedlysdk/famedlysdk.dart';
|
||||||
import 'package:fluffychat/components/settings_themes.dart';
|
import 'package:fluffychat/components/settings_themes.dart';
|
||||||
import 'package:fluffychat/views/settings_devices.dart';
|
import 'package:fluffychat/views/settings_devices.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:image_picker/image_picker.dart';
|
import 'package:image_picker/image_picker.dart';
|
||||||
|
import 'package:memoryfilepicker/memoryfilepicker.dart';
|
||||||
import 'package:url_launcher/url_launcher.dart';
|
import 'package:url_launcher/url_launcher.dart';
|
||||||
|
|
||||||
import 'app_info.dart';
|
import 'app_info.dart';
|
||||||
|
@ -81,7 +84,7 @@ class _SettingsState extends State<Settings> {
|
||||||
}
|
}
|
||||||
|
|
||||||
void setAvatarAction(BuildContext context) async {
|
void setAvatarAction(BuildContext context) async {
|
||||||
final tempFile = await ImagePicker.pickImage(
|
final tempFile = await MemoryFilePicker.getImage(
|
||||||
source: ImageSource.gallery,
|
source: ImageSource.gallery,
|
||||||
imageQuality: 50,
|
imageQuality: 50,
|
||||||
maxWidth: 1600,
|
maxWidth: 1600,
|
||||||
|
@ -91,7 +94,7 @@ class _SettingsState extends State<Settings> {
|
||||||
final success = await SimpleDialogs(context).tryRequestWithLoadingDialog(
|
final success = await SimpleDialogs(context).tryRequestWithLoadingDialog(
|
||||||
matrix.client.setAvatar(
|
matrix.client.setAvatar(
|
||||||
MatrixFile(
|
MatrixFile(
|
||||||
bytes: await tempFile.readAsBytes(),
|
bytes: tempFile.bytes,
|
||||||
path: tempFile.path,
|
path: tempFile.path,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -105,9 +108,9 @@ class _SettingsState extends State<Settings> {
|
||||||
}
|
}
|
||||||
|
|
||||||
void setWallpaperAction(BuildContext context) async {
|
void setWallpaperAction(BuildContext context) async {
|
||||||
final wallpaper = await ImagePicker.pickImage(source: ImageSource.gallery);
|
final wallpaper = await ImagePicker().getImage(source: ImageSource.gallery);
|
||||||
if (wallpaper == null) return;
|
if (wallpaper == null) return;
|
||||||
Matrix.of(context).wallpaper = wallpaper;
|
Matrix.of(context).wallpaper = File(wallpaper.path);
|
||||||
await Matrix.of(context)
|
await Matrix.of(context)
|
||||||
.store
|
.store
|
||||||
.setItem('chat.fluffy.wallpaper', wallpaper.path);
|
.setItem('chat.fluffy.wallpaper', wallpaper.path);
|
||||||
|
|
|
@ -4,6 +4,7 @@ import 'package:flutter_advanced_networkimage/provider.dart';
|
||||||
import 'package:famedlysdk/famedlysdk.dart';
|
import 'package:famedlysdk/famedlysdk.dart';
|
||||||
import 'package:image_picker/image_picker.dart';
|
import 'package:image_picker/image_picker.dart';
|
||||||
import 'package:bot_toast/bot_toast.dart';
|
import 'package:bot_toast/bot_toast.dart';
|
||||||
|
import 'package:memoryfilepicker/memoryfilepicker.dart';
|
||||||
|
|
||||||
import 'chat_list.dart';
|
import 'chat_list.dart';
|
||||||
import '../components/adaptive_page_layout.dart';
|
import '../components/adaptive_page_layout.dart';
|
||||||
|
@ -367,14 +368,13 @@ class _EmoteImagePickerState extends State<_EmoteImagePicker> {
|
||||||
BotToast.showText(text: L10n.of(context).notSupportedInWeb);
|
BotToast.showText(text: L10n.of(context).notSupportedInWeb);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
var file = await ImagePicker.pickImage(
|
var file = await MemoryFilePicker.getImage(
|
||||||
source: ImageSource.gallery,
|
source: ImageSource.gallery,
|
||||||
imageQuality: 50,
|
imageQuality: 50,
|
||||||
maxWidth: 128,
|
maxWidth: 128,
|
||||||
maxHeight: 128);
|
maxHeight: 128);
|
||||||
if (file == null) return;
|
if (file == null) return;
|
||||||
final matrixFile =
|
final matrixFile = MatrixFile(bytes: file.bytes, path: file.path);
|
||||||
MatrixFile(bytes: await file.readAsBytes(), path: file.path);
|
|
||||||
final uploadResp =
|
final uploadResp =
|
||||||
await SimpleDialogs(context).tryRequestWithLoadingDialog(
|
await SimpleDialogs(context).tryRequestWithLoadingDialog(
|
||||||
Matrix.of(context)
|
Matrix.of(context)
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
import 'dart:io';
|
|
||||||
import 'dart:math';
|
import 'dart:math';
|
||||||
|
|
||||||
import 'package:famedlysdk/famedlysdk.dart';
|
import 'package:famedlysdk/famedlysdk.dart';
|
||||||
|
@ -10,6 +9,7 @@ import 'package:fluffychat/views/sign_up_password.dart';
|
||||||
import 'package:flutter/cupertino.dart';
|
import 'package:flutter/cupertino.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:image_picker/image_picker.dart';
|
import 'package:image_picker/image_picker.dart';
|
||||||
|
import 'package:memoryfilepicker/memoryfilepicker.dart';
|
||||||
|
|
||||||
class SignUp extends StatefulWidget {
|
class SignUp extends StatefulWidget {
|
||||||
@override
|
@override
|
||||||
|
@ -20,10 +20,10 @@ class _SignUpState extends State<SignUp> {
|
||||||
final TextEditingController usernameController = TextEditingController();
|
final TextEditingController usernameController = TextEditingController();
|
||||||
String usernameError;
|
String usernameError;
|
||||||
bool loading = false;
|
bool loading = false;
|
||||||
File avatar;
|
MemoryFile avatar;
|
||||||
|
|
||||||
void setAvatarAction() async {
|
void setAvatarAction() async {
|
||||||
var file = await ImagePicker.pickImage(
|
var file = await MemoryFilePicker.getImage(
|
||||||
source: ImageSource.gallery,
|
source: ImageSource.gallery,
|
||||||
maxHeight: 512,
|
maxHeight: 512,
|
||||||
maxWidth: 512,
|
maxWidth: 512,
|
||||||
|
@ -92,7 +92,8 @@ class _SignUpState extends State<SignUp> {
|
||||||
),
|
),
|
||||||
ListTile(
|
ListTile(
|
||||||
leading: CircleAvatar(
|
leading: CircleAvatar(
|
||||||
backgroundImage: avatar == null ? null : FileImage(avatar),
|
backgroundImage:
|
||||||
|
avatar == null ? null : MemoryImage(avatar.bytes),
|
||||||
backgroundColor: avatar == null
|
backgroundColor: avatar == null
|
||||||
? Theme.of(context).brightness == Brightness.dark
|
? Theme.of(context).brightness == Brightness.dark
|
||||||
? Color(0xff121212)
|
? Color(0xff121212)
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
import 'dart:io';
|
|
||||||
import 'dart:math';
|
import 'dart:math';
|
||||||
|
|
||||||
import 'package:famedlysdk/famedlysdk.dart';
|
import 'package:famedlysdk/famedlysdk.dart';
|
||||||
|
@ -8,11 +7,12 @@ import 'package:fluffychat/utils/app_route.dart';
|
||||||
import 'package:fluffychat/views/auth_web_view.dart';
|
import 'package:fluffychat/views/auth_web_view.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:bot_toast/bot_toast.dart';
|
import 'package:bot_toast/bot_toast.dart';
|
||||||
|
import 'package:memoryfilepicker/memoryfilepicker.dart';
|
||||||
|
|
||||||
import 'chat_list.dart';
|
import 'chat_list.dart';
|
||||||
|
|
||||||
class SignUpPassword extends StatefulWidget {
|
class SignUpPassword extends StatefulWidget {
|
||||||
final File avatar;
|
final MemoryFile avatar;
|
||||||
final String username;
|
final String username;
|
||||||
final String displayname;
|
final String displayname;
|
||||||
const SignUpPassword(this.username, {this.avatar, this.displayname});
|
const SignUpPassword(this.username, {this.avatar, this.displayname});
|
||||||
|
@ -100,7 +100,7 @@ class _SignUpPasswordState extends State<SignUpPassword> {
|
||||||
try {
|
try {
|
||||||
await matrix.client.setAvatar(
|
await matrix.client.setAvatar(
|
||||||
MatrixFile(
|
MatrixFile(
|
||||||
bytes: await widget.avatar.readAsBytes(),
|
bytes: widget.avatar.bytes,
|
||||||
path: widget.avatar.path,
|
path: widget.avatar.path,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
40
pubspec.lock
40
pubspec.lock
|
@ -160,12 +160,19 @@ packages:
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.1.3"
|
version: "0.1.3"
|
||||||
file_picker:
|
file_picker:
|
||||||
dependency: "direct main"
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: file_picker
|
name: file_picker
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.4.3+2"
|
version: "1.12.0"
|
||||||
|
file_picker_platform_interface:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: file_picker_platform_interface
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "1.2.0"
|
||||||
firebase_messaging:
|
firebase_messaging:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
@ -225,6 +232,13 @@ packages:
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.1.0"
|
version: "0.1.0"
|
||||||
|
flutter_plugin_android_lifecycle:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: flutter_plugin_android_lifecycle
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "1.0.8"
|
||||||
flutter_secure_storage:
|
flutter_secure_storage:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
@ -327,12 +341,19 @@ packages:
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.12"
|
version: "2.1.12"
|
||||||
image_picker:
|
image_picker:
|
||||||
dependency: "direct main"
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: image_picker
|
name: image_picker
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.6.2+3"
|
version: "0.6.7+2"
|
||||||
|
image_picker_platform_interface:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: image_picker_platform_interface
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "1.1.0"
|
||||||
intl:
|
intl:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
@ -405,6 +426,13 @@ packages:
|
||||||
url: "https://gitlab.com/famedly/libraries/matrix_file_e2ee.git"
|
url: "https://gitlab.com/famedly/libraries/matrix_file_e2ee.git"
|
||||||
source: git
|
source: git
|
||||||
version: "1.0.3"
|
version: "1.0.3"
|
||||||
|
memoryfilepicker:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: memoryfilepicker
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "0.1.1"
|
||||||
meta:
|
meta:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -553,7 +581,7 @@ packages:
|
||||||
name: plugin_platform_interface
|
name: plugin_platform_interface
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.0.1"
|
version: "1.0.2"
|
||||||
pointycastle:
|
pointycastle:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -740,7 +768,7 @@ packages:
|
||||||
name: universal_html
|
name: universal_html
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.1.12"
|
version: "1.2.3"
|
||||||
universal_io:
|
universal_io:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
|
@ -31,8 +31,7 @@ dependencies:
|
||||||
|
|
||||||
localstorage: ^3.0.1+4
|
localstorage: ^3.0.1+4
|
||||||
bubble: ^1.1.9+1
|
bubble: ^1.1.9+1
|
||||||
file_picker: ^1.4.3+2
|
memoryfilepicker: ^0.1.1
|
||||||
image_picker: ^0.6.2+3
|
|
||||||
flutter_speed_dial: ^1.2.5
|
flutter_speed_dial: ^1.2.5
|
||||||
url_launcher: ^5.4.1
|
url_launcher: ^5.4.1
|
||||||
url_launcher_web: ^0.1.0
|
url_launcher_web: ^0.1.0
|
||||||
|
|
Loading…
Reference in a new issue