API authentication
All checks were successful
ci/woodpecker/push/flutterBuild Pipeline was successful
All checks were successful
ci/woodpecker/push/flutterBuild Pipeline was successful
This commit is contained in:
parent
4cd006be85
commit
ec02aaa406
7 changed files with 320 additions and 19 deletions
112
lib/main.dart
112
lib/main.dart
|
@ -1,6 +1,10 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:gitea_client/model/user.dart';
|
||||||
|
import 'package:gitea_client/service/AuthenticationChecker.dart';
|
||||||
import 'package:url_launcher/url_launcher.dart';
|
import 'package:url_launcher/url_launcher.dart';
|
||||||
|
|
||||||
|
import 'model/ApiAccess.dart';
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
runApp(const MyApp());
|
runApp(const MyApp());
|
||||||
}
|
}
|
||||||
|
@ -17,6 +21,22 @@ class MyApp extends StatelessWidget {
|
||||||
primarySwatch: Colors.lightGreen,
|
primarySwatch: Colors.lightGreen,
|
||||||
),
|
),
|
||||||
home: const LoginPage(title: 'Gitea client'),
|
home: const LoginPage(title: 'Gitea client'),
|
||||||
|
routes: {
|
||||||
|
|
||||||
|
},
|
||||||
|
onGenerateRoute: (route) {
|
||||||
|
switch (route.name) { // <- here
|
||||||
|
case "/loginstatus":
|
||||||
|
return MaterialPageRoute(
|
||||||
|
settings: const RouteSettings(name: "/parameterpage"),
|
||||||
|
builder: (context) => StatefulLoginStatus(
|
||||||
|
apiAccess: route.arguments as ApiAccess,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -60,7 +80,8 @@ class StatefulLoginForm extends StatefulWidget {
|
||||||
}
|
}
|
||||||
|
|
||||||
class _LoginForm extends State<StatefulLoginForm> {
|
class _LoginForm extends State<StatefulLoginForm> {
|
||||||
final Uri getTokenUri = Uri.parse("https://www.jetbrains.com/help/youtrack/incloud/integration-with-gitea.html#enable-youtrack-integration-gitea");
|
final Uri getTokenUri = Uri.parse(
|
||||||
|
"https://www.jetbrains.com/help/youtrack/incloud/integration-with-gitea.html#enable-youtrack-integration-gitea");
|
||||||
final tokenController = TextEditingController();
|
final tokenController = TextEditingController();
|
||||||
final instanceController = TextEditingController();
|
final instanceController = TextEditingController();
|
||||||
String instance = "gitea.com";
|
String instance = "gitea.com";
|
||||||
|
@ -76,7 +97,7 @@ class _LoginForm extends State<StatefulLoginForm> {
|
||||||
children: [
|
children: [
|
||||||
DropdownButton(
|
DropdownButton(
|
||||||
value: instance,
|
value: instance,
|
||||||
items: <String>['gitea.com', 'codeberg.com', 'Other']
|
items: <String>['gitea.com', 'codeberg.org', 'Other']
|
||||||
.map<DropdownMenuItem<String>>((String value) {
|
.map<DropdownMenuItem<String>>((String value) {
|
||||||
return DropdownMenuItem<String>(
|
return DropdownMenuItem<String>(
|
||||||
value: value,
|
value: value,
|
||||||
|
@ -105,26 +126,89 @@ class _LoginForm extends State<StatefulLoginForm> {
|
||||||
obscureText: true,
|
obscureText: true,
|
||||||
),
|
),
|
||||||
SizedBox(
|
SizedBox(
|
||||||
width: (media.width > 600) ? media.width * 0.25 : media.width * 0.5,
|
width:
|
||||||
|
(media.width > 600) ? media.width * 0.25 : media.width * 0.5,
|
||||||
child: Container(
|
child: Container(
|
||||||
padding: EdgeInsetsDirectional.all(15),
|
padding: EdgeInsetsDirectional.all(15),
|
||||||
child: ElevatedButton(
|
child: ElevatedButton(
|
||||||
child: const Text("Login"),
|
child: const Text("Login"),
|
||||||
onPressed: () => {
|
onPressed: () => {
|
||||||
|
Navigator.pushNamed(context, "/loginstatus",
|
||||||
},
|
arguments: ApiAccess(
|
||||||
),
|
(instance == "Other")
|
||||||
)),
|
? instanceController.text
|
||||||
|
: instance,
|
||||||
|
tokenController.text))
|
||||||
|
},
|
||||||
|
),
|
||||||
|
)),
|
||||||
//
|
//
|
||||||
TextButton(onPressed: () => {
|
TextButton(
|
||||||
_launchUrl(getTokenUri)
|
onPressed: () => {_launchUrl(getTokenUri)},
|
||||||
}, child: Text("Need a Token? Find out how to generate one!"))
|
child: Text("Need a Token? Find out how to generate one!"))
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class StatefulLoginStatus extends StatefulWidget {
|
||||||
|
final ApiAccess apiAccess;
|
||||||
|
const StatefulLoginStatus({Key? key, required this.apiAccess}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
_StatefulLoginStatus createState() => _StatefulLoginStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _StatefulLoginStatus extends State<StatefulLoginStatus> {
|
||||||
|
|
||||||
|
|
||||||
|
Future<AuthenticatedUser>? userRequest;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
userRequest = AuthenticationChecker(widget.apiAccess).getAuthenticatedUserOrError();
|
||||||
|
super.initState();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Scaffold(
|
||||||
|
appBar: AppBar(
|
||||||
|
title: const Text("Login status"),
|
||||||
|
),
|
||||||
|
body: Center(
|
||||||
|
child: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
FutureBuilder<AuthenticatedUser>(
|
||||||
|
future: userRequest,
|
||||||
|
builder: (context, snapshot) {
|
||||||
|
if (snapshot.hasError){
|
||||||
|
return Center(
|
||||||
|
child: Text(
|
||||||
|
"Hiba történt: ${snapshot.error}"
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} else if (snapshot.hasData){
|
||||||
|
var user = snapshot.data!;
|
||||||
|
final username = user.username;
|
||||||
|
return Text("Logged in as $username");
|
||||||
|
|
||||||
|
} else {
|
||||||
|
return Center(
|
||||||
|
child: CircularProgressIndicator(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void _launchUrl(Uri url) async {
|
void _launchUrl(Uri url) async {
|
||||||
if (!await launchUrl(url)) throw 'Could not launch $url';
|
if (!await launchUrl(url)) throw 'Could not launch $url';
|
||||||
}
|
}
|
||||||
|
|
6
lib/model/ApiAccess.dart
Normal file
6
lib/model/ApiAccess.dart
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
class ApiAccess{
|
||||||
|
final String instance;
|
||||||
|
final String token;
|
||||||
|
|
||||||
|
const ApiAccess(this.instance, this.token);
|
||||||
|
}
|
|
@ -1,3 +1,5 @@
|
||||||
|
import 'dart:convert';
|
||||||
|
|
||||||
class AuthenticatedUser {
|
class AuthenticatedUser {
|
||||||
int? id;
|
int? id;
|
||||||
String? login;
|
String? login;
|
||||||
|
|
21
lib/service/AuthenticationChecker.dart
Normal file
21
lib/service/AuthenticationChecker.dart
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
import 'package:gitea_client/model/ApiAccess.dart';
|
||||||
|
import 'package:gitea_client/service/gitea_service.dart';
|
||||||
|
|
||||||
|
import '../model/user.dart';
|
||||||
|
|
||||||
|
class AuthenticationChecker {
|
||||||
|
final ApiAccess apiAccess;
|
||||||
|
late GiteaServie service;
|
||||||
|
AuthenticationChecker(this.apiAccess) {
|
||||||
|
service = GiteaServie(apiAccess.instance, apiAccess.token);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<AuthenticatedUser> getAuthenticatedUserOrError() async{
|
||||||
|
final user = await service.getAuthenticatedUser();
|
||||||
|
if(user.username != null) {
|
||||||
|
return user;
|
||||||
|
}
|
||||||
|
throw Exception("Authentication failed");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
25
lib/service/gitea_service.dart
Normal file
25
lib/service/gitea_service.dart
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
import 'dart:convert';
|
||||||
|
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
|
import 'package:gitea_client/model/user.dart';
|
||||||
|
import 'package:http/http.dart' as http;
|
||||||
|
|
||||||
|
AuthenticatedUser _parseAuthenticatedUserResponse(String message){
|
||||||
|
return AuthenticatedUser.fromJson(jsonDecode(message));
|
||||||
|
}
|
||||||
|
|
||||||
|
class GiteaServie {
|
||||||
|
final String apiUrl;
|
||||||
|
final String _token;
|
||||||
|
|
||||||
|
const GiteaServie(this.apiUrl, this._token);
|
||||||
|
|
||||||
|
Future<AuthenticatedUser> getAuthenticatedUser() async {
|
||||||
|
var response = await http.get(
|
||||||
|
Uri.https(apiUrl, "api/v1/user", {
|
||||||
|
"token": _token,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
return compute(_parseAuthenticatedUserResponse, response.body);
|
||||||
|
}
|
||||||
|
}
|
163
pubspec.lock
163
pubspec.lock
|
@ -1,6 +1,27 @@
|
||||||
# Generated by pub
|
# Generated by pub
|
||||||
# See https://dart.dev/tools/pub/glossary#lockfile
|
# See https://dart.dev/tools/pub/glossary#lockfile
|
||||||
packages:
|
packages:
|
||||||
|
_fe_analyzer_shared:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: _fe_analyzer_shared
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "39.0.0"
|
||||||
|
analyzer:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: analyzer
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "4.0.0"
|
||||||
|
args:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: args
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "2.3.0"
|
||||||
async:
|
async:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -15,6 +36,20 @@ packages:
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.0"
|
version: "2.1.0"
|
||||||
|
build:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: build
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "2.3.0"
|
||||||
|
build_config:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: build_config
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "1.0.0"
|
||||||
characters:
|
characters:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -29,6 +64,13 @@ packages:
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.3.1"
|
version: "1.3.1"
|
||||||
|
checked_yaml:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: checked_yaml
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "2.0.1"
|
||||||
clock:
|
clock:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -43,6 +85,20 @@ packages:
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.15.0"
|
version: "1.15.0"
|
||||||
|
convert:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: convert
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "3.0.1"
|
||||||
|
crypto:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: crypto
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "3.0.2"
|
||||||
cupertino_icons:
|
cupertino_icons:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
@ -50,6 +106,13 @@ packages:
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.0.4"
|
version: "1.0.4"
|
||||||
|
dart_style:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: dart_style
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "2.2.3"
|
||||||
fake_async:
|
fake_async:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -57,6 +120,13 @@ packages:
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.2.0"
|
version: "1.2.0"
|
||||||
|
file:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: file
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "6.1.2"
|
||||||
flutter:
|
flutter:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description: flutter
|
description: flutter
|
||||||
|
@ -84,6 +154,27 @@ packages:
|
||||||
description: flutter
|
description: flutter
|
||||||
source: sdk
|
source: sdk
|
||||||
version: "0.0.0"
|
version: "0.0.0"
|
||||||
|
glob:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: glob
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "2.0.2"
|
||||||
|
http:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: http
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "0.13.4"
|
||||||
|
http_parser:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: http_parser
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "4.0.0"
|
||||||
intl:
|
intl:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
@ -98,6 +189,20 @@ packages:
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.6.3"
|
version: "0.6.3"
|
||||||
|
json_annotation:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: json_annotation
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "4.5.0"
|
||||||
|
json_serializable:
|
||||||
|
dependency: "direct dev"
|
||||||
|
description:
|
||||||
|
name: json_serializable
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "6.2.0"
|
||||||
lints:
|
lints:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -105,6 +210,13 @@ packages:
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.0.1"
|
version: "1.0.1"
|
||||||
|
logging:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: logging
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "1.0.2"
|
||||||
matcher:
|
matcher:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -126,6 +238,13 @@ packages:
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.7.0"
|
version: "1.7.0"
|
||||||
|
package_config:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: package_config
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "2.0.2"
|
||||||
path:
|
path:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -140,11 +259,39 @@ packages:
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.2"
|
version: "2.1.2"
|
||||||
|
pub_semver:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: pub_semver
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "2.1.1"
|
||||||
|
pubspec_parse:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: pubspec_parse
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "1.2.0"
|
||||||
sky_engine:
|
sky_engine:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description: flutter
|
description: flutter
|
||||||
source: sdk
|
source: sdk
|
||||||
version: "0.0.99"
|
version: "0.0.99"
|
||||||
|
source_gen:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: source_gen
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "1.2.2"
|
||||||
|
source_helper:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: source_helper
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "1.3.2"
|
||||||
source_span:
|
source_span:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -195,7 +342,7 @@ packages:
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.3.0"
|
version: "1.3.0"
|
||||||
url_launcher:
|
url_launcher:
|
||||||
dependency: "direct dev"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: url_launcher
|
name: url_launcher
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
|
@ -257,6 +404,20 @@ packages:
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.1"
|
version: "2.1.1"
|
||||||
|
watcher:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: watcher
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "1.0.1"
|
||||||
|
yaml:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: yaml
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "3.1.0"
|
||||||
sdks:
|
sdks:
|
||||||
dart: ">=2.16.2 <3.0.0"
|
dart: ">=2.16.2 <3.0.0"
|
||||||
flutter: ">=2.10.0"
|
flutter: ">=2.10.0"
|
||||||
|
|
10
pubspec.yaml
10
pubspec.yaml
|
@ -32,8 +32,10 @@ dependencies:
|
||||||
flutter_localizations:
|
flutter_localizations:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
intl: ^0.17.0
|
intl: ^0.17.0
|
||||||
|
# For opening links in the browser
|
||||||
|
url_launcher: ^6.1.0
|
||||||
|
# For http communication
|
||||||
|
http: ^0.13.4
|
||||||
# The following adds the Cupertino Icons font to your application.
|
# The following adds the Cupertino Icons font to your application.
|
||||||
# Use with the CupertinoIcons class for iOS style icons.
|
# Use with the CupertinoIcons class for iOS style icons.
|
||||||
cupertino_icons: ^1.0.2
|
cupertino_icons: ^1.0.2
|
||||||
|
@ -41,14 +43,14 @@ dependencies:
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
|
json_serializable: ^6.2.0
|
||||||
# The "flutter_lints" package below contains a set of recommended lints to
|
# The "flutter_lints" package below contains a set of recommended lints to
|
||||||
# encourage good coding practices. The lint set provided by the package is
|
# encourage good coding practices. The lint set provided by the package is
|
||||||
# activated in the `analysis_options.yaml` file located at the root of your
|
# activated in the `analysis_options.yaml` file located at the root of your
|
||||||
# package. See that file for information about deactivating specific lint
|
# package. See that file for information about deactivating specific lint
|
||||||
# rules and activating additional ones.
|
# rules and activating additional ones.
|
||||||
flutter_lints: ^1.0.0
|
flutter_lints: ^1.0.0
|
||||||
url_launcher: ^6.1.0
|
|
||||||
# For information on the generic Dart part of this file, see the
|
# For information on the generic Dart part of this file, see the
|
||||||
# following page: https://dart.dev/tools/pub/pubspec
|
# following page: https://dart.dev/tools/pub/pubspec
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue