Finish to add configuration tutorial
This commit is contained in:
parent
a3b25c3ee1
commit
4e8fd12fdc
9 changed files with 236 additions and 39 deletions
|
@ -1,19 +1,23 @@
|
||||||
|
import 'package:desktopapp/widgets/pages/home.dart';
|
||||||
import 'package:desktopapp/widgets/pages/tutorial.dart';
|
import 'package:desktopapp/widgets/pages/tutorial.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
enum Routes { streamlabsTutorial }
|
enum Routes { streamlabsTutorial, home }
|
||||||
|
|
||||||
extension on Routes {
|
extension RoutesExtension on Routes {
|
||||||
String get path {
|
String get path {
|
||||||
switch (this) {
|
switch (this) {
|
||||||
case Routes.streamlabsTutorial:
|
case Routes.streamlabsTutorial:
|
||||||
return '/streamlabs/tutorial';
|
return '/streamlabs/tutorial';
|
||||||
|
case Routes.home:
|
||||||
|
return '/home';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var routes = <String, WidgetBuilder>{
|
var routes = <String, WidgetBuilder>{
|
||||||
Routes.streamlabsTutorial.path: (context) => const TutorialPage(),
|
Routes.streamlabsTutorial.path: (context) => TutorialPage(),
|
||||||
|
Routes.home.path: (context) => const HomePage(),
|
||||||
};
|
};
|
||||||
|
|
||||||
var initialRoute = Routes.streamlabsTutorial.path;
|
var initialRoute = Routes.streamlabsTutorial.path;
|
||||||
|
|
30
lib/utils/logger.dart
Normal file
30
lib/utils/logger.dart
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
|
|
||||||
|
enum LoggerType { info, warning, error }
|
||||||
|
|
||||||
|
class Logger {
|
||||||
|
static void log(LoggerType logType, Object? instance, String message) {
|
||||||
|
if (kDebugMode) {
|
||||||
|
String className;
|
||||||
|
if (instance is Type) {
|
||||||
|
className = instance.toString();
|
||||||
|
} else {
|
||||||
|
className = instance?.runtimeType.toString() ?? 'GLOBAL';
|
||||||
|
}
|
||||||
|
String cat;
|
||||||
|
switch (logType) {
|
||||||
|
case LoggerType.info:
|
||||||
|
cat = 'INFO';
|
||||||
|
break;
|
||||||
|
case LoggerType.warning:
|
||||||
|
cat = 'WARNING';
|
||||||
|
break;
|
||||||
|
case LoggerType.error:
|
||||||
|
cat = 'ERROR';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
// ignore: avoid_print
|
||||||
|
print('[$cat][$className] $message');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,7 +2,9 @@ import 'package:desktopapp/widgets/components/link.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
class Step1 extends StatelessWidget {
|
class Step1 extends StatelessWidget {
|
||||||
const Step1({Key? key}) : super(key: key);
|
const Step1({Key? key, required this.onNext}) : super(key: key);
|
||||||
|
|
||||||
|
final VoidCallback onNext;
|
||||||
|
|
||||||
Widget buildFieldDescription(String field, String description) {
|
Widget buildFieldDescription(String field, String description) {
|
||||||
return Row(
|
return Row(
|
||||||
|
@ -60,7 +62,7 @@ class Step1 extends StatelessWidget {
|
||||||
child: Row(
|
child: Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||||
children: [
|
children: [
|
||||||
TextButton(onPressed: () {}, child: const Text('Étape suivante'))
|
TextButton(onPressed: onNext, child: const Text('Étape suivante'))
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
|
@ -4,7 +4,11 @@ import 'package:flutter/material.dart';
|
||||||
import 'package:shared_preferences/shared_preferences.dart';
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
|
|
||||||
class Step2 extends StatelessWidget {
|
class Step2 extends StatelessWidget {
|
||||||
Step2({Key? key}) : super(key: key);
|
Step2({Key? key, required this.onPrevious, required this.onNext})
|
||||||
|
: super(key: key);
|
||||||
|
|
||||||
|
final VoidCallback onPrevious;
|
||||||
|
final VoidCallback onNext;
|
||||||
|
|
||||||
final TextEditingController _clientIdController = TextEditingController();
|
final TextEditingController _clientIdController = TextEditingController();
|
||||||
final TextEditingController _clientSecretController = TextEditingController();
|
final TextEditingController _clientSecretController = TextEditingController();
|
||||||
|
@ -86,8 +90,8 @@ class Step2 extends StatelessWidget {
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||||
children: [
|
children: [
|
||||||
TextButton(
|
TextButton(
|
||||||
onPressed: () {}, child: const Text('Étape précédente')),
|
onPressed: onPrevious, child: const Text('Étape précédente')),
|
||||||
TextButton(onPressed: () {}, child: const Text('Étape suivante'))
|
TextButton(onPressed: onNext, child: const Text('Étape suivante'))
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,30 +1,45 @@
|
||||||
|
import 'dart:convert';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
|
import 'package:desktopapp/classes/routes.dart';
|
||||||
|
import 'package:desktopapp/utils/logger.dart';
|
||||||
|
import 'package:dio/dio.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:shared_preferences/shared_preferences.dart';
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
import 'package:desktopapp/widgets/components/link.dart' as link;
|
import 'package:url_launcher/url_launcher.dart';
|
||||||
|
|
||||||
class Step3 extends StatelessWidget {
|
class Step3 extends StatelessWidget {
|
||||||
const Step3({Key? key}) : super(key: key);
|
const Step3({Key? key, required this.onPrevious}) : super(key: key);
|
||||||
|
|
||||||
|
final VoidCallback onPrevious;
|
||||||
|
|
||||||
Widget buildContent(BuildContext context, SharedPreferences prefs) {
|
Widget buildContent(BuildContext context, SharedPreferences prefs) {
|
||||||
var clientId = prefs.getString('client_id');
|
|
||||||
return Column(children: [
|
return Column(children: [
|
||||||
const Padding(
|
const Padding(
|
||||||
padding: EdgeInsets.symmetric(vertical: 20),
|
padding: EdgeInsets.symmetric(vertical: 20),
|
||||||
child: Text('Étape 3:Authorisation de l\'application',
|
child: Text('Étape 3: Autorisation de l\'application',
|
||||||
style: TextStyle(fontSize: 20)),
|
style: TextStyle(fontSize: 20)),
|
||||||
),
|
),
|
||||||
link.Link(
|
const Text(
|
||||||
uri:
|
'Pour terminer la configuration, il reste plus qu\'à se connecter à Streamlabs pour autoriser l\'application à créer des alertes.'),
|
||||||
'https://streamlabs.com/api/v1.0/authorize?redirect_uri=http://localhost:1234/&client_id=$clientId&response_type=code&scope=alerts.create',
|
const SizedBox(height: 10),
|
||||||
label: 'Cliquez ici pour vous connecter à Streamlabs',
|
TextButton(
|
||||||
)
|
onPressed: () async {
|
||||||
|
waitAuthorization(context, prefs);
|
||||||
|
var clientId = prefs.getString('client_id');
|
||||||
|
|
||||||
|
await Future.delayed(const Duration(seconds: 1));
|
||||||
|
launch(
|
||||||
|
'https://streamlabs.com/api/v1.0/authorize?redirect_uri=http://localhost:1234/&client_id=$clientId&response_type=code&scope=alerts.create');
|
||||||
|
},
|
||||||
|
child: const Text('Cliquez ici pour vous connecter à Streamlabs')),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future waitAuthorizationCallback() async {
|
Future<String?> waitAuthorizationCallback(SharedPreferences prefs) async {
|
||||||
var server = await HttpServer.bind(InternetAddress.loopbackIPv4, 1214);
|
var server = await HttpServer.bind(InternetAddress.loopbackIPv4, 1234);
|
||||||
|
Logger.log(LoggerType.info, this,
|
||||||
|
'Server started at ${server.address.address}:${server.port}');
|
||||||
var request = await server.first;
|
var request = await server.first;
|
||||||
|
|
||||||
var uri = Uri.dataFromString(request.requestedUri.toString());
|
var uri = Uri.dataFromString(request.requestedUri.toString());
|
||||||
|
@ -32,30 +47,116 @@ class Step3 extends StatelessWidget {
|
||||||
var code = query['code'];
|
var code = query['code'];
|
||||||
|
|
||||||
if (code != null) {
|
if (code != null) {
|
||||||
var prefs = await SharedPreferences.getInstance();
|
|
||||||
prefs.setString('code', code);
|
prefs.setString('code', code);
|
||||||
|
|
||||||
|
request.response
|
||||||
|
..headers.contentType = ContentType.text
|
||||||
|
..write('You can close tab now')
|
||||||
|
..close();
|
||||||
|
} else {
|
||||||
|
request.response
|
||||||
|
..headers.contentType = ContentType.text
|
||||||
|
..write('Internal error')
|
||||||
|
..close();
|
||||||
}
|
}
|
||||||
|
|
||||||
request.response
|
return code;
|
||||||
..headers.contentType = ContentType.text
|
}
|
||||||
..write('You can close tab now')
|
|
||||||
..close();
|
|
||||||
|
|
||||||
// SEND TOKEN ASK TO API
|
Future<void> waitAuthorization(
|
||||||
// POST https://streamlabs.com/api/v1.0/token
|
BuildContext context, SharedPreferences prefs) async {
|
||||||
// {
|
var code = await waitAuthorizationCallback(prefs);
|
||||||
// "grant_type": "authorization_code",
|
|
||||||
// "code": "CODE",
|
if (code != null) {
|
||||||
// "redirect_uri": "http://localhost:1234/",
|
if (prefs.containsKey('credentials')) {
|
||||||
// "client_id": "CLIENT_ID",
|
showDialog(
|
||||||
// "client_secret": "CLIENT_SECRET"
|
context: context,
|
||||||
// }
|
builder: (_) => AlertDialog(
|
||||||
// SAVE RESPONSE TOKEN AS CREDENTIALS IN SHARED PREFERENCES
|
title: const Text('Info'),
|
||||||
|
content: const Text(
|
||||||
|
'Connexion réussi. Appuyez sur OK pour acceder à l\'application.'),
|
||||||
|
actions: [
|
||||||
|
TextButton(
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.pop(context);
|
||||||
|
Navigator.of(context)
|
||||||
|
.pushReplacementNamed(Routes.home.path);
|
||||||
|
},
|
||||||
|
child: const Text('OK'))
|
||||||
|
],
|
||||||
|
));
|
||||||
|
} else {
|
||||||
|
Dio dio = Dio(BaseOptions(
|
||||||
|
contentType: Headers.jsonContentType,
|
||||||
|
responseType: ResponseType.json,
|
||||||
|
validateStatus: (_) => true));
|
||||||
|
|
||||||
|
var data = jsonEncode({
|
||||||
|
'grant_type': 'authorization_code',
|
||||||
|
'client_id': prefs.getString('client_id'),
|
||||||
|
'client_secret': prefs.getString('client_secret'),
|
||||||
|
'redirect_uri': 'http://localhost:1234/',
|
||||||
|
'code': code,
|
||||||
|
});
|
||||||
|
|
||||||
|
var response =
|
||||||
|
await dio.post('https://streamlabs.com/api/v1.0/token', data: data);
|
||||||
|
|
||||||
|
if (response.statusCode == HttpStatus.ok) {
|
||||||
|
prefs.setString('credentials', response.data.toString());
|
||||||
|
Logger.log(LoggerType.info, this,
|
||||||
|
'Authorization success : ${response.data}');
|
||||||
|
showDialog(
|
||||||
|
context: context,
|
||||||
|
builder: (_) => AlertDialog(
|
||||||
|
title: const Text('Info'),
|
||||||
|
content: const Text(
|
||||||
|
'Connexion réussi. Appuyez sur OK pour acceder à l\'application.'),
|
||||||
|
actions: [
|
||||||
|
TextButton(
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.pop(context);
|
||||||
|
Navigator.of(context)
|
||||||
|
.pushReplacementNamed(Routes.home.path);
|
||||||
|
},
|
||||||
|
child: const Text('OK'))
|
||||||
|
],
|
||||||
|
));
|
||||||
|
} else {
|
||||||
|
Logger.log(LoggerType.error, this,
|
||||||
|
'Authorization error : ${response.data} with $data and ${response.requestOptions.headers}');
|
||||||
|
showDialog(
|
||||||
|
context: context,
|
||||||
|
builder: (_) => AlertDialog(
|
||||||
|
title: const Text('Erreur'),
|
||||||
|
content: const Text('Impossible de se connecter.'),
|
||||||
|
actions: [
|
||||||
|
TextButton(
|
||||||
|
onPressed: () => Navigator.pop(context),
|
||||||
|
child: const Text('OK'))
|
||||||
|
],
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Logger.log(LoggerType.error, this,
|
||||||
|
'Authorization error : impossible to get code');
|
||||||
|
showDialog(
|
||||||
|
context: context,
|
||||||
|
builder: (_) => AlertDialog(
|
||||||
|
title: const Text('Erreur'),
|
||||||
|
content: const Text('Impossible de se connecter.'),
|
||||||
|
actions: [
|
||||||
|
TextButton(
|
||||||
|
onPressed: () => Navigator.pop(context),
|
||||||
|
child: const Text('OK'))
|
||||||
|
],
|
||||||
|
));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
waitAuthorizationCallback();
|
|
||||||
return Column(
|
return Column(
|
||||||
children: [
|
children: [
|
||||||
Expanded(
|
Expanded(
|
||||||
|
@ -76,8 +177,7 @@ class Step3 extends StatelessWidget {
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||||
children: [
|
children: [
|
||||||
TextButton(
|
TextButton(
|
||||||
onPressed: () {}, child: const Text('Étape précédente')),
|
onPressed: onPrevious, child: const Text('Étape précédente'))
|
||||||
TextButton(onPressed: () {}, child: const Text('Étape suivante'))
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
10
lib/widgets/pages/home.dart
Normal file
10
lib/widgets/pages/home.dart
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
class HomePage extends StatelessWidget {
|
||||||
|
const HomePage({Key? key}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return const Scaffold(body: Text('Test'));
|
||||||
|
}
|
||||||
|
}
|
|
@ -4,10 +4,42 @@ import 'package:desktopapp/widgets/components/tutorials/step3.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
class TutorialPage extends StatelessWidget {
|
class TutorialPage extends StatelessWidget {
|
||||||
const TutorialPage({Key? key}) : super(key: key);
|
TutorialPage({Key? key}) : super(key: key);
|
||||||
|
|
||||||
|
final PageController videoPageController = PageController(
|
||||||
|
initialPage: 0,
|
||||||
|
viewportFraction: 1,
|
||||||
|
);
|
||||||
|
|
||||||
|
void onPrevious() {
|
||||||
|
videoPageController.previousPage(
|
||||||
|
duration: const Duration(milliseconds: 300),
|
||||||
|
curve: Curves.easeIn,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void onNext() {
|
||||||
|
videoPageController.nextPage(
|
||||||
|
duration: const Duration(milliseconds: 300),
|
||||||
|
curve: Curves.easeIn,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(body: Step3());
|
return Scaffold(
|
||||||
|
body: CustomScrollView(
|
||||||
|
controller: videoPageController,
|
||||||
|
scrollDirection: Axis.horizontal,
|
||||||
|
physics: const PageScrollPhysics(),
|
||||||
|
slivers: [
|
||||||
|
SliverFillViewport(
|
||||||
|
delegate: SliverChildListDelegate([
|
||||||
|
Step1(onNext: onNext),
|
||||||
|
Step2(onPrevious: onPrevious, onNext: onNext),
|
||||||
|
Step3(onPrevious: onPrevious),
|
||||||
|
])),
|
||||||
|
],
|
||||||
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
14
pubspec.lock
14
pubspec.lock
|
@ -57,6 +57,13 @@ packages:
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.0.4"
|
version: "1.0.4"
|
||||||
|
dio:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: dio
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "4.0.6"
|
||||||
fake_async:
|
fake_async:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -114,6 +121,13 @@ packages:
|
||||||
description: flutter
|
description: flutter
|
||||||
source: sdk
|
source: sdk
|
||||||
version: "0.0.0"
|
version: "0.0.0"
|
||||||
|
http_parser:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: http_parser
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "4.0.0"
|
||||||
js:
|
js:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
|
@ -38,6 +38,7 @@ dependencies:
|
||||||
url_launcher: ^6.0.20
|
url_launcher: ^6.0.20
|
||||||
shared_preferences: ^2.0.13
|
shared_preferences: ^2.0.13
|
||||||
flash: ^2.0.3+2
|
flash: ^2.0.3+2
|
||||||
|
dio: ^4.0.6
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
|
|
Loading…
Reference in a new issue