LinkedIn, Facebook and Xing links added in user profile.
parent
b5aa8d93fb
commit
7b82c1cbb8
|
@ -0,0 +1,59 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/services.dart' show Clipboard, ClipboardData;
|
||||||
|
import 'package:url_launcher/url_launcher.dart';
|
||||||
|
|
||||||
|
import '../utils/helper_dialogs.dart';
|
||||||
|
|
||||||
|
class ExternalLinkWidget extends StatelessWidget {
|
||||||
|
final String url;
|
||||||
|
final BuildContext context;
|
||||||
|
|
||||||
|
const ExternalLinkWidget(
|
||||||
|
{required this.url, required this.context, super.key});
|
||||||
|
|
||||||
|
Future<void> _onTap() async {
|
||||||
|
bool? confirm = await showConfirmationDialog(
|
||||||
|
context,
|
||||||
|
'External Link',
|
||||||
|
'You are about to open an external link. '
|
||||||
|
'Please make sure you trust the link before proceeding.\n'
|
||||||
|
'$url\n'
|
||||||
|
'Do you want to open this external link?\n\n'
|
||||||
|
'Hint: Long press to copy the link to your clipboard instead of opening it.',
|
||||||
|
);
|
||||||
|
if (confirm == true) {
|
||||||
|
Uri weblink = Uri.parse(url);
|
||||||
|
if (!await launchUrl(weblink)) {
|
||||||
|
if (context.mounted) {
|
||||||
|
showErrorSnackBar(context, 'Could not launch $weblink');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _onLongPress() {
|
||||||
|
_copyToClipboard(context, url);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _copyToClipboard(BuildContext context, String text) {
|
||||||
|
Clipboard.setData(ClipboardData(text: text)).then((_) {
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
SnackBar(content: Text('Copied $text to clipboard')),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return GestureDetector(
|
||||||
|
onTap: _onTap,
|
||||||
|
onLongPress: _onLongPress,
|
||||||
|
child: Text(
|
||||||
|
url,
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
style: const TextStyle(color: Colors.blue),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -58,6 +58,9 @@ class Constants {
|
||||||
static const String dbFieldUsersCommunication = 'communication';
|
static const String dbFieldUsersCommunication = 'communication';
|
||||||
static const String dbFieldUsersRiskTolerance = 'risk_tolerance';
|
static const String dbFieldUsersRiskTolerance = 'risk_tolerance';
|
||||||
static const String dbFieldUsersSectors = 'sectors_of_interest';
|
static const String dbFieldUsersSectors = 'sectors_of_interest';
|
||||||
|
static const String dbFieldUsersUrlFacebook = 'url_facebook';
|
||||||
|
static const String dbFieldUsersUrlLinkedIn = 'url_linkedin';
|
||||||
|
static const String dbFieldUsersUrlXing = 'url_xing';
|
||||||
|
|
||||||
static const String dbStoragePathProfiles = 'profile_images';
|
static const String dbStoragePathProfiles = 'profile_images';
|
||||||
|
|
||||||
|
|
|
@ -10,6 +10,9 @@ class UserProfile {
|
||||||
final String firstName;
|
final String firstName;
|
||||||
final String lastName;
|
final String lastName;
|
||||||
String? profilePictureUrl;
|
String? profilePictureUrl;
|
||||||
|
String? urlFacebook;
|
||||||
|
String? urlLinkedIn;
|
||||||
|
String? urlXing;
|
||||||
String? bio;
|
String? bio;
|
||||||
Gender? gender;
|
Gender? gender;
|
||||||
int? born;
|
int? born;
|
||||||
|
@ -33,6 +36,9 @@ class UserProfile {
|
||||||
required this.firstName,
|
required this.firstName,
|
||||||
required this.lastName,
|
required this.lastName,
|
||||||
this.profilePictureUrl,
|
this.profilePictureUrl,
|
||||||
|
this.urlFacebook,
|
||||||
|
this.urlLinkedIn,
|
||||||
|
this.urlXing,
|
||||||
this.bio,
|
this.bio,
|
||||||
this.gender,
|
this.gender,
|
||||||
this.born,
|
this.born,
|
||||||
|
|
|
@ -10,6 +10,7 @@ import 'dart:typed_data';
|
||||||
|
|
||||||
import '../constants.dart';
|
import '../constants.dart';
|
||||||
import '../models/user_profile.dart';
|
import '../models/user_profile.dart';
|
||||||
|
import '../utils/helper.dart';
|
||||||
|
|
||||||
class EditProfilePage extends StatefulWidget {
|
class EditProfilePage extends StatefulWidget {
|
||||||
final UserProfile userData;
|
final UserProfile userData;
|
||||||
|
@ -24,6 +25,9 @@ class EditProfilePageState extends State<EditProfilePage> {
|
||||||
final _formKey = GlobalKey<FormState>();
|
final _formKey = GlobalKey<FormState>();
|
||||||
late TextEditingController _nameController;
|
late TextEditingController _nameController;
|
||||||
late TextEditingController _bioController;
|
late TextEditingController _bioController;
|
||||||
|
late TextEditingController _urlFbController;
|
||||||
|
late TextEditingController _urlLnController;
|
||||||
|
late TextEditingController _urlXiController;
|
||||||
String? profileImageUrl;
|
String? profileImageUrl;
|
||||||
io.File? _profileImage; // for android/ios
|
io.File? _profileImage; // for android/ios
|
||||||
Uint8List? _webProfileImage; // for web
|
Uint8List? _webProfileImage; // for web
|
||||||
|
@ -33,6 +37,9 @@ class EditProfilePageState extends State<EditProfilePage> {
|
||||||
super.initState();
|
super.initState();
|
||||||
_nameController = TextEditingController(text: widget.userData.name);
|
_nameController = TextEditingController(text: widget.userData.name);
|
||||||
_bioController = TextEditingController(text: widget.userData.bio);
|
_bioController = TextEditingController(text: widget.userData.bio);
|
||||||
|
_urlFbController = TextEditingController(text: widget.userData.urlFacebook);
|
||||||
|
_urlLnController = TextEditingController(text: widget.userData.urlLinkedIn);
|
||||||
|
_urlXiController = TextEditingController(text: widget.userData.urlXing);
|
||||||
if (widget.userData.profilePictureUrl != null) {
|
if (widget.userData.profilePictureUrl != null) {
|
||||||
profileImageUrl = widget.userData.profilePictureUrl;
|
profileImageUrl = widget.userData.profilePictureUrl;
|
||||||
}
|
}
|
||||||
|
@ -135,6 +142,9 @@ class EditProfilePageState extends State<EditProfilePage> {
|
||||||
Constants.dbFieldUsersName: nameTrim,
|
Constants.dbFieldUsersName: nameTrim,
|
||||||
Constants.dbFieldUsersBio: bioTrim,
|
Constants.dbFieldUsersBio: bioTrim,
|
||||||
Constants.dbFieldUsersProfilePic: profileImageUrl,
|
Constants.dbFieldUsersProfilePic: profileImageUrl,
|
||||||
|
Constants.dbFieldUsersUrlFacebook: _urlFbController.text.trim(),
|
||||||
|
Constants.dbFieldUsersUrlLinkedIn: _urlLnController.text.trim(),
|
||||||
|
Constants.dbFieldUsersUrlXing: _urlXiController.text.trim(),
|
||||||
};
|
};
|
||||||
|
|
||||||
await FirebaseFirestore.instance
|
await FirebaseFirestore.instance
|
||||||
|
@ -152,6 +162,9 @@ class EditProfilePageState extends State<EditProfilePage> {
|
||||||
Constants.dbFieldUsersProfilePic: map[Constants.dbFieldUsersProfilePic],
|
Constants.dbFieldUsersProfilePic: map[Constants.dbFieldUsersProfilePic],
|
||||||
Constants.dbFieldUsersName: map[Constants.dbFieldUsersName],
|
Constants.dbFieldUsersName: map[Constants.dbFieldUsersName],
|
||||||
Constants.dbFieldUsersBio: map[Constants.dbFieldUsersBio],
|
Constants.dbFieldUsersBio: map[Constants.dbFieldUsersBio],
|
||||||
|
Constants.dbFieldUsersUrlFacebook: map[Constants.dbFieldUsersUrlFacebook],
|
||||||
|
Constants.dbFieldUsersUrlLinkedIn: map[Constants.dbFieldUsersUrlLinkedIn],
|
||||||
|
Constants.dbFieldUsersUrlXing: map[Constants.dbFieldUsersUrlXing],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -217,6 +230,32 @@ class EditProfilePageState extends State<EditProfilePage> {
|
||||||
maxLength: 4096,
|
maxLength: 4096,
|
||||||
),
|
),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
|
TextFormField(
|
||||||
|
controller: _urlFbController,
|
||||||
|
decoration:
|
||||||
|
const InputDecoration(labelText: 'Facebook Profile'),
|
||||||
|
validator: (value) {
|
||||||
|
return validateUrlFacebook(value);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
TextFormField(
|
||||||
|
controller: _urlLnController,
|
||||||
|
decoration:
|
||||||
|
const InputDecoration(labelText: 'LinkedIn Profile'),
|
||||||
|
validator: (value) {
|
||||||
|
return validateUrlLinkedIn(value);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
TextFormField(
|
||||||
|
controller: _urlXiController,
|
||||||
|
decoration: const InputDecoration(labelText: 'Xing Profile'),
|
||||||
|
validator: (value) {
|
||||||
|
return validateUrlXing(value);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
ElevatedButton(
|
ElevatedButton(
|
||||||
onPressed: _saveProfile,
|
onPressed: _saveProfile,
|
||||||
child: const Text('Save'),
|
child: const Text('Save'),
|
||||||
|
|
|
@ -104,31 +104,6 @@ class LikedUsersPageState extends State<LikedUsersPage> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<bool?> _showConfirmationDialog(String userId, String userName) async {
|
|
||||||
bool? confirm = await showDialog<bool>(
|
|
||||||
context: context,
|
|
||||||
builder: (BuildContext context) {
|
|
||||||
return AlertDialog(
|
|
||||||
title: const Text('Confirm Removal'),
|
|
||||||
content:
|
|
||||||
Text('Are you sure you want to remove $userName from your list?'),
|
|
||||||
actions: [
|
|
||||||
TextButton(
|
|
||||||
onPressed: () => Navigator.of(context).pop(false),
|
|
||||||
child: const Text('Cancel'),
|
|
||||||
),
|
|
||||||
TextButton(
|
|
||||||
onPressed: () => Navigator.of(context).pop(true),
|
|
||||||
child: const Text('Confirm'),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
return confirm;
|
|
||||||
}
|
|
||||||
|
|
||||||
List<MapEntry<Swipe, DocumentSnapshot>> _getSortedLikedUsersWithSwipes() {
|
List<MapEntry<Swipe, DocumentSnapshot>> _getSortedLikedUsersWithSwipes() {
|
||||||
List<MapEntry<Swipe, DocumentSnapshot>> likedOnlyUsers = [];
|
List<MapEntry<Swipe, DocumentSnapshot>> likedOnlyUsers = [];
|
||||||
List<MapEntry<Swipe, DocumentSnapshot>> matchedUsers = [];
|
List<MapEntry<Swipe, DocumentSnapshot>> matchedUsers = [];
|
||||||
|
@ -320,9 +295,10 @@ class LikedUsersPageState extends State<LikedUsersPage> {
|
||||||
user.data() as Map<String, dynamic>;
|
user.data() as Map<String, dynamic>;
|
||||||
bool hasName = userMap.containsKey(Constants.dbFieldUsersName);
|
bool hasName = userMap.containsKey(Constants.dbFieldUsersName);
|
||||||
|
|
||||||
bool? confirm = await _showConfirmationDialog(
|
bool? confirm = await showConfirmationDialog(
|
||||||
user.id,
|
context,
|
||||||
(hasName ? user[Constants.dbFieldUsersName] : 'Name: n/a'),
|
'Confirm Removal',
|
||||||
|
'Are you sure you want to remove ${(hasName ? user[Constants.dbFieldUsersName] : 'Name: n/a')} from your list?',
|
||||||
);
|
);
|
||||||
|
|
||||||
if (confirm == true) {
|
if (confirm == true) {
|
||||||
|
|
|
@ -2,6 +2,7 @@ import 'package:expandable_text/expandable_text.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:firebase_auth/firebase_auth.dart';
|
import 'package:firebase_auth/firebase_auth.dart';
|
||||||
|
|
||||||
|
import '../components/external_link_widget.dart';
|
||||||
import '../components/language_list.dart';
|
import '../components/language_list.dart';
|
||||||
import '../constants.dart';
|
import '../constants.dart';
|
||||||
import '../enumerations.dart';
|
import '../enumerations.dart';
|
||||||
|
@ -73,6 +74,9 @@ class _UserProfilePageState extends State<UserProfilePage> {
|
||||||
updatedUserData[Constants.dbFieldUsersProfilePic];
|
updatedUserData[Constants.dbFieldUsersProfilePic];
|
||||||
myData.name = updatedUserData[Constants.dbFieldUsersName];
|
myData.name = updatedUserData[Constants.dbFieldUsersName];
|
||||||
myData.bio = updatedUserData[Constants.dbFieldUsersBio];
|
myData.bio = updatedUserData[Constants.dbFieldUsersBio];
|
||||||
|
myData.urlFacebook = updatedUserData[Constants.dbFieldUsersUrlFacebook];
|
||||||
|
myData.urlLinkedIn = updatedUserData[Constants.dbFieldUsersUrlLinkedIn];
|
||||||
|
myData.urlXing = updatedUserData[Constants.dbFieldUsersUrlXing];
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -318,6 +322,12 @@ class _UserProfilePageState extends State<UserProfilePage> {
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
if (isOwner) Text(myData.email, style: const TextStyle(fontSize: 16)),
|
if (isOwner) Text(myData.email, style: const TextStyle(fontSize: 16)),
|
||||||
|
if (myData.urlXing != null && myData.urlXing!.isNotEmpty)
|
||||||
|
ExternalLinkWidget(url: myData.urlXing!, context: context),
|
||||||
|
if (myData.urlLinkedIn != null && myData.urlLinkedIn!.isNotEmpty)
|
||||||
|
ExternalLinkWidget(url: myData.urlLinkedIn!, context: context),
|
||||||
|
if (myData.urlFacebook != null && myData.urlFacebook!.isNotEmpty)
|
||||||
|
ExternalLinkWidget(url: myData.urlFacebook!, context: context),
|
||||||
const SizedBox(height: 32),
|
const SizedBox(height: 32),
|
||||||
Align(
|
Align(
|
||||||
alignment: Alignment.centerLeft,
|
alignment: Alignment.centerLeft,
|
||||||
|
|
|
@ -206,6 +206,9 @@ class UserService {
|
||||||
communication: communication,
|
communication: communication,
|
||||||
workValues: works,
|
workValues: works,
|
||||||
profilePictureUrl: data[Constants.dbFieldUsersProfilePic],
|
profilePictureUrl: data[Constants.dbFieldUsersProfilePic],
|
||||||
|
urlFacebook: data[Constants.dbFieldUsersUrlFacebook],
|
||||||
|
urlLinkedIn: data[Constants.dbFieldUsersUrlLinkedIn],
|
||||||
|
urlXing: data[Constants.dbFieldUsersUrlXing],
|
||||||
bio: data[Constants.dbFieldUsersBio],
|
bio: data[Constants.dbFieldUsersBio],
|
||||||
gender: Gender.values[data[Constants.dbFieldUsersGender] ?? 0],
|
gender: Gender.values[data[Constants.dbFieldUsersGender] ?? 0],
|
||||||
born: data[Constants.dbFieldUsersYearBorn],
|
born: data[Constants.dbFieldUsersYearBorn],
|
||||||
|
|
|
@ -100,3 +100,63 @@ String getDisplayText(dynamic option) {
|
||||||
// Fallback to default toString if not an enum
|
// Fallback to default toString if not an enum
|
||||||
return option.toString().split('.').last;
|
return option.toString().split('.').last;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns null if the given [value] is a valid Facebook Url,
|
||||||
|
/// else an error message is returned.
|
||||||
|
String? validateUrlFacebook(String? value) {
|
||||||
|
if (value == null || value.trim().isEmpty) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (!value.startsWith('http:') && !value.startsWith('https:')) {
|
||||||
|
return 'Link does not start with http(s):';
|
||||||
|
}
|
||||||
|
RegExp regex1 = RegExp(
|
||||||
|
r'^(?:https?://)?(?:www\.)?facebook.com/(?:profile\.php\?id=)?[0-9]+$');
|
||||||
|
RegExp regex2 = RegExp(
|
||||||
|
r'^(?:https?://)?(?:www\.)?(?:facebook|fb)\.com/(?![A-z]+\.php)(?!marketplace|gaming|watch|me|messages|help|search|groups)[A-z0-9_\-.]+/?$');
|
||||||
|
if (!regex1.hasMatch(value) && !regex2.hasMatch(value)) {
|
||||||
|
return 'Unknown or invalid Facebook URL format.';
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns null if the given [value] is a valid LinkedIn Url,
|
||||||
|
/// else an error message is returned.
|
||||||
|
String? validateUrlLinkedIn(String? value) {
|
||||||
|
if (value == null || value.trim().isEmpty) {
|
||||||
|
return null; // ok, nothing to validate
|
||||||
|
}
|
||||||
|
if (!value.startsWith('http:') && !value.startsWith('https:')) {
|
||||||
|
return 'Link does not start with http(s):';
|
||||||
|
}
|
||||||
|
// RegEx source https://github.com/lorey/social-media-profiles-regexs?tab=readme-ov-file#linkedin
|
||||||
|
RegExp regex1 = RegExp(
|
||||||
|
r'^(?:https?://)?(?:\w+\.)?linkedin\.com/(?:(company)|(school))/[A-z0-9-À-ÿ.]+/?$');
|
||||||
|
RegExp regex2 = RegExp(
|
||||||
|
r'^(?:https?://)?(?:\w+\.)?linkedin\.com/feed/update/urn:li:activity:[0-9]+/?$');
|
||||||
|
RegExp regex3 =
|
||||||
|
RegExp(r'^(?:https?://)?(?:\w+\.)?linkedin\.com/in/[\w\-_À-ÿ%]+/?$');
|
||||||
|
if (!regex1.hasMatch(value) &&
|
||||||
|
!regex2.hasMatch(value) &&
|
||||||
|
!regex3.hasMatch(value)) {
|
||||||
|
return 'Unknown or invalid LinkedIn URL format.';
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns null if the given [value] is a valid Xing Url,
|
||||||
|
/// else an error message is returned.
|
||||||
|
String? validateUrlXing(String? value) {
|
||||||
|
if (value == null || value.trim().isEmpty) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (!value.startsWith('http:') && !value.startsWith('https:')) {
|
||||||
|
return 'Link does not start with http(s):';
|
||||||
|
}
|
||||||
|
RegExp regex =
|
||||||
|
RegExp(r'^(?:https?://)?(?:www\.)?xing.com/profile/[A-z0-9-_]+$');
|
||||||
|
if (!regex.hasMatch(value)) {
|
||||||
|
return 'Unknown or invalid Xing URL format.';
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
|
@ -41,3 +41,30 @@ void showErrorSnackBar(BuildContext context, String message) {
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Show a confirmation dialog and return [true] on confirm,
|
||||||
|
/// [false] on cancel, or null otherwise.
|
||||||
|
Future<bool?> showConfirmationDialog(
|
||||||
|
BuildContext context, String title, String content) async {
|
||||||
|
bool? confirm = await showDialog<bool>(
|
||||||
|
context: context,
|
||||||
|
builder: (BuildContext context) {
|
||||||
|
return AlertDialog(
|
||||||
|
title: Text(title),
|
||||||
|
content: Text(content),
|
||||||
|
actions: [
|
||||||
|
TextButton(
|
||||||
|
onPressed: () => Navigator.of(context).pop(false),
|
||||||
|
child: const Text('Cancel'),
|
||||||
|
),
|
||||||
|
TextButton(
|
||||||
|
onPressed: () => Navigator.of(context).pop(true),
|
||||||
|
child: const Text('Confirm'),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
return confirm;
|
||||||
|
}
|
||||||
|
|
64
pubspec.lock
64
pubspec.lock
|
@ -829,6 +829,70 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.3.2"
|
version: "1.3.2"
|
||||||
|
url_launcher:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: url_launcher
|
||||||
|
sha256: "21b704ce5fa560ea9f3b525b43601c678728ba46725bab9b01187b4831377ed3"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "6.3.0"
|
||||||
|
url_launcher_android:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: url_launcher_android
|
||||||
|
sha256: ceb2625f0c24ade6ef6778d1de0b2e44f2db71fded235eb52295247feba8c5cf
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "6.3.3"
|
||||||
|
url_launcher_ios:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: url_launcher_ios
|
||||||
|
sha256: "7068716403343f6ba4969b4173cbf3b84fc768042124bc2c011e5d782b24fe89"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "6.3.0"
|
||||||
|
url_launcher_linux:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: url_launcher_linux
|
||||||
|
sha256: ab360eb661f8879369acac07b6bb3ff09d9471155357da8443fd5d3cf7363811
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "3.1.1"
|
||||||
|
url_launcher_macos:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: url_launcher_macos
|
||||||
|
sha256: "9a1a42d5d2d95400c795b2914c36fdcb525870c752569438e4ebb09a2b5d90de"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "3.2.0"
|
||||||
|
url_launcher_platform_interface:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: url_launcher_platform_interface
|
||||||
|
sha256: "552f8a1e663569be95a8190206a38187b531910283c3e982193e4f2733f01029"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.3.2"
|
||||||
|
url_launcher_web:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: url_launcher_web
|
||||||
|
sha256: "8d9e750d8c9338601e709cd0885f95825086bd8b642547f26bda435aade95d8a"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.3.1"
|
||||||
|
url_launcher_windows:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: url_launcher_windows
|
||||||
|
sha256: ecf9725510600aa2bb6d7ddabe16357691b6d2805f66216a97d1b881e21beff7
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "3.1.1"
|
||||||
uuid:
|
uuid:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
|
@ -30,6 +30,7 @@ dependencies:
|
||||||
expandable_text: ^2.3.0
|
expandable_text: ^2.3.0
|
||||||
shared_preferences: ^2.2.3
|
shared_preferences: ^2.2.3
|
||||||
flutter_launcher_icons: ^0.13.1
|
flutter_launcher_icons: ^0.13.1
|
||||||
|
url_launcher: ^6.3.0
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
|
|
Loading…
Reference in New Issue