From b715a99d414b419ae85b502c3ff3e48c187fd3dd Mon Sep 17 00:00:00 2001 From: Arthi-chaud Date: Tue, 22 Feb 2022 10:11:54 +0100 Subject: [PATCH 001/109] Mobile Client: Default connection state of Api is 'not connected' --- mobile/lib/src/aeris_api.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mobile/lib/src/aeris_api.dart b/mobile/lib/src/aeris_api.dart index 5abf6fe..e6eec2d 100644 --- a/mobile/lib/src/aeris_api.dart +++ b/mobile/lib/src/aeris_api.dart @@ -9,7 +9,7 @@ import 'package:aeris/src/models/trigger.dart'; /// Call to interact with Aeris' Back end class AerisAPI { ///TODO set status based on stored credentials - bool connected = true; + bool connected = false; late List fakeAPI; AerisAPI() { From 9b1d8c4e2a03c5ffbf8166df2f78319a667552fa Mon Sep 17 00:00:00 2001 From: Arthi-chaud Date: Tue, 22 Feb 2022 10:13:56 +0100 Subject: [PATCH 002/109] Mobile Client: some more iOS garbage --- mobile/ios/Runner.xcodeproj/project.pbxproj | 2 +- .../ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/mobile/ios/Runner.xcodeproj/project.pbxproj b/mobile/ios/Runner.xcodeproj/project.pbxproj index 5ee3094..9a1f78e 100644 --- a/mobile/ios/Runner.xcodeproj/project.pbxproj +++ b/mobile/ios/Runner.xcodeproj/project.pbxproj @@ -538,4 +538,4 @@ /* End XCConfigurationList section */ }; rootObject = 97C146E61CF9000F007C117D /* Project object */; -} \ No newline at end of file +} diff --git a/mobile/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/mobile/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index c87d15a..6dd6010 100644 --- a/mobile/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/mobile/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -1,6 +1,6 @@ Date: Tue, 22 Feb 2022 11:26:17 +0100 Subject: [PATCH 003/109] Mobile Client: Buffing AerisAPI to make calls --- mobile/lib/src/aeris_api.dart | 77 +++++++++++++++++++++++++++- mobile/lib/src/main.dart | 3 +- mobile/lib/src/views/login_page.dart | 6 +-- mobile/pubspec.lock | 4 +- mobile/pubspec.yaml | 2 + 5 files changed, 85 insertions(+), 7 deletions(-) diff --git a/mobile/lib/src/aeris_api.dart b/mobile/lib/src/aeris_api.dart index e6eec2d..538df1d 100644 --- a/mobile/lib/src/aeris_api.dart +++ b/mobile/lib/src/aeris_api.dart @@ -1,10 +1,16 @@ import 'dart:async'; +import 'dart:io'; import 'package:aeris/src/models/action.dart'; import 'package:aeris/src/models/action_template.dart'; import 'package:aeris/src/models/pipeline.dart'; import 'package:aeris/src/models/reaction.dart'; import 'package:aeris/src/models/service.dart'; import 'package:aeris/src/models/trigger.dart'; +import 'package:path_provider/path_provider.dart'; +import 'package:http/http.dart' as http; + +/// Requests types supported by Aeris API +enum AerisAPIRequestType { get, post, put, delete } /// Call to interact with Aeris' Back end class AerisAPI { @@ -12,6 +18,12 @@ class AerisAPI { bool connected = false; late List fakeAPI; + /// JWT token used to request API + late String jwt; + + ///TODO Use .env + String baseRoute = 'http://aeris.com'; + AerisAPI() { var trigger1 = Trigger( service: const Service.spotify(), @@ -30,7 +42,9 @@ class AerisAPI { var reaction2 = Reaction( service: const Service.gmail(), parameters: {}, name: "Do smth"); var reaction1 = Reaction( - service: const Service.youtube(), parameters: {}, name: "Do smth youtube"); + service: const Service.youtube(), + parameters: {}, + name: "Do smth youtube"); var pipeline1 = Pipeline( id: 10, name: "My Action", @@ -65,6 +79,47 @@ class AerisAPI { ]; } + /// Name of the file that contains the JWT used for Aeris' API requestd + static const String jwtFile = 'aeris_jwt.txt'; + + /// Retrieves the file containing the JWT + Future getJWTFile() async { + final directory = await getApplicationDocumentsDirectory(); + final path = directory.path; + return File('$path/$jwtFile.txt'); + } + + /// Registers new user in the database and connects it + Future signUpUser(String username, String password) async {} + + /// On success, sets API as connected to given user + Future createConnection(String username, String password) async {} + + /// Create an API connection using previously created credentials + Future restoreConnection() async { + try { + final file = await getJWTFile(); + final cred = await file.readAsString(); + if (cred == "") { + throw Exception("Empty creds"); + } + jwt = cred; + connected = true; + } catch (e) { + return; + } + } + + /// Delete JWT file and disconnect from API + Future stopConnection() async { + File credentials = await getJWTFile(); + + if (credentials.existsSync()) { + await credentials.delete(); + } + connected = false; + } + /// Adds new pipeline to API Future createPipeline(Pipeline newPipeline) async { ///TODO Send Pipeline to API @@ -124,4 +179,24 @@ class AerisAPI { parameters: {'key1': 'value1', 'key2': 'value2'}) ]; } + + /// Encodes Uri for request + Uri _encoreUri(String route) { + return Uri.parse('$baseRoute$route'); + } + + /// Calls API using a HTTP request type, a route and body + Future _requestAPI( + String route, AerisAPIRequestType requestType, Object? body) async { + switch (requestType) { + case AerisAPIRequestType.delete: + return await http.delete(_encoreUri(route), body: body); + case AerisAPIRequestType.get: + return await http.get(_encoreUri(route)); + case AerisAPIRequestType.post: + return await http.post(_encoreUri(route), body: body); + case AerisAPIRequestType.put: + return await http.put(_encoreUri(route), body: body); + } + } } diff --git a/mobile/lib/src/main.dart b/mobile/lib/src/main.dart index 8e1f8a5..46976e8 100644 --- a/mobile/lib/src/main.dart +++ b/mobile/lib/src/main.dart @@ -12,9 +12,10 @@ import 'package:provider/provider.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:get_it/get_it.dart'; -void main() { +void main() async { AerisAPI interface = AerisAPI(); GetIt.I.registerSingleton(interface); + await interface.restoreConnection(); runApp(MultiProvider(providers: [ ChangeNotifierProvider(create: (_) => PipelineProvider()), ChangeNotifierProvider(create: (_) => UserServiceProvider()) diff --git a/mobile/lib/src/views/login_page.dart b/mobile/lib/src/views/login_page.dart index 68ffd82..a2073ab 100644 --- a/mobile/lib/src/views/login_page.dart +++ b/mobile/lib/src/views/login_page.dart @@ -34,7 +34,7 @@ class LoginPage extends StatelessWidget { /// Opens signup page of [FlutterLogin] widget Future _signupUser(SignupData data) { - debugPrint('Signup Name: ${data.name}, Password: ${data.password}'); + return Future.delayed(loginDuration).then((_) { return null; }); @@ -42,7 +42,6 @@ class LoginPage extends StatelessWidget { /// Opens user password recovery page Future _recoverPassword(String name) { - debugPrint('Name: $name'); return Future.delayed(loginDuration).then((_) { if (!users.containsKey(name)) { return AppLocalizations.of(Aeris.materialKey.currentContext!) @@ -59,7 +58,8 @@ class LoginPage extends StatelessWidget { body: FlutterLogin( disableCustomPageTransformer: true, logo: const AssetImage("assets/logo.png"), - onRecoverPassword: _recoverPassword, + hideForgotPasswordButton: true, + onRecoverPassword: (_) => null, theme: LoginTheme( pageColorLight: Colors.transparent, pageColorDark: Colors.transparent, diff --git a/mobile/pubspec.lock b/mobile/pubspec.lock index 7849237..a0344be 100644 --- a/mobile/pubspec.lock +++ b/mobile/pubspec.lock @@ -260,7 +260,7 @@ packages: source: hosted version: "7.2.0" http: - dependency: transitive + dependency: "direct main" description: name: http url: "https://pub.dartlang.org" @@ -358,7 +358,7 @@ packages: source: hosted version: "1.8.0" path_provider: - dependency: transitive + dependency: "direct main" description: name: path_provider url: "https://pub.dartlang.org" diff --git a/mobile/pubspec.yaml b/mobile/pubspec.yaml index a088dff..2a541a5 100644 --- a/mobile/pubspec.yaml +++ b/mobile/pubspec.yaml @@ -53,6 +53,8 @@ dependencies: skeleton_loader: ^2.0.0+4 drag_and_drop_lists: ^0.3.2+2 reorderables: ^0.4.2 + path_provider: ^2.0.9 + http: ^0.13.4 dev_dependencies: flutter_launcher_icons: "^0.9.2" From 7344d97a2333e5827d42ed44bcf5e83b42761dce Mon Sep 17 00:00:00 2001 From: Arthi-chaud Date: Tue, 22 Feb 2022 12:04:34 +0100 Subject: [PATCH 004/109] Mobile Client: Signin user --- mobile/lib/src/aeris_api.dart | 23 ++++++++++++++++++++++- mobile/lib/src/views/login_page.dart | 23 +++++++++-------------- 2 files changed, 31 insertions(+), 15 deletions(-) diff --git a/mobile/lib/src/aeris_api.dart b/mobile/lib/src/aeris_api.dart index 538df1d..47b7216 100644 --- a/mobile/lib/src/aeris_api.dart +++ b/mobile/lib/src/aeris_api.dart @@ -93,7 +93,28 @@ class AerisAPI { Future signUpUser(String username, String password) async {} /// On success, sets API as connected to given user - Future createConnection(String username, String password) async {} + Future createConnection(String username, String password) async { + http.Response response = + await _requestAPI('/auth/login', AerisAPIRequestType.post, { + username: username, + password: password, + }); + if (response.statusCode != 200) { + return false; + } + final String jwt = response.headers[HttpHeaders.setCookieHeader]! + .split(';') + .where((element) => element.trim().startsWith('JWT-Cookie=')) + .first + .replaceAll('JWT-Cookie=', "").trim(); + + final File jwtFile = await getJWTFile(); + jwtFile.writeAsString(jwt); + response.headers['Set-Cookie']; + connected = true; + this.jwt = jwt; + return true; + } /// Create an API connection using previously created credentials Future restoreConnection() async { diff --git a/mobile/lib/src/views/login_page.dart b/mobile/lib/src/views/login_page.dart index a2073ab..f097b85 100644 --- a/mobile/lib/src/views/login_page.dart +++ b/mobile/lib/src/views/login_page.dart @@ -1,8 +1,10 @@ +import 'package:aeris/src/aeris_api.dart'; import 'package:aeris/src/main.dart'; import 'package:aeris/src/widgets/aeris_page.dart'; import 'package:flutter_login/flutter_login.dart'; import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; +import 'package:get_it/get_it.dart'; const users = { 'dribbble@gmail.com': '12345', @@ -17,24 +19,17 @@ class LoginPage extends StatelessWidget { Duration get loginDuration => const Duration(milliseconds: 2500); /// Called when user clicks on [FlutterLogin] widget 'login' button - Future _authUser(LoginData data) { - debugPrint('Name: ${data.name}, Password: ${data.password}'); - return Future.delayed(loginDuration).then((_) { - if (!users.containsKey(data.name)) { - return AppLocalizations.of(Aeris.materialKey.currentContext!) - .usernameOrPasswordIncorrect; - } - if (users[data.name] != data.password) { - return AppLocalizations.of(Aeris.materialKey.currentContext!) - .usernameOrPasswordIncorrect; - } - return null; - }); + Future _authUser(LoginData data) async { + bool connected = await GetIt.I().createConnection(data.name, data.password); + if (!connected) { + return AppLocalizations.of(Aeris.materialKey.currentContext!) + .usernameOrPasswordIncorrect; + } + return null; } /// Opens signup page of [FlutterLogin] widget Future _signupUser(SignupData data) { - return Future.delayed(loginDuration).then((_) { return null; }); From 0218fdd84067cc4a57ab63f729a42236d43d6b67 Mon Sep 17 00:00:00 2001 From: Arthi-chaud Date: Tue, 22 Feb 2022 12:08:46 +0100 Subject: [PATCH 005/109] Mobile Client: Signin user --- mobile/lib/src/aeris_api.dart | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/mobile/lib/src/aeris_api.dart b/mobile/lib/src/aeris_api.dart index 47b7216..dec76b3 100644 --- a/mobile/lib/src/aeris_api.dart +++ b/mobile/lib/src/aeris_api.dart @@ -106,11 +106,11 @@ class AerisAPI { .split(';') .where((element) => element.trim().startsWith('JWT-Cookie=')) .first - .replaceAll('JWT-Cookie=', "").trim(); + .replaceAll('JWT-Cookie=', "") + .trim(); final File jwtFile = await getJWTFile(); jwtFile.writeAsString(jwt); - response.headers['Set-Cookie']; connected = true; this.jwt = jwt; return true; @@ -207,17 +207,17 @@ class AerisAPI { } /// Calls API using a HTTP request type, a route and body - Future _requestAPI( - String route, AerisAPIRequestType requestType, Object? body) async { + Future _requestAPI(String route, AerisAPIRequestType requestType, Object? body) async { + final Map? header = connected ? {'authorization': 'Bearer $jwt'} : null; switch (requestType) { case AerisAPIRequestType.delete: - return await http.delete(_encoreUri(route), body: body); + return await http.delete(_encoreUri(route), body: body, headers: header); case AerisAPIRequestType.get: - return await http.get(_encoreUri(route)); + return await http.get(_encoreUri(route), headers: header); case AerisAPIRequestType.post: - return await http.post(_encoreUri(route), body: body); + return await http.post(_encoreUri(route), body: body, headers: header); case AerisAPIRequestType.put: - return await http.put(_encoreUri(route), body: body); + return await http.put(_encoreUri(route), body: body, headers: header); } } } From 49d499141b5d5c12177c3e11ccb0c6399be3cde6 Mon Sep 17 00:00:00 2001 From: Arthi-chaud Date: Tue, 22 Feb 2022 13:20:46 +0100 Subject: [PATCH 006/109] Mobile Client: Signup --- mobile/lib/src/aeris_api.dart | 49 +++++++++++++++++++++++------------ 1 file changed, 33 insertions(+), 16 deletions(-) diff --git a/mobile/lib/src/aeris_api.dart b/mobile/lib/src/aeris_api.dart index dec76b3..d921a06 100644 --- a/mobile/lib/src/aeris_api.dart +++ b/mobile/lib/src/aeris_api.dart @@ -89,10 +89,20 @@ class AerisAPI { return File('$path/$jwtFile.txt'); } - /// Registers new user in the database and connects it - Future signUpUser(String username, String password) async {} + /// Registers new user in the database and connects it. Returns false if register failed + Future signUpUser(String username, String password) async { + http.Response response = + await _requestAPI('/auth/signup', AerisAPIRequestType.post, { + username: username, + password: password, + }); + if (response.statusCode != 200) { + return false; + } + return createConnection(username, password); + } - /// On success, sets API as connected to given user + /// On success, sets API as connected to given user. Returns false if connection false Future createConnection(String username, String password) async { http.Response response = await _requestAPI('/auth/login', AerisAPIRequestType.post, { @@ -102,17 +112,21 @@ class AerisAPI { if (response.statusCode != 200) { return false; } - final String jwt = response.headers[HttpHeaders.setCookieHeader]! - .split(';') - .where((element) => element.trim().startsWith('JWT-Cookie=')) - .first - .replaceAll('JWT-Cookie=', "") - .trim(); + try { + final String jwt = response.headers[HttpHeaders.setCookieHeader]! + .split(';') + .where((element) => element.trim().startsWith('JWT-Cookie=')) + .first + .replaceAll('JWT-Cookie=', "") + .trim(); - final File jwtFile = await getJWTFile(); - jwtFile.writeAsString(jwt); - connected = true; - this.jwt = jwt; + final File jwtFile = await getJWTFile(); + jwtFile.writeAsString(jwt); + connected = true; + this.jwt = jwt; + } catch (e) { + return false; + } return true; } @@ -207,11 +221,14 @@ class AerisAPI { } /// Calls API using a HTTP request type, a route and body - Future _requestAPI(String route, AerisAPIRequestType requestType, Object? body) async { - final Map? header = connected ? {'authorization': 'Bearer $jwt'} : null; + Future _requestAPI( + String route, AerisAPIRequestType requestType, Object? body) async { + final Map? header = + connected ? {'authorization': 'Bearer $jwt'} : null; switch (requestType) { case AerisAPIRequestType.delete: - return await http.delete(_encoreUri(route), body: body, headers: header); + return await http.delete(_encoreUri(route), + body: body, headers: header); case AerisAPIRequestType.get: return await http.get(_encoreUri(route), headers: header); case AerisAPIRequestType.post: From 3f200716f1ae8446dbf1ce4944fb65ec05278248 Mon Sep 17 00:00:00 2001 From: Arthi-chaud Date: Tue, 22 Feb 2022 13:33:24 +0100 Subject: [PATCH 007/109] Mobile Client: error handling on signup error --- mobile/lib/l10n/app_en.arb | 3 ++- mobile/lib/l10n/app_fr.arb | 3 ++- mobile/lib/src/aeris_api.dart | 21 ++++++++++-------- mobile/lib/src/views/login_page.dart | 30 ++++++++------------------ mobile/lib/src/views/startup_page.dart | 2 +- 5 files changed, 26 insertions(+), 33 deletions(-) diff --git a/mobile/lib/l10n/app_en.arb b/mobile/lib/l10n/app_en.arb index cb6d5bf..b199153 100644 --- a/mobile/lib/l10n/app_en.arb +++ b/mobile/lib/l10n/app_en.arb @@ -36,5 +36,6 @@ "pipelineFormMisingAction": "You must select at least a trigger and a reaction", "logoutWarningMessage": "You are about to logout, are you sure?", "warning": "Warning", - "cancel": "Cancel" + "cancel": "Cancel", + "errorOnSignup": "An error occured while signing up you, please try again" } \ No newline at end of file diff --git a/mobile/lib/l10n/app_fr.arb b/mobile/lib/l10n/app_fr.arb index 3a54dbc..3c7d50c 100644 --- a/mobile/lib/l10n/app_fr.arb +++ b/mobile/lib/l10n/app_fr.arb @@ -36,5 +36,6 @@ "pipelineFormMisingAction": "Vous devez selectionner au moins un déclancheur et une réaction", "logoutWarningMessage": "Êtes-vous sûr(e) de voulour vous déconnecter d'Aeris?", "warning": "Attention", - "cancel": "Annuler" + "cancel": "Annuler", + "errorOnSignup": "Une erreur est survenue, veuillez réessayer" } \ No newline at end of file diff --git a/mobile/lib/src/aeris_api.dart b/mobile/lib/src/aeris_api.dart index d921a06..fa7ce44 100644 --- a/mobile/lib/src/aeris_api.dart +++ b/mobile/lib/src/aeris_api.dart @@ -14,12 +14,14 @@ enum AerisAPIRequestType { get, post, put, delete } /// Call to interact with Aeris' Back end class AerisAPI { - ///TODO set status based on stored credentials - bool connected = false; + /// Get Connection state + bool _connected = false; + bool get isConnected => _connected; + late List fakeAPI; /// JWT token used to request API - late String jwt; + late String _jwt; ///TODO Use .env String baseRoute = 'http://aeris.com'; @@ -89,6 +91,7 @@ class AerisAPI { return File('$path/$jwtFile.txt'); } + ///ROUTES /// Registers new user in the database and connects it. Returns false if register failed Future signUpUser(String username, String password) async { http.Response response = @@ -122,8 +125,8 @@ class AerisAPI { final File jwtFile = await getJWTFile(); jwtFile.writeAsString(jwt); - connected = true; - this.jwt = jwt; + _connected = true; + _jwt = jwt; } catch (e) { return false; } @@ -138,8 +141,8 @@ class AerisAPI { if (cred == "") { throw Exception("Empty creds"); } - jwt = cred; - connected = true; + _jwt = cred; + _connected = true; } catch (e) { return; } @@ -152,7 +155,7 @@ class AerisAPI { if (credentials.existsSync()) { await credentials.delete(); } - connected = false; + _connected = false; } /// Adds new pipeline to API @@ -224,7 +227,7 @@ class AerisAPI { Future _requestAPI( String route, AerisAPIRequestType requestType, Object? body) async { final Map? header = - connected ? {'authorization': 'Bearer $jwt'} : null; + _connected ? {'authorization': 'Bearer $_jwt'} : null; switch (requestType) { case AerisAPIRequestType.delete: return await http.delete(_encoreUri(route), diff --git a/mobile/lib/src/views/login_page.dart b/mobile/lib/src/views/login_page.dart index f097b85..b9063c6 100644 --- a/mobile/lib/src/views/login_page.dart +++ b/mobile/lib/src/views/login_page.dart @@ -6,11 +6,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:get_it/get_it.dart'; -const users = { - 'dribbble@gmail.com': '12345', - 'hunter@gmail.com': 'hunter', -}; - /// Login Page Widget class LoginPage extends StatelessWidget { const LoginPage({Key? key}) : super(key: key); @@ -20,7 +15,8 @@ class LoginPage extends StatelessWidget { /// Called when user clicks on [FlutterLogin] widget 'login' button Future _authUser(LoginData data) async { - bool connected = await GetIt.I().createConnection(data.name, data.password); + bool connected = + await GetIt.I().createConnection(data.name, data.password); if (!connected) { return AppLocalizations.of(Aeris.materialKey.currentContext!) .usernameOrPasswordIncorrect; @@ -29,21 +25,13 @@ class LoginPage extends StatelessWidget { } /// Opens signup page of [FlutterLogin] widget - Future _signupUser(SignupData data) { - return Future.delayed(loginDuration).then((_) { - return null; - }); - } - - /// Opens user password recovery page - Future _recoverPassword(String name) { - return Future.delayed(loginDuration).then((_) { - if (!users.containsKey(name)) { - return AppLocalizations.of(Aeris.materialKey.currentContext!) - .userDoesNotExist; - } - return null; - }); + Future _signupUser(SignupData data) async { + bool connected = + await GetIt.I().signUpUser(data.name!, data.password!); + if (connected == false) { + return AppLocalizations.of(Aeris.materialKey.currentContext!).errorOnSignup; + } + return null; } @override diff --git a/mobile/lib/src/views/startup_page.dart b/mobile/lib/src/views/startup_page.dart index 9e84589..474f1ef 100644 --- a/mobile/lib/src/views/startup_page.dart +++ b/mobile/lib/src/views/startup_page.dart @@ -17,7 +17,7 @@ class StartupPage extends StatefulWidget { class _StartupPageState extends State { @override Widget build(BuildContext context) { - bool isConnected = GetIt.I().connected; + bool isConnected = GetIt.I().isConnected; return AerisPage( displayAppbar: false, body: Column( From 4e6c70ab6413dd6ef8e0be795a3d0e2ea1a09ec0 Mon Sep 17 00:00:00 2001 From: Arthi-chaud Date: Tue, 22 Feb 2022 17:27:06 +0100 Subject: [PATCH 008/109] Mobile Client: Objects: Unserialize from JSON --- mobile/lib/src/models/action.dart | 10 ++++++++++ mobile/lib/src/models/pipeline.dart | 16 +++++++++++++++- mobile/lib/src/models/reaction.dart | 12 ++++++++++++ mobile/lib/src/models/service.dart | 7 +++++++ mobile/lib/src/models/trigger.dart | 16 +++++++++++++++- mobile/pubspec.lock | 7 +++++++ mobile/pubspec.yaml | 1 + 7 files changed, 67 insertions(+), 2 deletions(-) diff --git a/mobile/lib/src/models/action.dart b/mobile/lib/src/models/action.dart index 0e675c5..ff16fef 100644 --- a/mobile/lib/src/models/action.dart +++ b/mobile/lib/src/models/action.dart @@ -1,5 +1,7 @@ import 'package:flutter/widgets.dart'; import 'package:aeris/src/models/service.dart'; +import 'package:recase/recase.dart'; +import 'package:tuple/tuple.dart'; ///Base class for reactions and trigger abstract class Action { @@ -16,4 +18,12 @@ abstract class Action { required this.service, required this.name, this.parameters = const {}}); + + static Tuple2 parseServiceAndName(String rType) { + var snake = ReCase(rType).snakeCase.split('_'); + return Tuple2( + Service.factory(snake.first), + ReCase(snake.getRange(1, snake.length - 1).join('_')).titleCase + ); + } } diff --git a/mobile/lib/src/models/pipeline.dart b/mobile/lib/src/models/pipeline.dart index 7b9c9cc..d846013 100644 --- a/mobile/lib/src/models/pipeline.dart +++ b/mobile/lib/src/models/pipeline.dart @@ -16,7 +16,6 @@ class Pipeline { /// Is the pipeline enabled bool enabled; - ///The pipeline's reactions final List reactions; @@ -29,4 +28,19 @@ class Pipeline { required this.enabled, required this.trigger, required this.reactions}); + + static Pipeline fromJSON(Map data) { + var action = data['action'] as Map; + var reactions = data['reactions'] as Map; + + return Pipeline( + name: action['name'] as String, + enabled: action['enabled'] as bool, + id: action['id'] as int, + triggerCount: action['triggerCount'] as int, + trigger: Trigger.fromJSON(action), + reactions: (reactions as List) + .map((e) => Reaction.fromJSON(e)).toList() + ); + } } diff --git a/mobile/lib/src/models/reaction.dart b/mobile/lib/src/models/reaction.dart index 1e8cf03..4eab3e9 100644 --- a/mobile/lib/src/models/reaction.dart +++ b/mobile/lib/src/models/reaction.dart @@ -3,6 +3,7 @@ import 'package:aeris/src/models/action.dart' as aeris_action; import 'package:flutter/widgets.dart'; import 'package:aeris/src/models/service.dart'; +import 'package:tuple/tuple.dart'; ///Object representation of a reaction class Reaction extends aeris_action.Action { @@ -17,6 +18,17 @@ class Reaction extends aeris_action.Action { Reaction.template() : super(service: Service.all()[0], name: '', parameters: {}); + static Reaction fromJSON(Object reaction) { + var reactionJSON = reaction as Map; + Tuple2 service = + aeris_action.Action.parseServiceAndName(reactionJSON['rType'] as String); + return Reaction( + service: service.item1, + name: service.item2, + parameters: (reactionJSON['rParams'] as Map)['contents'] + as Map); + } + @override bool operator ==(Object other) { Reaction otherReaction = other as Reaction; diff --git a/mobile/lib/src/models/service.dart b/mobile/lib/src/models/service.dart index c9048df..4564a99 100644 --- a/mobile/lib/src/models/service.dart +++ b/mobile/lib/src/models/service.dart @@ -61,4 +61,11 @@ class Service { Service.twitter(), Service.spotify() ]; + + static Service factory(String name) { + for (Service service in Service.all()) { + if (service.name.toLowerCase() == name.toLowerCase()) return service; + } + throw Exception("Unknown service"); + } } diff --git a/mobile/lib/src/models/trigger.dart b/mobile/lib/src/models/trigger.dart index ed0ac63..b00fb0a 100644 --- a/mobile/lib/src/models/trigger.dart +++ b/mobile/lib/src/models/trigger.dart @@ -5,6 +5,7 @@ import 'package:aeris/src/main.dart'; import 'package:aeris/src/models/service.dart'; import 'package:aeris/src/models/action.dart' as aeris_action; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; +import 'package:tuple/tuple.dart'; ///Object representation of a pipeline trigger class Trigger extends aeris_action.Action { @@ -18,7 +19,20 @@ class Trigger extends aeris_action.Action { this.last}) : super(service: service, name: name, parameters: parameters); - ///TODO Constructor from DB 'Type' field + /// Unserialize + static Trigger fromJSON(Object action) { + var triggerJSON = action as Map; + Tuple2 service = + aeris_action.Action.parseServiceAndName(triggerJSON['pType'] as String); + DateTime last = DateTime.parse(action['lastTrigger'] as String); + + return Trigger( + service: service.item1, + name: service.item2, + last: last.year == 0 ? null : last, + parameters: (triggerJSON['pParams'] as Map)['contents'] + as Map); + } String lastToString() { var context = AppLocalizations.of(Aeris.materialKey.currentContext!); diff --git a/mobile/pubspec.lock b/mobile/pubspec.lock index 7849237..02ce136 100644 --- a/mobile/pubspec.lock +++ b/mobile/pubspec.lock @@ -558,6 +558,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "0.4.8" + tuple: + dependency: "direct main" + description: + name: tuple + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.0" typed_data: dependency: transitive description: diff --git a/mobile/pubspec.yaml b/mobile/pubspec.yaml index a088dff..fd927a3 100644 --- a/mobile/pubspec.yaml +++ b/mobile/pubspec.yaml @@ -53,6 +53,7 @@ dependencies: skeleton_loader: ^2.0.0+4 drag_and_drop_lists: ^0.3.2+2 reorderables: ^0.4.2 + tuple: ^2.0.0 dev_dependencies: flutter_launcher_icons: "^0.9.2" From 28d2f5e78dc63f62879370d0462ab5c2d19e3232 Mon Sep 17 00:00:00 2001 From: Arthi-chaud Date: Wed, 23 Feb 2022 09:57:56 +0100 Subject: [PATCH 009/109] Mobile Client: Action: 'serialise' action type --- mobile/lib/src/models/action.dart | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/mobile/lib/src/models/action.dart b/mobile/lib/src/models/action.dart index ff16fef..361917c 100644 --- a/mobile/lib/src/models/action.dart +++ b/mobile/lib/src/models/action.dart @@ -21,9 +21,13 @@ abstract class Action { static Tuple2 parseServiceAndName(String rType) { var snake = ReCase(rType).snakeCase.split('_'); - return Tuple2( - Service.factory(snake.first), - ReCase(snake.getRange(1, snake.length - 1).join('_')).titleCase - ); + return Tuple2(Service.factory(snake.first), + ReCase(snake.getRange(1, snake.length - 1).join('_')).titleCase); + } + + static String getType(Service service, String aName) { + String serviceName = ReCase(service.name).pascalCase; + String actionName = ReCase(aName).paramCase; + return "$serviceName$actionName"; } } From 3413eae9cdce454e6108274f6b8b0541ee90016e Mon Sep 17 00:00:00 2001 From: Arthi-chaud Date: Wed, 23 Feb 2022 10:00:10 +0100 Subject: [PATCH 010/109] Mobile Client: Serialize Reaction --- mobile/lib/src/models/reaction.dart | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/mobile/lib/src/models/reaction.dart b/mobile/lib/src/models/reaction.dart index 4eab3e9..55e974b 100644 --- a/mobile/lib/src/models/reaction.dart +++ b/mobile/lib/src/models/reaction.dart @@ -20,8 +20,8 @@ class Reaction extends aeris_action.Action { static Reaction fromJSON(Object reaction) { var reactionJSON = reaction as Map; - Tuple2 service = - aeris_action.Action.parseServiceAndName(reactionJSON['rType'] as String); + Tuple2 service = aeris_action.Action.parseServiceAndName( + reactionJSON['rType'] as String); return Reaction( service: service.item1, name: service.item2, @@ -29,6 +29,16 @@ class Reaction extends aeris_action.Action { as Map); } + /// Serialize Reaction to JSON + Object toJSON() { + return { + "rType": aeris_action.Action.getType(service, name), + "rParams": { + "contents": parameters + } + }; + } + @override bool operator ==(Object other) { Reaction otherReaction = other as Reaction; From 7680028bb577c09ec632fed9385e6556d7e633aa Mon Sep 17 00:00:00 2001 From: Arthi-chaud Date: Wed, 23 Feb 2022 10:11:07 +0100 Subject: [PATCH 011/109] Mobile Client: Serialize Reaction --- mobile/lib/src/models/reaction.dart | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/mobile/lib/src/models/reaction.dart b/mobile/lib/src/models/reaction.dart index 55e974b..8673ba4 100644 --- a/mobile/lib/src/models/reaction.dart +++ b/mobile/lib/src/models/reaction.dart @@ -30,14 +30,10 @@ class Reaction extends aeris_action.Action { } /// Serialize Reaction to JSON - Object toJSON() { - return { - "rType": aeris_action.Action.getType(service, name), - "rParams": { - "contents": parameters - } - }; - } + Object toJSON() => { + "rType": aeris_action.Action.getType(service, name), + "rParams": {"contents": parameters} + }; @override bool operator ==(Object other) { From 33a6e2f911590384349b4c8d3f60e515ed8bdfa4 Mon Sep 17 00:00:00 2001 From: Arthi-chaud Date: Wed, 23 Feb 2022 10:11:14 +0100 Subject: [PATCH 012/109] Mobile Client: Serialize Reaction --- mobile/lib/src/models/pipeline.dart | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/mobile/lib/src/models/pipeline.dart b/mobile/lib/src/models/pipeline.dart index d846013..e233268 100644 --- a/mobile/lib/src/models/pipeline.dart +++ b/mobile/lib/src/models/pipeline.dart @@ -1,3 +1,4 @@ +import 'package:aeris/src/models/action.dart' as aeris_action; import 'package:flutter/material.dart'; import 'package:aeris/src/models/reaction.dart'; import 'package:aeris/src/models/trigger.dart'; @@ -29,6 +30,7 @@ class Pipeline { required this.trigger, required this.reactions}); + /// Unserialize Pipeline from JSON static Pipeline fromJSON(Map data) { var action = data['action'] as Map; var reactions = data['reactions'] as Map; @@ -40,7 +42,20 @@ class Pipeline { triggerCount: action['triggerCount'] as int, trigger: Trigger.fromJSON(action), reactions: (reactions as List) - .map((e) => Reaction.fromJSON(e)).toList() - ); + .map((e) => Reaction.fromJSON(e)) + .toList()); } + + /// Serialize Pipeline into JSON + Object toJSON() => { + "action": { + "name": name, + "pType": aeris_action.Action.getType(trigger.service, trigger.name), + "pParams": trigger.parameters, + "enabled": enabled, + "lastTrigger": trigger.last?.toIso8601String() ?? "0000-00-00", + "triggerCount": triggerCount + }, + 'reactions': reactions.map((e) => e.toJSON()).toList() + }; } From 7439cb862fc7e6c947f3010cb465df9c47548655 Mon Sep 17 00:00:00 2001 From: Arthi-chaud Date: Wed, 23 Feb 2022 10:51:31 +0100 Subject: [PATCH 013/109] Mobile Client: more & more api interactions --- mobile/ios/Runner.xcodeproj/project.pbxproj | 2 +- .../xcshareddata/xcschemes/Runner.xcscheme | 2 +- mobile/lib/src/aeris_api.dart | 56 ++++++++++--------- mobile/lib/src/models/pipeline.dart | 1 + mobile/lib/src/views/home_page.dart | 7 ++- 5 files changed, 38 insertions(+), 30 deletions(-) diff --git a/mobile/ios/Runner.xcodeproj/project.pbxproj b/mobile/ios/Runner.xcodeproj/project.pbxproj index 5ee3094..9a1f78e 100644 --- a/mobile/ios/Runner.xcodeproj/project.pbxproj +++ b/mobile/ios/Runner.xcodeproj/project.pbxproj @@ -538,4 +538,4 @@ /* End XCConfigurationList section */ }; rootObject = 97C146E61CF9000F007C117D /* Project object */; -} \ No newline at end of file +} diff --git a/mobile/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/mobile/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index 6dd6010..c87d15a 100644 --- a/mobile/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/mobile/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -1,6 +1,6 @@ createPipeline(Pipeline newPipeline) async { - ///TODO Send Pipeline to API + /// Adds new pipeline to API, returns false if post failed + Future createPipeline(Pipeline newPipeline) async { fakeAPI.add(newPipeline); - await Future.delayed(const Duration(seconds: 2)); - return; + var res = await _requestAPI( + '/workflow', AerisAPIRequestType.post, newPipeline.toJSON()); + + return res.ok; } /// Removes pipeline from API - Future removePipeline(Pipeline pipeline) async { - ///TODO Send delete request to API - fakeAPI.remove(pipeline); - await Future.delayed(const Duration(seconds: 2)); - return; + Future removePipeline(Pipeline pipeline) async { + var res = await _requestAPI( + '/workflow/${pipeline.id}', AerisAPIRequestType.delete, null); + return res.ok; } - Future editPipeline(Pipeline updatedPipeline) async { - ///TODO Send update request to API - for (var pipeline in fakeAPI) { - if (pipeline.id == updatedPipeline.id) { - ///TODO Call Api - break; - } - } - - await Future.delayed(const Duration(seconds: 2)); - return; + /// Send PUT request to update Pipeline, returns false if failed + Future editPipeline(Pipeline updatedPipeline) async { + var res = await _requestAPI('/workflow/${updatedPipeline.id}', + AerisAPIRequestType.put, updatedPipeline.toJSON()); + return res.ok; } /// Fetches the Pipelines from the API Future> getPipelines() async { - /// TODO Fetch the API - await Future.delayed(const Duration(seconds: 2)); + var res = await _requestAPI('/workflows', AerisAPIRequestType.get, null); + List body = jsonDecode(res.body); + ///TODO error handling + ///TODO return body.map((e) => Pipeline.fromJSON(e as Map)).toList(); return fakeAPI; } @@ -230,8 +233,7 @@ class AerisAPI { _connected ? {'authorization': 'Bearer $_jwt'} : null; switch (requestType) { case AerisAPIRequestType.delete: - return await http.delete(_encoreUri(route), - body: body, headers: header); + return await http.delete(_encoreUri(route), body: body, headers: header); case AerisAPIRequestType.get: return await http.get(_encoreUri(route), headers: header); case AerisAPIRequestType.post: diff --git a/mobile/lib/src/models/pipeline.dart b/mobile/lib/src/models/pipeline.dart index e233268..3b32d9d 100644 --- a/mobile/lib/src/models/pipeline.dart +++ b/mobile/lib/src/models/pipeline.dart @@ -49,6 +49,7 @@ class Pipeline { /// Serialize Pipeline into JSON Object toJSON() => { "action": { + "id": id, "name": name, "pType": aeris_action.Action.getType(trigger.service, trigger.name), "pParams": trigger.parameters, diff --git a/mobile/lib/src/views/home_page.dart b/mobile/lib/src/views/home_page.dart index 49987e2..5aaef98 100644 --- a/mobile/lib/src/views/home_page.dart +++ b/mobile/lib/src/views/home_page.dart @@ -1,3 +1,4 @@ +import 'package:aeris/src/aeris_api.dart'; import 'package:aeris/src/views/create_pipeline_page.dart'; import 'package:aeris/src/views/service_page.dart'; import 'package:aeris/src/widgets/aeris_card_page.dart'; @@ -8,6 +9,7 @@ import 'package:aeris/src/widgets/aeris_page.dart'; import 'package:aeris/src/widgets/clickable_card.dart'; import 'package:aeris/src/widgets/home_page_sort_menu.dart'; import 'package:aeris/src/widgets/pipeline_card.dart'; +import 'package:get_it/get_it.dart'; import 'package:liquid_pull_to_refresh/liquid_pull_to_refresh.dart'; import 'package:provider/provider.dart'; import 'package:skeleton_loader/skeleton_loader.dart'; @@ -36,7 +38,10 @@ class _HomePageState extends State { context: context, builder: (BuildContext context) => WarningDialog( message: AppLocalizations.of(context).logoutWarningMessage, - onAccept: () => Navigator.of(context).popAndPushNamed('/'), //TODO logout + onAccept: () { + GetIt.I().stopConnection(); + Navigator.of(context).popAndPushNamed('/'); + }, warnedAction: AppLocalizations.of(context).logout ) ), From dd576299dcdb29d40a17e7828b4256c137b3748d Mon Sep 17 00:00:00 2001 From: Arthi-chaud Date: Wed, 23 Feb 2022 11:04:05 +0100 Subject: [PATCH 014/109] Mobile Client: more & more api interactions --- mobile/lib/src/aeris_api.dart | 16 ++++++++-------- mobile/lib/src/views/service_page.dart | 20 +++----------------- 2 files changed, 11 insertions(+), 25 deletions(-) diff --git a/mobile/lib/src/aeris_api.dart b/mobile/lib/src/aeris_api.dart index c3e90e6..8a06e94 100644 --- a/mobile/lib/src/aeris_api.dart +++ b/mobile/lib/src/aeris_api.dart @@ -11,9 +11,7 @@ import 'package:path_provider/path_provider.dart'; import 'package:http/http.dart' as http; extension IsOk on http.Response { - bool get ok { - return (statusCode ~/ 100) == 2; - } + bool get ok => (statusCode ~/ 100) == 2; } /// Requests types supported by Aeris API @@ -192,16 +190,17 @@ class AerisAPI { Future> getPipelines() async { var res = await _requestAPI('/workflows', AerisAPIRequestType.get, null); List body = jsonDecode(res.body); + ///TODO error handling ///TODO return body.map((e) => Pipeline.fromJSON(e as Map)).toList(); return fakeAPI; } /// Disconnects the user from the service - Future disconnectService(Service service) async { - ///TODO disconnect service from user - await Future.delayed(const Duration(seconds: 2)); - return; + Future disconnectService(Service service) async { + var res = await _requestAPI('/auth/${service.name.toLowerCase()}', + AerisAPIRequestType.delete, null); + return res.ok; } Future> getActionsFor( @@ -233,7 +232,8 @@ class AerisAPI { _connected ? {'authorization': 'Bearer $_jwt'} : null; switch (requestType) { case AerisAPIRequestType.delete: - return await http.delete(_encoreUri(route), body: body, headers: header); + return await http.delete(_encoreUri(route), + body: body, headers: header); case AerisAPIRequestType.get: return await http.get(_encoreUri(route), headers: header); case AerisAPIRequestType.post: diff --git a/mobile/lib/src/views/service_page.dart b/mobile/lib/src/views/service_page.dart index 4b7a719..4cee766 100644 --- a/mobile/lib/src/views/service_page.dart +++ b/mobile/lib/src/views/service_page.dart @@ -1,7 +1,5 @@ import 'package:aeris/src/aeris_api.dart'; import 'package:flutter/material.dart'; -import 'package:aeris/src/models/pipeline.dart'; -import 'package:aeris/src/models/reaction.dart'; import 'package:aeris/src/models/service.dart'; import 'package:aeris/src/providers/pipelines_provider.dart'; import 'package:aeris/src/providers/user_services_provider.dart'; @@ -78,21 +76,9 @@ class ServicePage extends StatelessWidget { builder: (BuildContext context) => WarningDialog( message: AppLocalizations.of(context) .disconnectServiceWarningMessage, - onAccept: () => { - provider.removePipelinesWhere((Pipeline pipeline) { - if (pipeline.trigger.service == service) { - return true; - } - if (pipeline.reactions - .where((Reaction react) => - react.service == service) - .isNotEmpty) { - return true; - } - return false; - }), - GetIt.I().disconnectService(service) - }, + onAccept: () => GetIt.I() + .disconnectService(service) + .then((_) => provider.fetchPipelines()), warnedAction: AppLocalizations.of(context).disconnect)), context), From 47defbc6df5edf51f80a567382eef53078407e4b Mon Sep 17 00:00:00 2001 From: Arthi-chaud Date: Wed, 23 Feb 2022 18:49:40 +0100 Subject: [PATCH 015/109] Mobile Client: Fix JWT fetching --- mobile/lib/src/aeris_api.dart | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/mobile/lib/src/aeris_api.dart b/mobile/lib/src/aeris_api.dart index 8a06e94..4879c51 100644 --- a/mobile/lib/src/aeris_api.dart +++ b/mobile/lib/src/aeris_api.dart @@ -121,13 +121,7 @@ class AerisAPI { return false; } try { - final String jwt = response.headers[HttpHeaders.setCookieHeader]! - .split(';') - .where((element) => element.trim().startsWith('JWT-Cookie=')) - .first - .replaceAll('JWT-Cookie=', "") - .trim(); - + final String jwt = jsonDecode(response.body)['jwt']; final File jwtFile = await getJWTFile(); jwtFile.writeAsString(jwt); _connected = true; From 0cd90c378a66baaed85ded14325551fcc1066caf Mon Sep 17 00:00:00 2001 From: Arthi-chaud Date: Wed, 23 Feb 2022 19:05:05 +0100 Subject: [PATCH 016/109] Mobile Client: using hostname --- mobile/lib/src/aeris_api.dart | 4 +++- mobile/lib/src/main.dart | 2 ++ mobile/pubspec.lock | 7 +++++++ mobile/pubspec.yaml | 2 ++ 4 files changed, 14 insertions(+), 1 deletion(-) diff --git a/mobile/lib/src/aeris_api.dart b/mobile/lib/src/aeris_api.dart index 4879c51..53cc386 100644 --- a/mobile/lib/src/aeris_api.dart +++ b/mobile/lib/src/aeris_api.dart @@ -9,6 +9,7 @@ import 'package:aeris/src/models/service.dart'; import 'package:aeris/src/models/trigger.dart'; import 'package:path_provider/path_provider.dart'; import 'package:http/http.dart' as http; +import 'package:flutter_dotenv/flutter_dotenv.dart'; extension IsOk on http.Response { bool get ok => (statusCode ~/ 100) == 2; @@ -29,9 +30,10 @@ class AerisAPI { late String _jwt; ///TODO Use .env - String baseRoute = 'http://aeris.com'; + late final String baseRoute; AerisAPI() { + baseRoute = dotenv.env['HOSTNAME']!; var trigger1 = Trigger( service: const Service.spotify(), name: "Play song", diff --git a/mobile/lib/src/main.dart b/mobile/lib/src/main.dart index 46976e8..76f2206 100644 --- a/mobile/lib/src/main.dart +++ b/mobile/lib/src/main.dart @@ -1,4 +1,5 @@ import 'package:aeris/src/aeris_api.dart'; +import 'package:flutter_dotenv/flutter_dotenv.dart'; import 'package:flutter_localizations/flutter_localizations.dart'; import 'package:form_builder_validators/localization/l10n.dart'; import 'package:aeris/src/providers/pipelines_provider.dart'; @@ -13,6 +14,7 @@ import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:get_it/get_it.dart'; void main() async { + await dotenv.load(fileName: ".env"); AerisAPI interface = AerisAPI(); GetIt.I.registerSingleton(interface); await interface.restoreConnection(); diff --git a/mobile/pubspec.lock b/mobile/pubspec.lock index 85d5eab..03e6c74 100644 --- a/mobile/pubspec.lock +++ b/mobile/pubspec.lock @@ -167,6 +167,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "3.3.0" + flutter_dotenv: + dependency: "direct main" + description: + name: flutter_dotenv + url: "https://pub.dartlang.org" + source: hosted + version: "5.0.2" flutter_fadein: dependency: "direct main" description: diff --git a/mobile/pubspec.yaml b/mobile/pubspec.yaml index f29f081..3ca3ca5 100644 --- a/mobile/pubspec.yaml +++ b/mobile/pubspec.yaml @@ -56,6 +56,7 @@ dependencies: path_provider: ^2.0.9 http: ^0.13.4 tuple: ^2.0.0 + flutter_dotenv: ^5.0.2 dev_dependencies: flutter_launcher_icons: "^0.9.2" @@ -93,6 +94,7 @@ flutter: assets: - assets/logo.png + - .env # An image asset can refer to one or more resolution-specific "variants", see # https://flutter.dev/assets-and-images/#resolution-aware. From c170a1b79981feda9f2a58b2a059294dc2356de0 Mon Sep 17 00:00:00 2001 From: Arthi-chaud Date: Wed, 23 Feb 2022 19:08:15 +0100 Subject: [PATCH 017/109] Env: adding hostnma efor clients --- .env.example | 1 + 1 file changed, 1 insertion(+) diff --git a/.env.example b/.env.example index b151f60..771658d 100644 --- a/.env.example +++ b/.env.example @@ -4,3 +4,4 @@ POSTGRES_PORT=5432 YOUTUBE_KEY= +HOSTNAME=aeris.com \ No newline at end of file From 1708c2cba4d98880231000c02a4816a77f12ab39 Mon Sep 17 00:00:00 2001 From: Arthi-chaud Date: Thu, 24 Feb 2022 10:57:15 +0100 Subject: [PATCH 018/109] Mobile Client: Pipeline Provider: fix push workflow method --- mobile/lib/src/aeris_api.dart | 3 +-- mobile/lib/src/providers/pipelines_provider.dart | 11 ++++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/mobile/lib/src/aeris_api.dart b/mobile/lib/src/aeris_api.dart index 53cc386..b90cc52 100644 --- a/mobile/lib/src/aeris_api.dart +++ b/mobile/lib/src/aeris_api.dart @@ -29,7 +29,6 @@ class AerisAPI { /// JWT token used to request API late String _jwt; - ///TODO Use .env late final String baseRoute; AerisAPI() { @@ -164,7 +163,7 @@ class AerisAPI { fakeAPI.add(newPipeline); var res = await _requestAPI( '/workflow', AerisAPIRequestType.post, newPipeline.toJSON()); - + newPipeline = Pipeline.fromJSON(jsonDecode(res.body)); return res.ok; } diff --git a/mobile/lib/src/providers/pipelines_provider.dart b/mobile/lib/src/providers/pipelines_provider.dart index 7944e7e..722a933 100644 --- a/mobile/lib/src/providers/pipelines_provider.dart +++ b/mobile/lib/src/providers/pipelines_provider.dart @@ -10,7 +10,8 @@ class PipelineProvider extends ChangeNotifier { late PipelineCollection _pipelineCollection; /// Tells if the provers has loaded data at least once - bool initialized = false; + bool _initialized = false; + bool get initialized => _initialized; PipelineProvider() { _pipelineCollection = PipelineCollection( @@ -23,17 +24,17 @@ class PipelineProvider extends ChangeNotifier { /// Fetches the pipelines from API and put them in the collection Future fetchPipelines() { return GetIt.I().getPipelines().then((pipelines) { + _initialized = true; _pipelineCollection.pipelines = pipelines; sortPipelines(); - initialized = true; }); } /// Adds a pipeline in the Provider - addPipeline(Pipeline newPipeline) { - initialized = true; + addPipeline(Pipeline newPipeline) async { + await GetIt.I().createPipeline(newPipeline); + ///TODO Check newPipeline got the ID _pipelineCollection.pipelines.add(newPipeline); - GetIt.I().createPipeline(newPipeline); sortPipelines(); notifyListeners(); } From ef96d03de642fb6ef868edfac6e4b60042bcb9bb Mon Sep 17 00:00:00 2001 From: Arthi-chaud Date: Thu, 24 Feb 2022 13:24:27 +0100 Subject: [PATCH 019/109] Mobile Client: better maangemnt of page transitions --- mobile/lib/src/views/login_page.dart | 3 +-- mobile/lib/src/views/startup_page.dart | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/mobile/lib/src/views/login_page.dart b/mobile/lib/src/views/login_page.dart index b9063c6..1c3ce94 100644 --- a/mobile/lib/src/views/login_page.dart +++ b/mobile/lib/src/views/login_page.dart @@ -50,8 +50,7 @@ class LoginPage extends StatelessWidget { onLogin: _authUser, onSignup: _signupUser, onSubmitAnimationCompleted: () { - Navigator.of(context).popUntil((route) => route.isFirst); - Navigator.of(context).popAndPushNamed("/home"); + Navigator.of(context).pushNamedAndRemoveUntil('/home', (route) => false); })); } } diff --git a/mobile/lib/src/views/startup_page.dart b/mobile/lib/src/views/startup_page.dart index 474f1ef..f42d281 100644 --- a/mobile/lib/src/views/startup_page.dart +++ b/mobile/lib/src/views/startup_page.dart @@ -50,7 +50,7 @@ class _StartupPageState extends State { ), onPressed: () { if (isConnected) { - Navigator.of(context).popAndPushNamed('/home'); + Navigator.of(context).pushNamedAndRemoveUntil('/home', (route) => false); } else { Navigator.of(context).pushNamed('/login'); } From a2081aab15f3279b03bc935601fc6c98ea684c92 Mon Sep 17 00:00:00 2001 From: Arthi-chaud Date: Fri, 25 Feb 2022 10:24:18 +0100 Subject: [PATCH 020/109] Mobile Client: Action form: add static description --- mobile/ios/Runner.xcodeproj/project.pbxproj | 6 +++--- .../xcshareddata/xcschemes/Runner.xcscheme | 2 +- mobile/lib/src/views/setup_action_page.dart | 9 +++++++-- mobile/lib/src/widgets/action_form.dart | 4 ++++ 4 files changed, 15 insertions(+), 6 deletions(-) diff --git a/mobile/ios/Runner.xcodeproj/project.pbxproj b/mobile/ios/Runner.xcodeproj/project.pbxproj index 5ee3094..b95f859 100644 --- a/mobile/ios/Runner.xcodeproj/project.pbxproj +++ b/mobile/ios/Runner.xcodeproj/project.pbxproj @@ -523,7 +523,7 @@ 249021D3217E4FDB00AE95B9 /* Profile */, ); defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; + defaultConfigurationName = Debug; }; 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { isa = XCConfigurationList; @@ -533,9 +533,9 @@ 249021D4217E4FDB00AE95B9 /* Profile */, ); defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; + defaultConfigurationName = Debug; }; /* End XCConfigurationList section */ }; rootObject = 97C146E61CF9000F007C117D /* Project object */; -} \ No newline at end of file +} diff --git a/mobile/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/mobile/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index c87d15a..6dd6010 100644 --- a/mobile/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/mobile/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -1,6 +1,6 @@ { }).toList(), ); + var cardShape = const RoundedRectangleBorder( + borderRadius: BorderRadius.all(Radius.circular(10))); + return AerisCardPage( body: Padding( padding: const EdgeInsets.only(bottom: 20, left: 10, right: 10), @@ -101,7 +104,7 @@ class _SetupActionPageState extends State { const SizedBox(height: 30), if (availableActions == null) SkeletonLoader( - builder: const Card(child: SizedBox(height: 40), elevation: 5), + builder: Card(shape: cardShape, child: const SizedBox(height: 40), elevation: 5), items: 10, highlightColor: Theme.of(context).colorScheme.secondary ) @@ -109,7 +112,8 @@ class _SetupActionPageState extends State { ...[for (aeris.Action availableAction in availableActions!) Card( elevation: 5, - child: ExpandablePanel( + shape: cardShape, + child: ExpandablePanel( header: Padding( padding: const EdgeInsets.only(left: 30, top: 20, bottom: 20), @@ -119,6 +123,7 @@ class _SetupActionPageState extends State { expanded: Padding( padding: const EdgeInsets.all(20), child: ActionForm( + description: "This is the action's very very very very long description", ///TODO Find actual description name: availableAction.name, parametersNames: availableAction.parameters.keys.toList(), diff --git a/mobile/lib/src/widgets/action_form.dart b/mobile/lib/src/widgets/action_form.dart index 3370993..ca7aea4 100644 --- a/mobile/lib/src/widgets/action_form.dart +++ b/mobile/lib/src/widgets/action_form.dart @@ -11,6 +11,8 @@ class ActionForm extends StatefulWidget { final List parametersNames; /// Initial values of the fields final Map initValues; + /// What the action does + final String description; /// On validate callback final void Function(Map) onValidate; @@ -18,6 +20,7 @@ class ActionForm extends StatefulWidget { const ActionForm( {Key? key, required this.name, + required this.description, required this.parametersNames, required this.onValidate, this.initValues = const {}}) @@ -36,6 +39,7 @@ class _ActionFormState extends State { key: _formKey, child: Column( children: [ + Text(widget.description, textAlign: TextAlign.left, style: TextStyle(color: Theme.of(context).colorScheme.onSurface)), ...widget.parametersNames.map((name) => FormBuilderTextField( initialValue: (widget.initValues.containsKey(name)) ? widget.initValues[name] as String : null, name: name, From feb781debee0cd2fb15ba0a677df3a453fc2e541 Mon Sep 17 00:00:00 2001 From: Arthi-chaud Date: Fri, 25 Feb 2022 11:24:16 +0100 Subject: [PATCH 021/109] Mobile Client: Made a entire class for Action parameters --- mobile/lib/src/aeris_api.dart | 12 +++++++---- mobile/lib/src/models/action.dart | 5 +++-- mobile/lib/src/models/action_parameter.dart | 23 +++++++++++++++++++++ mobile/lib/src/models/action_template.dart | 3 ++- mobile/lib/src/models/reaction.dart | 13 ++++++------ mobile/lib/src/models/trigger.dart | 12 +++++------ mobile/lib/src/views/setup_action_page.dart | 7 ++++--- 7 files changed, 52 insertions(+), 23 deletions(-) create mode 100644 mobile/lib/src/models/action_parameter.dart diff --git a/mobile/lib/src/aeris_api.dart b/mobile/lib/src/aeris_api.dart index 5abf6fe..44e93b6 100644 --- a/mobile/lib/src/aeris_api.dart +++ b/mobile/lib/src/aeris_api.dart @@ -1,5 +1,6 @@ import 'dart:async'; import 'package:aeris/src/models/action.dart'; +import 'package:aeris/src/models/action_parameter.dart'; import 'package:aeris/src/models/action_template.dart'; import 'package:aeris/src/models/pipeline.dart'; import 'package:aeris/src/models/reaction.dart'; @@ -26,11 +27,11 @@ class AerisAPI { name: "Play song", last: DateTime.parse("2022-01-01")); var reaction = Reaction( - service: const Service.twitter(), parameters: {}, name: "Post a tweet"); + service: const Service.twitter(), parameters: [], name: "Post a tweet"); var reaction2 = Reaction( - service: const Service.gmail(), parameters: {}, name: "Do smth"); + service: const Service.gmail(), parameters: [], name: "Do smth"); var reaction1 = Reaction( - service: const Service.youtube(), parameters: {}, name: "Do smth youtube"); + service: const Service.youtube(), parameters: [], name: "Do smth youtube"); var pipeline1 = Pipeline( id: 10, name: "My Action", @@ -121,7 +122,10 @@ class AerisAPI { ActionTemplate( service: service, name: "action$i", - parameters: {'key1': 'value1', 'key2': 'value2'}) + parameters: [ + for (int j = 0; j < 3; j++) + ActionParameter(name: "key$j", value: "value$j", description: "description$j") + ]) ]; } } diff --git a/mobile/lib/src/models/action.dart b/mobile/lib/src/models/action.dart index ff16fef..4d36c41 100644 --- a/mobile/lib/src/models/action.dart +++ b/mobile/lib/src/models/action.dart @@ -1,3 +1,4 @@ +import 'package:aeris/src/models/action_parameter.dart'; import 'package:flutter/widgets.dart'; import 'package:aeris/src/models/service.dart'; import 'package:recase/recase.dart'; @@ -12,12 +13,12 @@ abstract class Action { String name; ///Action's parameters - Map parameters; + List parameters; Action( {Key? key, required this.service, required this.name, - this.parameters = const {}}); + this.parameters = const []}); static Tuple2 parseServiceAndName(String rType) { var snake = ReCase(rType).snakeCase.split('_'); diff --git a/mobile/lib/src/models/action_parameter.dart b/mobile/lib/src/models/action_parameter.dart new file mode 100644 index 0000000..5acae7f --- /dev/null +++ b/mobile/lib/src/models/action_parameter.dart @@ -0,0 +1,23 @@ +import 'package:flutter/material.dart'; + +/// Object representation of an action's parameter +class ActionParameter { + /// Name of the action parameter + final String name; + + /// Description of theparameter + final String description; + + /// Value of the pamrameter + Object? value; + + ActionParameter( + {Key? key, required this.name, this.description = "", this.value}); + + static List fromJSON(Map params) { + List actionParameters = []; + params.forEach((key, value) => + actionParameters.add(ActionParameter(name: key, value: value))); + return actionParameters; + } +} diff --git a/mobile/lib/src/models/action_template.dart b/mobile/lib/src/models/action_template.dart index a4c7f40..d14a784 100644 --- a/mobile/lib/src/models/action_template.dart +++ b/mobile/lib/src/models/action_template.dart @@ -1,4 +1,5 @@ import 'package:aeris/src/models/action.dart'; +import 'package:aeris/src/models/action_parameter.dart'; import 'package:aeris/src/models/service.dart'; import 'package:flutter/foundation.dart'; @@ -8,6 +9,6 @@ class ActionTemplate extends Action { {Key? key, required Service service, required String name, - Map parameters = const {}}) + List parameters = const []}) : super(service: service, name: name, parameters: parameters); } diff --git a/mobile/lib/src/models/reaction.dart b/mobile/lib/src/models/reaction.dart index 4eab3e9..a5415b0 100644 --- a/mobile/lib/src/models/reaction.dart +++ b/mobile/lib/src/models/reaction.dart @@ -1,6 +1,7 @@ // ignore_for_file: hash_and_equals import 'package:aeris/src/models/action.dart' as aeris_action; +import 'package:aeris/src/models/action_parameter.dart'; import 'package:flutter/widgets.dart'; import 'package:aeris/src/models/service.dart'; import 'package:tuple/tuple.dart'; @@ -11,12 +12,12 @@ class Reaction extends aeris_action.Action { {Key? key, required Service service, required String name, - Map parameters = const {}}) + List parameters = const []}) : super(service: service, name: name, parameters: parameters); /// Template trigger, used as an 'empty' trigger Reaction.template() - : super(service: Service.all()[0], name: '', parameters: {}); + : super(service: Service.all()[0], name: '', parameters: []); static Reaction fromJSON(Object reaction) { var reactionJSON = reaction as Map; @@ -25,8 +26,8 @@ class Reaction extends aeris_action.Action { return Reaction( service: service.item1, name: service.item2, - parameters: (reactionJSON['rParams'] as Map)['contents'] - as Map); + parameters: ActionParameter.fromJSON((reactionJSON['rParams'] as Map)['contents'] + as Map)); } @override @@ -34,8 +35,6 @@ class Reaction extends aeris_action.Action { Reaction otherReaction = other as Reaction; return service.name == otherReaction.service.name && name == otherReaction.name && - parameters.values.toString() == - otherReaction.parameters.values.toString() && - parameters.keys.toString() == otherReaction.parameters.keys.toString(); + parameters.map((e) => e.name).toString() == other.parameters.map((e) => e.name).toString(); } } diff --git a/mobile/lib/src/models/trigger.dart b/mobile/lib/src/models/trigger.dart index b00fb0a..3fbd619 100644 --- a/mobile/lib/src/models/trigger.dart +++ b/mobile/lib/src/models/trigger.dart @@ -1,5 +1,6 @@ // ignore_for_file: hash_and_equals +import 'package:aeris/src/models/action_parameter.dart'; import 'package:flutter/material.dart'; import 'package:aeris/src/main.dart'; import 'package:aeris/src/models/service.dart'; @@ -15,7 +16,7 @@ class Trigger extends aeris_action.Action { {Key? key, required Service service, required String name, - Map parameters = const {}, + List parameters = const [], this.last}) : super(service: service, name: name, parameters: parameters); @@ -30,8 +31,8 @@ class Trigger extends aeris_action.Action { service: service.item1, name: service.item2, last: last.year == 0 ? null : last, - parameters: (triggerJSON['pParams'] as Map)['contents'] - as Map); + parameters: ActionParameter.fromJSON((triggerJSON['pParams'] as Map)['contents'] + as Map)); } String lastToString() { @@ -46,7 +47,7 @@ class Trigger extends aeris_action.Action { /// Template trigger, used as an 'empty' trigger Trigger.template({Key? key, this.last}) - : super(service: const Service.twitter(), name: '', parameters: {}); + : super(service: const Service.twitter(), name: '', parameters: []); @override // ignore: avoid_renaming_method_parameters @@ -55,7 +56,6 @@ class Trigger extends aeris_action.Action { return service.name == other.service.name && name == other.name && last == other.last && - parameters.values.toString() == other.parameters.values.toString() && - parameters.keys.toString() == other.parameters.keys.toString(); + parameters.map((e) => e.name).toString() == other.parameters.map((e) => e.name).toString(); } } diff --git a/mobile/lib/src/views/setup_action_page.dart b/mobile/lib/src/views/setup_action_page.dart index bd5758f..ffecb55 100644 --- a/mobile/lib/src/views/setup_action_page.dart +++ b/mobile/lib/src/views/setup_action_page.dart @@ -1,3 +1,4 @@ +import 'package:aeris/src/models/action_parameter.dart'; import 'package:aeris/src/models/action_template.dart'; import 'package:aeris/src/aeris_api.dart'; import 'package:aeris/src/models/trigger.dart'; @@ -126,13 +127,13 @@ class _SetupActionPageState extends State { description: "This is the action's very very very very long description", ///TODO Find actual description name: availableAction.name, parametersNames: - availableAction.parameters.keys.toList(), + availableAction.parameters.map((e) =>e.name).toList(), initValues: widget.action.name == availableAction.name && availableAction.service.name == widget.action.service.name - ? widget.action.parameters : const {}, + ? widget.action.parameters.asMap().map((_, e) => MapEntry(e.name, e.value ?? "")) : const {}, onValidate: (parameters) { widget.action.service = serviceState!; - widget.action.parameters = parameters; + widget.action.parameters = ActionParameter.fromJSON(parameters); widget.action.name = availableAction.name; Navigator.of(context).pop(); }), From 11ab3e278241756bb6a3d7a29d3d745b2f8f50ee Mon Sep 17 00:00:00 2001 From: Arthi-chaud Date: Fri, 25 Feb 2022 11:54:39 +0100 Subject: [PATCH 022/109] Mobile Client: Made a entire class for Action parameters --- mobile/lib/src/aeris_api.dart | 2 +- mobile/lib/src/views/setup_action_page.dart | 14 ++++++++----- mobile/lib/src/widgets/action_form.dart | 22 ++++++++++----------- 3 files changed, 20 insertions(+), 18 deletions(-) diff --git a/mobile/lib/src/aeris_api.dart b/mobile/lib/src/aeris_api.dart index 44e93b6..fd322f7 100644 --- a/mobile/lib/src/aeris_api.dart +++ b/mobile/lib/src/aeris_api.dart @@ -124,7 +124,7 @@ class AerisAPI { name: "action$i", parameters: [ for (int j = 0; j < 3; j++) - ActionParameter(name: "key$j", value: "value$j", description: "description$j") + ActionParameter(name: "key$j", description: "description$j") ]) ]; } diff --git a/mobile/lib/src/views/setup_action_page.dart b/mobile/lib/src/views/setup_action_page.dart index ffecb55..90378b2 100644 --- a/mobile/lib/src/views/setup_action_page.dart +++ b/mobile/lib/src/views/setup_action_page.dart @@ -126,11 +126,15 @@ class _SetupActionPageState extends State { child: ActionForm( description: "This is the action's very very very very long description", ///TODO Find actual description name: availableAction.name, - parametersNames: - availableAction.parameters.map((e) =>e.name).toList(), - initValues: widget.action.name == availableAction.name - && availableAction.service.name == widget.action.service.name - ? widget.action.parameters.asMap().map((_, e) => MapEntry(e.name, e.value ?? "")) : const {}, + parameters: availableAction.parameters.map((param) { + if (widget.action.service.name == serviceState!.name && widget.action.name == availableAction.name) { + var previousParams = widget.action.parameters.where((element) => element.name == param.name); + if (previousParams.isNotEmpty) { + param.value = previousParams.first.value; + } + } + return param; + }).toList(), onValidate: (parameters) { widget.action.service = serviceState!; widget.action.parameters = ActionParameter.fromJSON(parameters); diff --git a/mobile/lib/src/widgets/action_form.dart b/mobile/lib/src/widgets/action_form.dart index ca7aea4..d563cf3 100644 --- a/mobile/lib/src/widgets/action_form.dart +++ b/mobile/lib/src/widgets/action_form.dart @@ -1,3 +1,4 @@ +import 'package:aeris/src/models/action_parameter.dart'; import 'package:flutter/material.dart'; import 'package:flutter_form_builder/flutter_form_builder.dart'; import 'package:form_builder_validators/form_builder_validators.dart'; @@ -7,10 +8,8 @@ import 'package:flutter_gen/gen_l10n/app_localizations.dart'; class ActionForm extends StatefulWidget { /// Name of the action final String name; - /// Names of the parameters - final List parametersNames; - /// Initial values of the fields - final Map initValues; + /// List of parameters, 'values' are used as default values + final List parameters; /// What the action does final String description; @@ -21,9 +20,8 @@ class ActionForm extends StatefulWidget { {Key? key, required this.name, required this.description, - required this.parametersNames, - required this.onValidate, - this.initValues = const {}}) + required this.parameters, + required this.onValidate}) : super(key: key); @override @@ -40,16 +38,16 @@ class _ActionFormState extends State { child: Column( children: [ Text(widget.description, textAlign: TextAlign.left, style: TextStyle(color: Theme.of(context).colorScheme.onSurface)), - ...widget.parametersNames.map((name) => FormBuilderTextField( - initialValue: (widget.initValues.containsKey(name)) ? widget.initValues[name] as String : null, - name: name, + ...widget.parameters.map((param) => FormBuilderTextField( + initialValue: param.value?.toString(), + name: param.name, decoration: InputDecoration( - labelText: name, + labelText: param.name, + helperText: param.description ), validator: FormBuilderValidators.compose([ FormBuilderValidators.required(context), ]), - keyboardType: (widget.initValues.containsKey(name)) && widget.initValues[name] is int ? TextInputType.number : null, )), ...[ ElevatedButton( From be9922e03cf92bf6f89c7801603002d78500f16f Mon Sep 17 00:00:00 2001 From: Arthi-chaud Date: Fri, 25 Feb 2022 14:06:39 +0100 Subject: [PATCH 023/109] Mobile Client: Setup action page: on expand, scroll --- mobile/lib/src/views/setup_action_page.dart | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/mobile/lib/src/views/setup_action_page.dart b/mobile/lib/src/views/setup_action_page.dart index 90378b2..8808102 100644 --- a/mobile/lib/src/views/setup_action_page.dart +++ b/mobile/lib/src/views/setup_action_page.dart @@ -106,7 +106,7 @@ class _SetupActionPageState extends State { if (availableActions == null) SkeletonLoader( builder: Card(shape: cardShape, child: const SizedBox(height: 40), elevation: 5), - items: 10, + items: 15, highlightColor: Theme.of(context).colorScheme.secondary ) else @@ -114,7 +114,8 @@ class _SetupActionPageState extends State { Card( elevation: 5, shape: cardShape, - child: ExpandablePanel( + child: ExpandableNotifier( + child: ScrollOnExpand(child: ExpandablePanel( header: Padding( padding: const EdgeInsets.only(left: 30, top: 20, bottom: 20), @@ -142,7 +143,7 @@ class _SetupActionPageState extends State { Navigator.of(context).pop(); }), )), - ), + ))), const SizedBox(height: 10) ] ], From ec725ff037fad6007533dd9bb6e8e2b6e50ab20c Mon Sep 17 00:00:00 2001 From: Arthi-chaud Date: Sat, 26 Feb 2022 10:15:12 +0100 Subject: [PATCH 024/109] Mobile Client: OAUth Links for services --- mobile/ios/Runner/Info-Debug.plist | 15 ++++ mobile/ios/Runner/Info-Release.plist | 15 ++++ mobile/lib/src/aeris_api.dart | 14 ++-- mobile/lib/src/models/service.dart | 57 ++++++++++--- mobile/lib/src/models/trigger.dart | 2 +- mobile/lib/src/views/home_page.dart | 110 +++++++++++++------------ mobile/lib/src/views/service_page.dart | 67 ++++++++------- 7 files changed, 176 insertions(+), 104 deletions(-) diff --git a/mobile/ios/Runner/Info-Debug.plist b/mobile/ios/Runner/Info-Debug.plist index 6cdf697..1f4b792 100644 --- a/mobile/ios/Runner/Info-Debug.plist +++ b/mobile/ios/Runner/Info-Debug.plist @@ -47,5 +47,20 @@ UIViewControllerBasedStatusBarAppearance + FlutterDeepLinkingEnabled + + CFBundleURLTypes + + + CFBundleTypeRole + Editor + CFBundleURLName + localhost:3000 + CFBundleURLSchemes + + http + + + diff --git a/mobile/ios/Runner/Info-Release.plist b/mobile/ios/Runner/Info-Release.plist index f759f12..911a728 100644 --- a/mobile/ios/Runner/Info-Release.plist +++ b/mobile/ios/Runner/Info-Release.plist @@ -45,5 +45,20 @@ UIStatusBarHidden + FlutterDeepLinkingEnabled + + CFBundleURLTypes + + + CFBundleTypeRole + Editor + CFBundleURLName + localhost:3000 + CFBundleURLSchemes + + http + + + \ No newline at end of file diff --git a/mobile/lib/src/aeris_api.dart b/mobile/lib/src/aeris_api.dart index 0717f1d..c8c4ad7 100644 --- a/mobile/lib/src/aeris_api.dart +++ b/mobile/lib/src/aeris_api.dart @@ -22,7 +22,7 @@ enum AerisAPIRequestType { get, post, put, delete } /// Call to interact with Aeris' Back end class AerisAPI { /// Get Connection state - bool _connected = false; + bool _connected = true; bool get isConnected => _connected; late List fakeAPI; @@ -35,23 +35,23 @@ class AerisAPI { AerisAPI() { baseRoute = dotenv.env['HOSTNAME']!; var trigger1 = Trigger( - service: const Service.spotify(), + service: Service.spotify(), name: "Play song", last: DateTime.now()); var trigger3 = Trigger( - service: const Service.discord(), + service: Service.discord(), name: "Send a message", last: DateTime.now()); var trigger2 = Trigger( - service: const Service.spotify(), + service: Service.spotify(), name: "Play song", last: DateTime.parse("2022-01-01")); var reaction = Reaction( - service: const Service.twitter(), parameters: [], name: "Post a tweet"); + service: Service.twitter(), parameters: [], name: "Post a tweet"); var reaction2 = Reaction( - service: const Service.gmail(), parameters: [], name: "Do smth"); + service: Service.gmail(), parameters: [], name: "Do smth"); var reaction1 = Reaction( - service: const Service.youtube(), parameters: [], name: "Do smth youtube"); + service: Service.youtube(), parameters: [], name: "Do smth youtube"); var pipeline1 = Pipeline( id: 10, name: "My Action", diff --git a/mobile/lib/src/models/service.dart b/mobile/lib/src/models/service.dart index 4564a99..60c45d8 100644 --- a/mobile/lib/src/models/service.dart +++ b/mobile/lib/src/models/service.dart @@ -1,6 +1,10 @@ // Class for a service (Youtube, Gmail, ...) +import 'dart:math'; + import 'package:cached_network_image/cached_network_image.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_dotenv/flutter_dotenv.dart'; +import 'dart:core'; /// Data class used to store data about a service (logo, url, name) class Service { @@ -13,6 +17,9 @@ class Service { ///URL To a service's logo final String logoUrl; + ///return the url to authenticate to service via OAuth2 + final String authUrl; + Widget getLogo({double logoSize = 40}) => ClipRRect( borderRadius: BorderRadius.circular(8.0), child: CachedNetworkImage( @@ -22,38 +29,62 @@ class Service { height: logoSize, )); - const Service.spotify() + static String _generateRandomString() { + var randomString = ""; + var seed = Random(); + var randomNumber = seed.nextInt(10) * 10; + + for (var i = 0; i < 20 + randomNumber; i++) { + randomString += String.fromCharCode(33 + (seed.nextInt(10) * 94)); + } + return randomString; + } + + Service.spotify() : name = "Spotify", url = "https://www.spotify.com", logoUrl = - "https://www.presse-citron.net/app/uploads/2020/06/spotify-une-.jpg"; - const Service.gmail() + "https://www.presse-citron.net/app/uploads/2020/06/spotify-une-.jpg", + authUrl = + "https://accounts.spotify.com/authorize?client_id=${dotenv.env['SPOTIFY_CLIENT_ID']}&response_type=code&redirect_uri=https://localhost:3000/authorization/spotify"; + Service.gmail() : name = "Gmail", url = "https://mail.google.com/", logoUrl = - "https://play-lh.googleusercontent.com/KSuaRLiI_FlDP8cM4MzJ23ml3og5Hxb9AapaGTMZ2GgR103mvJ3AAnoOFz1yheeQBBI"; - const Service.discord() + "https://play-lh.googleusercontent.com/KSuaRLiI_FlDP8cM4MzJ23ml3og5Hxb9AapaGTMZ2GgR103mvJ3AAnoOFz1yheeQBBI", + authUrl = ""; + + ///TODO find + Service.discord() : name = "Discord", url = "https://discord.com/app", logoUrl = - "https://play-lh.googleusercontent.com/fbrWR4LbtB_1Ulgz3_rw8bY3tx_zPU7A9ZOB5WYG_QmqOUUjA6JEzE_20GA4YBDWMx4"; - const Service.twitter() + "https://play-lh.googleusercontent.com/fbrWR4LbtB_1Ulgz3_rw8bY3tx_zPU7A9ZOB5WYG_QmqOUUjA6JEzE_20GA4YBDWMx4", + authUrl = + "https://discord.com/api/oauth2/authorize?response_type=code&client_id=${dotenv.env['DISCORD_CLIENT_ID']}&scope=applications.commands%20applications.entitlements%20applications.store.update%20bot%20guilds%20guilds.join%20guilds.members.read%20identify%20messages.read%20webhook.incoming&state=${_generateRandomString()}"; + Service.twitter() : name = "Twitter", url = "https://twitter.com", logoUrl = - "https://f.hellowork.com/blogdumoderateur/2019/11/twitter-logo-1200x1200.jpg"; - const Service.github() + "https://f.hellowork.com/blogdumoderateur/2019/11/twitter-logo-1200x1200.jpg", + authUrl = + "https://twitter.com/i/oauth2/authorize?response_type=code&client_id=${dotenv.env['TWITTER_CLIENT_ID']}&redirect_uri=https://localhost:3000/authorization/twitter&scope=tweet.read%20users.read%20offline.access&state=${_generateRandomString()}&code_challenge=challenge&code_challenge_method=plain"; + Service.github() : name = "GitHub", url = "https://github.com/", - logoUrl = "https://avatars.githubusercontent.com/u/9919?s=280&v=4"; - const Service.youtube() + logoUrl = "https://avatars.githubusercontent.com/u/9919?s=280&v=4", + authUrl = + "https://github.com/login/oauth/authorize?client_id=${dotenv.env['GITHUB_CLIENT_ID']}&response_type=code&redirect_uri=http://localhost:3000/authorization/github"; + Service.youtube() : name = "Youtube", url = "https://youtube.com", logoUrl = - "https://play-lh.googleusercontent.com/lMoItBgdPPVDJsNOVtP26EKHePkwBg-PkuY9NOrc-fumRtTFP4XhpUNk_22syN4Datc"; + "https://play-lh.googleusercontent.com/lMoItBgdPPVDJsNOVtP26EKHePkwBg-PkuY9NOrc-fumRtTFP4XhpUNk_22syN4Datc", + authUrl = + "https://accounts.google.com/o/oauth2/v2/auth?response_type=code&client_id=${dotenv.env['GOOGLE_CLIENT_ID']}&scope=openid%20email&redirect_uri=http://localhost:3000/authorization/google&state=${_generateRandomString()}"; /// Returns a list of all the available services - static all() => const [ + static all() => [ Service.discord(), Service.github(), Service.gmail(), diff --git a/mobile/lib/src/models/trigger.dart b/mobile/lib/src/models/trigger.dart index 3fbd619..6b37b5f 100644 --- a/mobile/lib/src/models/trigger.dart +++ b/mobile/lib/src/models/trigger.dart @@ -47,7 +47,7 @@ class Trigger extends aeris_action.Action { /// Template trigger, used as an 'empty' trigger Trigger.template({Key? key, this.last}) - : super(service: const Service.twitter(), name: '', parameters: []); + : super(service: Service.twitter(), name: '', parameters: []); @override // ignore: avoid_renaming_method_parameters diff --git a/mobile/lib/src/views/home_page.dart b/mobile/lib/src/views/home_page.dart index 5aaef98..c964d11 100644 --- a/mobile/lib/src/views/home_page.dart +++ b/mobile/lib/src/views/home_page.dart @@ -30,61 +30,69 @@ class _HomePageState extends State { Widget serviceActionButtons = IconButton( icon: const Icon(Icons.electrical_services), - onPressed: () => showAerisCardPage(context, (context) => const ServicePage()) - ); + onPressed: () => + showAerisCardPage(context, (context) => ServicePage())); Widget logoutActionButton = IconButton( icon: const Icon(Icons.logout), onPressed: () => showDialog( - context: context, - builder: (BuildContext context) => WarningDialog( - message: AppLocalizations.of(context).logoutWarningMessage, - onAccept: () { - GetIt.I().stopConnection(); - Navigator.of(context).popAndPushNamed('/'); - }, - warnedAction: AppLocalizations.of(context).logout - ) - ), + context: context, + builder: (BuildContext context) => WarningDialog( + message: AppLocalizations.of(context).logoutWarningMessage, + onAccept: () { + GetIt.I().stopConnection(); + Navigator.of(context).popAndPushNamed('/'); + }, + warnedAction: AppLocalizations.of(context).logout)), ); return Consumer( - builder: (context, provider, _) => AerisPage( - floatingActionButton: FloatingActionButton( - onPressed: () => showAerisCardPage(context, (_) => const CreatePipelinePage()), - backgroundColor: Theme.of(context).colorScheme.secondary, - elevation: 10, - child: const Icon(Icons.add), - ), - actions: [ - HomePageSortMenu( - collectionProvider: provider, - ), - serviceActionButtons, - logoutActionButton - ], - body: provider.initialized == false - ? ListView(physics: const BouncingScrollPhysics(), - padding: const EdgeInsets.only(bottom: 20, top: 20, left: 10, right: 10), - children: [SkeletonLoader( - builder: ClickableCard(onTap:(){}, body: const SizedBox(height: 80)), - items: 10, - highlightColor: Theme.of(context).colorScheme.secondary - )]) - : LiquidPullToRefresh( - borderWidth: 2, - animSpeedFactor: 3, - color: Colors.transparent, - showChildOpacityTransition: false, - onRefresh: () => provider.fetchPipelines() - .then((_) => setState(() {})), // refresh callback - child: ListView.builder( - physics: const BouncingScrollPhysics(), - padding: const EdgeInsets.only(bottom: 20, top: 20, left: 10, right: 10), - controller: listController, - itemCount: provider.pipelineCount, - itemBuilder: (BuildContext context, int index) => - PipelineCard(pipeline: provider.getPipelineAt(index), - ), - )), - )); + builder: (context, provider, _) => AerisPage( + floatingActionButton: FloatingActionButton( + onPressed: () => showAerisCardPage( + context, (_) => const CreatePipelinePage()), + backgroundColor: Theme.of(context).colorScheme.secondary, + elevation: 10, + child: const Icon(Icons.add), + ), + actions: [ + HomePageSortMenu( + collectionProvider: provider, + ), + serviceActionButtons, + logoutActionButton + ], + body: provider.initialized == false + ? ListView( + physics: const BouncingScrollPhysics(), + padding: const EdgeInsets.only( + bottom: 20, top: 20, left: 10, right: 10), + children: [ + SkeletonLoader( + builder: ClickableCard( + onTap: () {}, + body: const SizedBox(height: 80)), + items: 10, + highlightColor: + Theme.of(context).colorScheme.secondary) + ]) + : LiquidPullToRefresh( + borderWidth: 2, + animSpeedFactor: 3, + color: Colors.transparent, + showChildOpacityTransition: false, + onRefresh: () => provider + .fetchPipelines() + .then((_) => setState(() {})), // refresh callback + child: ListView.builder( + physics: const BouncingScrollPhysics(), + padding: const EdgeInsets.only( + bottom: 20, top: 20, left: 10, right: 10), + controller: listController, + itemCount: provider.pipelineCount, + itemBuilder: (BuildContext context, int index) => + PipelineCard( + pipeline: provider.getPipelineAt(index), + ), + )), + )); } } diff --git a/mobile/lib/src/views/service_page.dart b/mobile/lib/src/views/service_page.dart index 4cee766..a781498 100644 --- a/mobile/lib/src/views/service_page.dart +++ b/mobile/lib/src/views/service_page.dart @@ -9,11 +9,13 @@ import 'package:aeris/src/widgets/warning_dialog.dart'; import 'package:get_it/get_it.dart'; import 'package:provider/provider.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; +import 'package:url_launcher/url_launcher.dart'; ///Page listing connected & available services class ServicePage extends StatelessWidget { - const ServicePage({Key? key}) : super(key: key); + ServicePage({Key? key}) : super(key: key); + ///TODO from an api call, determine what services are plugged List getServiceGroup(String groupName, Icon trailingIcon, void Function(Service) onTap, BuildContext context) { UserServiceProvider uServicesProvider = @@ -41,7 +43,7 @@ class ServicePage extends StatelessWidget { @override Widget build(BuildContext context) { - List services = const [ + List services = [ Service.discord(), Service.gmail(), Service.github(), @@ -59,39 +61,40 @@ class ServicePage extends StatelessWidget { return Consumer( builder: (context, provider, _) => AerisCardPage( body: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - ...[ - Align( - alignment: Alignment.center, - child: Text(AppLocalizations.of(context).services, style: const TextStyle(fontSize: 25)), - ), - const SizedBox(height: 60) - ], - ...getServiceGroup( - AppLocalizations.of(context).connected, - const Icon(Icons.delete, color: Colors.red), - (Service service) => showDialog( - context: context, - builder: (BuildContext context) => WarningDialog( - message: AppLocalizations.of(context) - .disconnectServiceWarningMessage, - onAccept: () => GetIt.I() + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + ...[ + Align( + alignment: Alignment.center, + child: Text(AppLocalizations.of(context).services, + style: const TextStyle(fontSize: 25)), + ), + const SizedBox(height: 60) + ], + ...getServiceGroup( + AppLocalizations.of(context).connected, + const Icon(Icons.delete, color: Colors.red), + (Service service) => showDialog( + context: context, + builder: (BuildContext context) => WarningDialog( + message: AppLocalizations.of(context) + .disconnectServiceWarningMessage, + onAccept: () => GetIt.I() .disconnectService(service) .then((_) => provider.fetchPipelines()), - warnedAction: - AppLocalizations.of(context).disconnect)), - context), - ...getServiceGroup( - AppLocalizations.of(context).available, - const Icon(Icons.connect_without_contact, - color: Colors.green), - (Service service) => + warnedAction: AppLocalizations.of(context).disconnect)), + context), + ...getServiceGroup( + AppLocalizations.of(context).available, + const Icon(Icons.connect_without_contact, color: Colors.green), + (Service service) => { print("Connected") /* TODO open page to connect service*/, - context), - ], - ), + launch(Uri.parse(service.authUrl).toString()) + }, + context), + ], ), - ); + ), + ); } } From c4a18ce28c250fc07050683c4208a3269b254b68 Mon Sep 17 00:00:00 2001 From: Arthi-chaud Date: Sat, 26 Feb 2022 18:13:09 +0100 Subject: [PATCH 025/109] Mobile Client: On OAuth, redirect to page --- mobile/ios/Runner.xcodeproj/project.pbxproj | 4 ++-- mobile/ios/Runner/Info-Debug.plist | 4 ++-- mobile/ios/Runner/Info-Release.plist | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/mobile/ios/Runner.xcodeproj/project.pbxproj b/mobile/ios/Runner.xcodeproj/project.pbxproj index b95f859..e5f44b1 100644 --- a/mobile/ios/Runner.xcodeproj/project.pbxproj +++ b/mobile/ios/Runner.xcodeproj/project.pbxproj @@ -217,7 +217,7 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin\n"; }; 755B6DB6A94E09EAD68F291E /* [CP] Embed Pods Frameworks */ = { isa = PBXShellScriptBuildPhase; @@ -248,7 +248,7 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build\n"; }; /* End PBXShellScriptBuildPhase section */ diff --git a/mobile/ios/Runner/Info-Debug.plist b/mobile/ios/Runner/Info-Debug.plist index 1f4b792..d668aca 100644 --- a/mobile/ios/Runner/Info-Debug.plist +++ b/mobile/ios/Runner/Info-Debug.plist @@ -55,10 +55,10 @@ CFBundleTypeRole Editor CFBundleURLName - localhost:3000 + aeris.com CFBundleURLSchemes - http + aeris diff --git a/mobile/ios/Runner/Info-Release.plist b/mobile/ios/Runner/Info-Release.plist index 911a728..e4d7cb3 100644 --- a/mobile/ios/Runner/Info-Release.plist +++ b/mobile/ios/Runner/Info-Release.plist @@ -53,10 +53,10 @@ CFBundleTypeRole Editor CFBundleURLName - localhost:3000 + aeris.com CFBundleURLSchemes - http + aeris From 2f14e3a48905a98cdfe9a5167aa76cd27bf502ca Mon Sep 17 00:00:00 2001 From: Arthi-chaud Date: Sat, 26 Feb 2022 18:50:38 +0100 Subject: [PATCH 026/109] Mobile Client: Animation on service push --- mobile/lib/src/aeris_api.dart | 7 +++++ mobile/lib/src/main.dart | 29 +++++++++++--------- mobile/lib/src/models/service.dart | 10 +++---- mobile/lib/src/views/authorization_page.dart | 27 ++++++++++++++++++ mobile/lib/src/views/service_page.dart | 3 +- mobile/pubspec.lock | 7 +++++ mobile/pubspec.yaml | 1 + 7 files changed, 64 insertions(+), 20 deletions(-) create mode 100644 mobile/lib/src/views/authorization_page.dart diff --git a/mobile/lib/src/aeris_api.dart b/mobile/lib/src/aeris_api.dart index c8c4ad7..dc22043 100644 --- a/mobile/lib/src/aeris_api.dart +++ b/mobile/lib/src/aeris_api.dart @@ -197,6 +197,13 @@ class AerisAPI { return res.ok; } + /// Connects the user from the service + Future connectService(Service service, String code) async { + var res = await _requestAPI('/auth/${service.name.toLowerCase()}?code=$code', + AerisAPIRequestType.get, null); + return res.ok; + } + Future> getActionsFor( Service service, Action action) async { await Future.delayed(const Duration(seconds: 3)); diff --git a/mobile/lib/src/main.dart b/mobile/lib/src/main.dart index 76f2206..0e800c4 100644 --- a/mobile/lib/src/main.dart +++ b/mobile/lib/src/main.dart @@ -1,4 +1,5 @@ import 'package:aeris/src/aeris_api.dart'; +import 'package:aeris/src/views/authorization_page.dart'; import 'package:flutter_dotenv/flutter_dotenv.dart'; import 'package:flutter_localizations/flutter_localizations.dart'; import 'package:form_builder_validators/localization/l10n.dart'; @@ -12,6 +13,7 @@ import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:get_it/get_it.dart'; +import 'package:aeris/src/models/service.dart'; void main() async { await dotenv.load(fileName: ".env"); @@ -44,29 +46,30 @@ class Aeris extends StatelessWidget { ], supportedLocales: const [Locale('fr', ''), Locale('en', '')], theme: ThemeData(colorScheme: aerisScheme), - initialRoute: '/', + initialRoute: GetIt.I().isConnected ? '/home' : '/', onGenerateRoute: (settings) { Map routes = { '/': () => const StartupPage(), '/login': () => const LoginPage(), '/home': () => const HomePage(), }; + return PageRouteBuilder( opaque: false, settings: settings, - pageBuilder: (_, __, ___) => routes[settings.name].call(), + pageBuilder: (_, __, ___) { + if (settings.name!.startsWith('/authorization')) { + return const AuthorizationPage(); + } + return routes[settings.name].call(); + }, transitionDuration: const Duration(milliseconds: 350), - transitionsBuilder: (context, animation, secondaryAnimation, child) => - SlideTransition( - child: child, - position: animation.drive( - Tween( - begin: const Offset(1.0, 0.0), - end: Offset.zero - ) - ) - ) - ); + transitionsBuilder: (context, animation, secondaryAnimation, + child) => + SlideTransition( + child: child, + position: animation.drive(Tween( + begin: const Offset(1.0, 0.0), end: Offset.zero)))); }); } } diff --git a/mobile/lib/src/models/service.dart b/mobile/lib/src/models/service.dart index 60c45d8..cf85377 100644 --- a/mobile/lib/src/models/service.dart +++ b/mobile/lib/src/models/service.dart @@ -46,7 +46,7 @@ class Service { logoUrl = "https://www.presse-citron.net/app/uploads/2020/06/spotify-une-.jpg", authUrl = - "https://accounts.spotify.com/authorize?client_id=${dotenv.env['SPOTIFY_CLIENT_ID']}&response_type=code&redirect_uri=https://localhost:3000/authorization/spotify"; + "https://accounts.spotify.com/authorize?client_id=${dotenv.env['SPOTIFY_CLIENT_ID']}&response_type=code&redirect_uri=aeris://aeris.com/authorization/spotify"; Service.gmail() : name = "Gmail", url = "https://mail.google.com/", @@ -68,23 +68,23 @@ class Service { logoUrl = "https://f.hellowork.com/blogdumoderateur/2019/11/twitter-logo-1200x1200.jpg", authUrl = - "https://twitter.com/i/oauth2/authorize?response_type=code&client_id=${dotenv.env['TWITTER_CLIENT_ID']}&redirect_uri=https://localhost:3000/authorization/twitter&scope=tweet.read%20users.read%20offline.access&state=${_generateRandomString()}&code_challenge=challenge&code_challenge_method=plain"; + "https://twitter.com/i/oauth2/authorize?response_type=code&client_id=${dotenv.env['TWITTER_CLIENT_ID']}&redirect_uri=aeris://aeris.com/authorization/twitter&scope=tweet.read%20users.read%20offline.access&state=${_generateRandomString()}&code_challenge=challenge&code_challenge_method=plain"; Service.github() : name = "GitHub", url = "https://github.com/", logoUrl = "https://avatars.githubusercontent.com/u/9919?s=280&v=4", authUrl = - "https://github.com/login/oauth/authorize?client_id=${dotenv.env['GITHUB_CLIENT_ID']}&response_type=code&redirect_uri=http://localhost:3000/authorization/github"; + "https://github.com/login/oauth/authorize?client_id=${dotenv.env['GITHUB_CLIENT_ID']}&response_type=code&redirect_uri=aeris://aeris.com/authorization/github"; Service.youtube() : name = "Youtube", url = "https://youtube.com", logoUrl = "https://play-lh.googleusercontent.com/lMoItBgdPPVDJsNOVtP26EKHePkwBg-PkuY9NOrc-fumRtTFP4XhpUNk_22syN4Datc", authUrl = - "https://accounts.google.com/o/oauth2/v2/auth?response_type=code&client_id=${dotenv.env['GOOGLE_CLIENT_ID']}&scope=openid%20email&redirect_uri=http://localhost:3000/authorization/google&state=${_generateRandomString()}"; + "https://accounts.google.com/o/oauth2/v2/auth?response_type=code&client_id=${dotenv.env['GOOGLE_CLIENT_ID']}&scope=openid%20email&redirect_uri=aeris://aeric.com/authorization/google&state=${_generateRandomString()}"; /// Returns a list of all the available services - static all() => [ + static List all() => [ Service.discord(), Service.github(), Service.gmail(), diff --git a/mobile/lib/src/views/authorization_page.dart b/mobile/lib/src/views/authorization_page.dart new file mode 100644 index 0000000..8b9c984 --- /dev/null +++ b/mobile/lib/src/views/authorization_page.dart @@ -0,0 +1,27 @@ +import 'package:aeris/src/aeris_api.dart'; +import 'package:aeris/src/models/service.dart'; +import 'package:flutter/material.dart'; +import 'package:get_it/get_it.dart'; +import 'package:loading_indicator/loading_indicator.dart'; + +class AuthorizationPage extends StatelessWidget { + const AuthorizationPage({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + final route = ModalRoute.of(context)!.settings.name!; + final code = Uri.parse(route).queryParameters['code']!; + final serviceName = Uri.parse(route).pathSegments.last; + final service = Service.all() + .firstWhere((element) => element.name.toLowerCase() == serviceName); + GetIt.I() + .connectService(service, code) + .then((_) => Navigator.pop(context)); + return Container( + alignment: Alignment.center, + child: LoadingIndicator( + indicatorType: Indicator.ballClipRotateMultiple, + colors: [Theme.of(context).colorScheme.secondary], + )); + } +} diff --git a/mobile/lib/src/views/service_page.dart b/mobile/lib/src/views/service_page.dart index a781498..4fc11ed 100644 --- a/mobile/lib/src/views/service_page.dart +++ b/mobile/lib/src/views/service_page.dart @@ -88,8 +88,7 @@ class ServicePage extends StatelessWidget { AppLocalizations.of(context).available, const Icon(Icons.connect_without_contact, color: Colors.green), (Service service) => { - print("Connected") /* TODO open page to connect service*/, - launch(Uri.parse(service.authUrl).toString()) + launch(Uri.parse(service.authUrl).toString(), forceSafariVC: false) }, context), ], diff --git a/mobile/pubspec.lock b/mobile/pubspec.lock index 03e6c74..a79b985 100644 --- a/mobile/pubspec.lock +++ b/mobile/pubspec.lock @@ -315,6 +315,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "3.0.1" + loading_indicator: + dependency: "direct main" + description: + name: loading_indicator + url: "https://pub.dartlang.org" + source: hosted + version: "3.0.3" matcher: dependency: transitive description: diff --git a/mobile/pubspec.yaml b/mobile/pubspec.yaml index 3ca3ca5..2ca084b 100644 --- a/mobile/pubspec.yaml +++ b/mobile/pubspec.yaml @@ -57,6 +57,7 @@ dependencies: http: ^0.13.4 tuple: ^2.0.0 flutter_dotenv: ^5.0.2 + loading_indicator: ^3.0.3 dev_dependencies: flutter_launcher_icons: "^0.9.2" From a5395b885e4ce53375ded74405e052f744b48e0f Mon Sep 17 00:00:00 2001 From: Arthi-chaud Date: Sun, 27 Feb 2022 16:00:06 +0100 Subject: [PATCH 027/109] Mobile Client: fix default route --- mobile/lib/src/main.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mobile/lib/src/main.dart b/mobile/lib/src/main.dart index 7b0c6ab..abe5c36 100644 --- a/mobile/lib/src/main.dart +++ b/mobile/lib/src/main.dart @@ -45,7 +45,7 @@ class Aeris extends StatelessWidget { ], supportedLocales: const [Locale('fr', ''), Locale('en', '')], theme: ThemeData(colorScheme: aerisScheme), - initialRoute: GetIt.I().isConnected ? '/home' : '/', + initialRoute: '/', onGenerateRoute: (settings) { Map routes = { '/': () => const StartupPage(), From 697e2a6a58ff08b95e48a1b17ed832166d95de6d Mon Sep 17 00:00:00 2001 From: Arthi-chaud Date: Mon, 28 Feb 2022 07:26:32 +0100 Subject: [PATCH 028/109] Mobile Client: fix default route --- .env.example | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.env.example b/.env.example index 7c414af..ada2f65 100644 --- a/.env.example +++ b/.env.example @@ -4,6 +4,8 @@ POSTGRES_PORT= POSTGRES_DB= POSTGRES_HOST=db +HOSTNAME= + YOUTUBE_KEY= WORKER_API_KEY= From 8bd95c80a9b179c948ddfb3bf432c7b9dd94ae12 Mon Sep 17 00:00:00 2001 From: Arthi-chaud Date: Mon, 28 Feb 2022 07:33:58 +0100 Subject: [PATCH 029/109] Mobile Client: API: error handling --- mobile/lib/src/aeris_api.dart | 29 +++++++++++++---------------- 1 file changed, 13 insertions(+), 16 deletions(-) diff --git a/mobile/lib/src/aeris_api.dart b/mobile/lib/src/aeris_api.dart index dc22043..7fcf891 100644 --- a/mobile/lib/src/aeris_api.dart +++ b/mobile/lib/src/aeris_api.dart @@ -22,7 +22,7 @@ enum AerisAPIRequestType { get, post, put, delete } /// Call to interact with Aeris' Back end class AerisAPI { /// Get Connection state - bool _connected = true; + bool _connected = true; //TODO Will be false later bool get isConnected => _connected; late List fakeAPI; @@ -35,9 +35,7 @@ class AerisAPI { AerisAPI() { baseRoute = dotenv.env['HOSTNAME']!; var trigger1 = Trigger( - service: Service.spotify(), - name: "Play song", - last: DateTime.now()); + service: Service.spotify(), name: "Play song", last: DateTime.now()); var trigger3 = Trigger( service: Service.discord(), name: "Send a message", @@ -48,8 +46,8 @@ class AerisAPI { last: DateTime.parse("2022-01-01")); var reaction = Reaction( service: Service.twitter(), parameters: [], name: "Post a tweet"); - var reaction2 = Reaction( - service: Service.gmail(), parameters: [], name: "Do smth"); + var reaction2 = + Reaction(service: Service.gmail(), parameters: [], name: "Do smth"); var reaction1 = Reaction( service: Service.youtube(), parameters: [], name: "Do smth youtube"); var pipeline1 = Pipeline( @@ -183,9 +181,9 @@ class AerisAPI { /// Fetches the Pipelines from the API Future> getPipelines() async { var res = await _requestAPI('/workflows', AerisAPIRequestType.get, null); + if (res.ok == false) return []; List body = jsonDecode(res.body); - ///TODO error handling ///TODO return body.map((e) => Pipeline.fromJSON(e as Map)).toList(); return fakeAPI; } @@ -199,8 +197,10 @@ class AerisAPI { /// Connects the user from the service Future connectService(Service service, String code) async { - var res = await _requestAPI('/auth/${service.name.toLowerCase()}?code=$code', - AerisAPIRequestType.get, null); + var res = await _requestAPI( + '/auth/${service.name.toLowerCase()}?code=$code', + AerisAPIRequestType.get, + null); return res.ok; } @@ -214,13 +214,10 @@ class AerisAPI { } return [ for (int i = 0; i <= 10; i++) - ActionTemplate( - service: service, - name: "action$i", - parameters: [ - for (int j = 0; j < 3; j++) - ActionParameter(name: "key$j", description: "description$j") - ]) + ActionTemplate(service: service, name: "action$i", parameters: [ + for (int j = 0; j < 3; j++) + ActionParameter(name: "key$j", description: "description$j") + ]) ]; } From d41fd1dac55308c30b10412a411438ebde219f14 Mon Sep 17 00:00:00 2001 From: Arthi-chaud Date: Mon, 28 Feb 2022 09:25:28 +0100 Subject: [PATCH 030/109] Mobile Client: Service Provider --- .../lib/src/providers/services_provider.dart | 41 ++++++++++ .../src/providers/user_services_provider.dart | 80 ------------------- 2 files changed, 41 insertions(+), 80 deletions(-) create mode 100644 mobile/lib/src/providers/services_provider.dart delete mode 100644 mobile/lib/src/providers/user_services_provider.dart diff --git a/mobile/lib/src/providers/services_provider.dart b/mobile/lib/src/providers/services_provider.dart new file mode 100644 index 0000000..2454297 --- /dev/null +++ b/mobile/lib/src/providers/services_provider.dart @@ -0,0 +1,41 @@ +import 'package:aeris/src/aeris_api.dart'; +import 'package:aeris/src/models/service.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:get_it/get_it.dart'; + +/// Provider used to store every Service the User is authenticated to +class ServiceProvider extends ChangeNotifier { + /// List of [Service] related to the user + List _connectedServices = []; + List get connectedServices => _connectedServices; + + /// Get the services the user is not connected to + List get availableServices => Service + .all() + .where((element) => !_connectedServices.contains(element)).toList(); + + ServiceProvider() { + refreshServices(); + } + + /// Adds a service into the Provider + addService(Service service) { + _connectedServices.add(service); + notifyListeners(); + } + + /// Refresh services from API + refreshServices() { + GetIt.I().getConnectedService().then((value) { + _connectedServices = value; + notifyListeners(); + }); + } + + /// Removes a service from the Provider, and calls API + removeService(Service service) { + GetIt.I().disconnectService(service); + _connectedServices.remove(service); + notifyListeners(); + } +} diff --git a/mobile/lib/src/providers/user_services_provider.dart b/mobile/lib/src/providers/user_services_provider.dart deleted file mode 100644 index 1c941d7..0000000 --- a/mobile/lib/src/providers/user_services_provider.dart +++ /dev/null @@ -1,80 +0,0 @@ -import 'package:aeris/src/models/user_service.dart'; -import 'package:aeris/src/models/service.dart'; -import 'package:flutter/cupertino.dart'; - -/// Provider used to store every Service related to the User -class UserServiceProvider extends ChangeNotifier { - /// List of [Service] related to the user - List userServices = []; - - /// Adds a service into the Provider - addServiceForUser(UserService newService) { - userServices.add(newService); - notifyListeners(); - } - - /// Creates a new service related to the user - createUserService(Service serviceToSet, - {String accountId = "", - String accUsername = "", - String accountSlug = "", - String externalToken = ""}) { - UserService newService = UserService( - serviceAccountId: accountId, - accountUsername: accUsername, - accountSlug: accountSlug, - userExternalToken: externalToken, - serviceProvider: serviceToSet); - userServices.add(newService); - // notifyListeners(); /// TODO Get the notifyListeners method back. - } - - /// Sets a list of service into the Provider - setServiceForUser(List newServices) { - userServices = []; - userServices = newServices; - notifyListeners(); - } - - /// Modifies a service given as argument - modifyService(UserService toModify, String serviceAccountId, - String accountUsername, String accountSlug, String userExternalToken) { - for (int i = 0; i < userServices.length; i++) { - if (userServices[i].serviceProvider.name == - toModify.serviceProvider.name && - userServices[i].serviceAccountId == toModify.serviceAccountId && - userServices[i].userExternalToken == toModify.userExternalToken) { - UserService newService = UserService( - serviceProvider: userServices[i].serviceProvider, - serviceAccountId: serviceAccountId, - accountUsername: accountUsername, - accountSlug: accountSlug, - userExternalToken: userExternalToken); - userServices[i] = newService; - notifyListeners(); - return true; - } - } - return false; - } - - /// Removes a service from the Provider - removeService(UserService toRemove) { - for (UserService uService in userServices) { - if (uService.serviceProvider.name == toRemove.serviceProvider.name && - uService.serviceAccountId == toRemove.serviceAccountId && - uService.userExternalToken == toRemove.userExternalToken) { - userServices.remove(uService); - notifyListeners(); - return true; - } - } - return false; - } - - /// Clears Provider from data - clearProvider() { - userServices.clear(); - notifyListeners(); - } -} From 0c6b852589b07a9b61069105c9fa32ac85dbd9a4 Mon Sep 17 00:00:00 2001 From: Arthi-chaud Date: Mon, 28 Feb 2022 09:29:42 +0100 Subject: [PATCH 031/109] Mobile Client: Service Provider in main --- mobile/lib/src/main.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mobile/lib/src/main.dart b/mobile/lib/src/main.dart index abe5c36..3b0caee 100644 --- a/mobile/lib/src/main.dart +++ b/mobile/lib/src/main.dart @@ -4,7 +4,7 @@ import 'package:flutter_dotenv/flutter_dotenv.dart'; import 'package:flutter_localizations/flutter_localizations.dart'; import 'package:form_builder_validators/localization/l10n.dart'; import 'package:aeris/src/providers/pipelines_provider.dart'; -import 'package:aeris/src/providers/user_services_provider.dart'; +import 'package:aeris/src/providers/services_provider.dart'; import 'package:aeris/src/views/startup_page.dart'; import 'package:aeris/src/views/login_page.dart'; import 'package:aeris/src/views/home_page.dart'; @@ -21,7 +21,7 @@ void main() async { await interface.restoreConnection(); runApp(MultiProvider(providers: [ ChangeNotifierProvider(create: (_) => PipelineProvider()), - ChangeNotifierProvider(create: (_) => UserServiceProvider()) + ChangeNotifierProvider(create: (_) => ServiceProvider()) ], child: const Aeris())); } From f3ef04326a78c4b6fb42a3747a87c4aeb88fac4f Mon Sep 17 00:00:00 2001 From: Arthi-chaud Date: Mon, 28 Feb 2022 09:36:38 +0100 Subject: [PATCH 032/109] Mobile Client: Service Management --- mobile/lib/src/aeris_api.dart | 8 +++ mobile/lib/src/models/user_service.dart | 28 ----------- .../lib/src/providers/services_provider.dart | 13 ++--- mobile/lib/src/views/authorization_page.dart | 9 ++-- mobile/lib/src/views/service_page.dart | 50 ++++++------------- 5 files changed, 37 insertions(+), 71 deletions(-) delete mode 100644 mobile/lib/src/models/user_service.dart diff --git a/mobile/lib/src/aeris_api.dart b/mobile/lib/src/aeris_api.dart index 7fcf891..eaa5f0f 100644 --- a/mobile/lib/src/aeris_api.dart +++ b/mobile/lib/src/aeris_api.dart @@ -188,6 +188,14 @@ class AerisAPI { return fakeAPI; } + /// Fetch the services the user is authenticated to + Future> getConnectedService() async { + var res = await _requestAPI('', ///TODO Get route to fetch connected services + AerisAPIRequestType.get, null); + if (!res.ok) return []; + return Service.all(); ///TODO Return real services + } + /// Disconnects the user from the service Future disconnectService(Service service) async { var res = await _requestAPI('/auth/${service.name.toLowerCase()}', diff --git a/mobile/lib/src/models/user_service.dart b/mobile/lib/src/models/user_service.dart deleted file mode 100644 index 195bf1c..0000000 --- a/mobile/lib/src/models/user_service.dart +++ /dev/null @@ -1,28 +0,0 @@ -// Class for a service related to a user (Youtube, Gmail, ...) -import 'package:aeris/src/models/service.dart'; -import 'package:flutter/cupertino.dart'; - -class UserService { - /// Service name related to the user - final Service serviceProvider; - - /// Id of an user for this service - final String serviceAccountId; - - /// Account Username - final String accountUsername; - - /// Account Slug for this Service - final String accountSlug; - - /// Account External Token for this Service - final String userExternalToken; - - UserService( - {Key? key, - required this.serviceAccountId, - required this.accountUsername, - required this.accountSlug, - required this.userExternalToken, - required this.serviceProvider}); -} diff --git a/mobile/lib/src/providers/services_provider.dart b/mobile/lib/src/providers/services_provider.dart index 2454297..cccd9ec 100644 --- a/mobile/lib/src/providers/services_provider.dart +++ b/mobile/lib/src/providers/services_provider.dart @@ -10,18 +10,19 @@ class ServiceProvider extends ChangeNotifier { List get connectedServices => _connectedServices; /// Get the services the user is not connected to - List get availableServices => Service - .all() - .where((element) => !_connectedServices.contains(element)).toList(); + List get availableServices => Service.all() + .where((element) => !_connectedServices.contains(element)) + .toList(); ServiceProvider() { refreshServices(); } /// Adds a service into the Provider - addService(Service service) { + addService(Service service, String code) async { _connectedServices.add(service); notifyListeners(); + await GetIt.I().connectService(service, code); } /// Refresh services from API @@ -33,9 +34,9 @@ class ServiceProvider extends ChangeNotifier { } /// Removes a service from the Provider, and calls API - removeService(Service service) { - GetIt.I().disconnectService(service); + removeService(Service service) async { _connectedServices.remove(service); notifyListeners(); + await GetIt.I().disconnectService(service); } } diff --git a/mobile/lib/src/views/authorization_page.dart b/mobile/lib/src/views/authorization_page.dart index 8b9c984..935e795 100644 --- a/mobile/lib/src/views/authorization_page.dart +++ b/mobile/lib/src/views/authorization_page.dart @@ -1,8 +1,10 @@ import 'package:aeris/src/aeris_api.dart'; import 'package:aeris/src/models/service.dart'; +import 'package:aeris/src/providers/services_provider.dart'; import 'package:flutter/material.dart'; import 'package:get_it/get_it.dart'; import 'package:loading_indicator/loading_indicator.dart'; +import 'package:provider/provider.dart'; class AuthorizationPage extends StatelessWidget { const AuthorizationPage({Key? key}) : super(key: key); @@ -14,9 +16,10 @@ class AuthorizationPage extends StatelessWidget { final serviceName = Uri.parse(route).pathSegments.last; final service = Service.all() .firstWhere((element) => element.name.toLowerCase() == serviceName); - GetIt.I() - .connectService(service, code) - .then((_) => Navigator.pop(context)); + + context.read().addService(service, code).then( + (_) => Navigator.pop(context) + ); return Container( alignment: Alignment.center, child: LoadingIndicator( diff --git a/mobile/lib/src/views/service_page.dart b/mobile/lib/src/views/service_page.dart index 4fc11ed..a3f3e93 100644 --- a/mobile/lib/src/views/service_page.dart +++ b/mobile/lib/src/views/service_page.dart @@ -1,41 +1,35 @@ -import 'package:aeris/src/aeris_api.dart'; import 'package:flutter/material.dart'; import 'package:aeris/src/models/service.dart'; import 'package:aeris/src/providers/pipelines_provider.dart'; -import 'package:aeris/src/providers/user_services_provider.dart'; +import 'package:aeris/src/providers/services_provider.dart'; import 'package:aeris/src/widgets/action_card.dart'; import 'package:aeris/src/widgets/aeris_card_page.dart'; import 'package:aeris/src/widgets/warning_dialog.dart'; -import 'package:get_it/get_it.dart'; import 'package:provider/provider.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:url_launcher/url_launcher.dart'; ///Page listing connected & available services class ServicePage extends StatelessWidget { - ServicePage({Key? key}) : super(key: key); + const ServicePage({Key? key}) : super(key: key); - ///TODO from an api call, determine what services are plugged - List getServiceGroup(String groupName, Icon trailingIcon, + List getServiceGroup(List services, String groupName, Icon trailingIcon, void Function(Service) onTap, BuildContext context) { - UserServiceProvider uServicesProvider = - Provider.of(context); - return [ Text( "$groupName:", style: const TextStyle(fontWeight: FontWeight.w500), ), const SizedBox(height: 10), - for (var service in uServicesProvider.userServices) + for (var service in services) ActionCard( - leading: service.serviceProvider.getLogo(logoSize: 50), - title: service.serviceProvider.name, + leading: service.getLogo(logoSize: 50), + title: service.name, trailing: IconButton( splashColor: trailingIcon.color!.withAlpha(100), splashRadius: 20, icon: trailingIcon, - onPressed: () => onTap(service.serviceProvider), + onPressed: () => onTap(service), )), const SizedBox(height: 30), ]; @@ -43,23 +37,9 @@ class ServicePage extends StatelessWidget { @override Widget build(BuildContext context) { - List services = [ - Service.discord(), - Service.gmail(), - Service.github(), - Service.youtube(), - Service.twitter(), - Service.spotify() - ]; - UserServiceProvider uServiceProvider = - Provider.of(context, listen: false); - uServiceProvider.clearProvider(); - for (var service in services) { - uServiceProvider.createUserService(service); - } - - return Consumer( - builder: (context, provider, _) => AerisCardPage( + return Consumer( + builder: (context, serviceProvider, _) => Consumer( + builder: (context, pipelineProvider, _) => AerisCardPage( body: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ @@ -72,6 +52,7 @@ class ServicePage extends StatelessWidget { const SizedBox(height: 60) ], ...getServiceGroup( + serviceProvider.connectedServices, AppLocalizations.of(context).connected, const Icon(Icons.delete, color: Colors.red), (Service service) => showDialog( @@ -79,12 +60,13 @@ class ServicePage extends StatelessWidget { builder: (BuildContext context) => WarningDialog( message: AppLocalizations.of(context) .disconnectServiceWarningMessage, - onAccept: () => GetIt.I() - .disconnectService(service) - .then((_) => provider.fetchPipelines()), + onAccept: () => serviceProvider + .removeService(service) + .then((_) => pipelineProvider.fetchPipelines()), warnedAction: AppLocalizations.of(context).disconnect)), context), ...getServiceGroup( + serviceProvider.availableServices, AppLocalizations.of(context).available, const Icon(Icons.connect_without_contact, color: Colors.green), (Service service) => { @@ -94,6 +76,6 @@ class ServicePage extends StatelessWidget { ], ), ), - ); + )); } } From 2e6369d425e69b86f4f348de7f66fa802c2ebd1b Mon Sep 17 00:00:00 2001 From: Arthi-chaud Date: Mon, 28 Feb 2022 16:05:10 +0100 Subject: [PATCH 033/109] Mobile CLient: Deep Linking on Android --- mobile/android/app/src/main/AndroidManifest.xml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/mobile/android/app/src/main/AndroidManifest.xml b/mobile/android/app/src/main/AndroidManifest.xml index d4912bf..f2755e4 100644 --- a/mobile/android/app/src/main/AndroidManifest.xml +++ b/mobile/android/app/src/main/AndroidManifest.xml @@ -25,6 +25,14 @@ + + + + + + + + From b8732ff25205b16e3d80119811f4324b5277ed1d Mon Sep 17 00:00:00 2001 From: Arthi-chaud Date: Mon, 28 Feb 2022 16:06:10 +0100 Subject: [PATCH 034/109] .env.example: remove duplicate key --- .env.example | 2 -- 1 file changed, 2 deletions(-) diff --git a/.env.example b/.env.example index ada2f65..7c414af 100644 --- a/.env.example +++ b/.env.example @@ -4,8 +4,6 @@ POSTGRES_PORT= POSTGRES_DB= POSTGRES_HOST=db -HOSTNAME= - YOUTUBE_KEY= WORKER_API_KEY= From 63789591eaf0d680b6fdf53ee9b3fa9e0984dc63 Mon Sep 17 00:00:00 2001 From: Arthi-chaud Date: Mon, 28 Feb 2022 16:16:30 +0100 Subject: [PATCH 035/109] Mobile Client: add route to fetch connected services --- mobile/lib/src/aeris_api.dart | 7 ++++--- mobile/lib/src/models/service.dart | 1 + mobile/lib/src/views/authorization_page.dart | 5 +---- mobile/lib/src/views/home_page.dart | 2 +- 4 files changed, 7 insertions(+), 8 deletions(-) diff --git a/mobile/lib/src/aeris_api.dart b/mobile/lib/src/aeris_api.dart index eaa5f0f..ca70416 100644 --- a/mobile/lib/src/aeris_api.dart +++ b/mobile/lib/src/aeris_api.dart @@ -190,10 +190,11 @@ class AerisAPI { /// Fetch the services the user is authenticated to Future> getConnectedService() async { - var res = await _requestAPI('', ///TODO Get route to fetch connected services - AerisAPIRequestType.get, null); + var res = + await _requestAPI('/auth/services', AerisAPIRequestType.get, null); if (!res.ok) return []; - return Service.all(); ///TODO Return real services + return (jsonDecode(res.body) as List) + .map((e) => Service.factory(e)).toList(); } /// Disconnects the user from the service diff --git a/mobile/lib/src/models/service.dart b/mobile/lib/src/models/service.dart index cf85377..6d3a6c1 100644 --- a/mobile/lib/src/models/service.dart +++ b/mobile/lib/src/models/service.dart @@ -93,6 +93,7 @@ class Service { Service.spotify() ]; + /// Construct a service based on a lowercase string, the name of the service static Service factory(String name) { for (Service service in Service.all()) { if (service.name.toLowerCase() == name.toLowerCase()) return service; diff --git a/mobile/lib/src/views/authorization_page.dart b/mobile/lib/src/views/authorization_page.dart index 935e795..bcb411a 100644 --- a/mobile/lib/src/views/authorization_page.dart +++ b/mobile/lib/src/views/authorization_page.dart @@ -1,8 +1,6 @@ -import 'package:aeris/src/aeris_api.dart'; import 'package:aeris/src/models/service.dart'; import 'package:aeris/src/providers/services_provider.dart'; import 'package:flutter/material.dart'; -import 'package:get_it/get_it.dart'; import 'package:loading_indicator/loading_indicator.dart'; import 'package:provider/provider.dart'; @@ -14,8 +12,7 @@ class AuthorizationPage extends StatelessWidget { final route = ModalRoute.of(context)!.settings.name!; final code = Uri.parse(route).queryParameters['code']!; final serviceName = Uri.parse(route).pathSegments.last; - final service = Service.all() - .firstWhere((element) => element.name.toLowerCase() == serviceName); + final service = Service.factory(serviceName); context.read().addService(service, code).then( (_) => Navigator.pop(context) diff --git a/mobile/lib/src/views/home_page.dart b/mobile/lib/src/views/home_page.dart index c964d11..e1600da 100644 --- a/mobile/lib/src/views/home_page.dart +++ b/mobile/lib/src/views/home_page.dart @@ -31,7 +31,7 @@ class _HomePageState extends State { Widget serviceActionButtons = IconButton( icon: const Icon(Icons.electrical_services), onPressed: () => - showAerisCardPage(context, (context) => ServicePage())); + showAerisCardPage(context, (context) => const ServicePage())); Widget logoutActionButton = IconButton( icon: const Icon(Icons.logout), onPressed: () => showDialog( From 541c3c7120143e996bfb043195658a114e161ec4 Mon Sep 17 00:00:00 2001 From: Arthi-chaud Date: Mon, 28 Feb 2022 16:49:20 +0100 Subject: [PATCH 036/109] Mobile Client: default date is null --- mobile/lib/src/models/pipeline.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mobile/lib/src/models/pipeline.dart b/mobile/lib/src/models/pipeline.dart index 3b32d9d..b801cbb 100644 --- a/mobile/lib/src/models/pipeline.dart +++ b/mobile/lib/src/models/pipeline.dart @@ -54,7 +54,7 @@ class Pipeline { "pType": aeris_action.Action.getType(trigger.service, trigger.name), "pParams": trigger.parameters, "enabled": enabled, - "lastTrigger": trigger.last?.toIso8601String() ?? "0000-00-00", + "lastTrigger": trigger.last?.toIso8601String(), "triggerCount": triggerCount }, 'reactions': reactions.map((e) => e.toJSON()).toList() From a84ca39e1b30871398505ca223b5de0e741612f9 Mon Sep 17 00:00:00 2001 From: Arthi-chaud Date: Mon, 28 Feb 2022 17:11:54 +0100 Subject: [PATCH 037/109] Mobile Client: Remove Gmail + remove dotenv --- mobile/lib/src/aeris_api.dart | 14 +++++--- mobile/lib/src/main.dart | 4 +-- mobile/lib/src/models/service.dart | 52 +++++++++--------------------- mobile/pubspec.lock | 7 ---- mobile/pubspec.yaml | 2 -- 5 files changed, 25 insertions(+), 54 deletions(-) diff --git a/mobile/lib/src/aeris_api.dart b/mobile/lib/src/aeris_api.dart index ca70416..e177bac 100644 --- a/mobile/lib/src/aeris_api.dart +++ b/mobile/lib/src/aeris_api.dart @@ -10,7 +10,6 @@ import 'package:aeris/src/models/service.dart'; import 'package:aeris/src/models/trigger.dart'; import 'package:path_provider/path_provider.dart'; import 'package:http/http.dart' as http; -import 'package:flutter_dotenv/flutter_dotenv.dart'; extension IsOk on http.Response { bool get ok => (statusCode ~/ 100) == 2; @@ -30,10 +29,9 @@ class AerisAPI { /// JWT token used to request API late String _jwt; - late final String baseRoute; + final String baseRoute = "localhost:8081"; ///TODO make it modifiable AerisAPI() { - baseRoute = dotenv.env['HOSTNAME']!; var trigger1 = Trigger( service: Service.spotify(), name: "Play song", last: DateTime.now()); var trigger3 = Trigger( @@ -47,7 +45,7 @@ class AerisAPI { var reaction = Reaction( service: Service.twitter(), parameters: [], name: "Post a tweet"); var reaction2 = - Reaction(service: Service.gmail(), parameters: [], name: "Do smth"); + Reaction(service: Service.anilist(), parameters: [], name: "Do smth"); var reaction1 = Reaction( service: Service.youtube(), parameters: [], name: "Do smth youtube"); var pipeline1 = Pipeline( @@ -170,6 +168,11 @@ class AerisAPI { '/workflow/${pipeline.id}', AerisAPIRequestType.delete, null); return res.ok; } + + String getServiceAuthURL(Service service) { + final serviceName = service.name.toLowerCase(); + return "$baseRoute/auth/$serviceName/url?redirect_uri=aeris://aeris.com$serviceName"; + } /// Send PUT request to update Pipeline, returns false if failed Future editPipeline(Pipeline updatedPipeline) async { @@ -194,7 +197,8 @@ class AerisAPI { await _requestAPI('/auth/services', AerisAPIRequestType.get, null); if (!res.ok) return []; return (jsonDecode(res.body) as List) - .map((e) => Service.factory(e)).toList(); + .map((e) => Service.factory(e)) + .toList(); } /// Disconnects the user from the service diff --git a/mobile/lib/src/main.dart b/mobile/lib/src/main.dart index 3b0caee..91b01a1 100644 --- a/mobile/lib/src/main.dart +++ b/mobile/lib/src/main.dart @@ -1,6 +1,5 @@ import 'package:aeris/src/aeris_api.dart'; import 'package:aeris/src/views/authorization_page.dart'; -import 'package:flutter_dotenv/flutter_dotenv.dart'; import 'package:flutter_localizations/flutter_localizations.dart'; import 'package:form_builder_validators/localization/l10n.dart'; import 'package:aeris/src/providers/pipelines_provider.dart'; @@ -15,7 +14,6 @@ import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:get_it/get_it.dart'; void main() async { - await dotenv.load(fileName: ".env"); AerisAPI interface = AerisAPI(); GetIt.I.registerSingleton(interface); await interface.restoreConnection(); @@ -52,7 +50,7 @@ class Aeris extends StatelessWidget { '/login': () => const LoginPage(), '/home': () => const HomePage(), }; - + return PageRouteBuilder( opaque: false, settings: settings, diff --git a/mobile/lib/src/models/service.dart b/mobile/lib/src/models/service.dart index 6d3a6c1..2896b9d 100644 --- a/mobile/lib/src/models/service.dart +++ b/mobile/lib/src/models/service.dart @@ -1,11 +1,13 @@ // Class for a service (Youtube, Gmail, ...) import 'dart:math'; +import 'package:aeris/src/aeris_api.dart'; import 'package:cached_network_image/cached_network_image.dart'; import 'package:flutter/material.dart'; -import 'package:flutter_dotenv/flutter_dotenv.dart'; import 'dart:core'; +import 'package:get_it/get_it.dart'; + /// Data class used to store data about a service (logo, url, name) class Service { ///Name of the service @@ -17,9 +19,6 @@ class Service { ///URL To a service's logo final String logoUrl; - ///return the url to authenticate to service via OAuth2 - final String authUrl; - Widget getLogo({double logoSize = 40}) => ClipRRect( borderRadius: BorderRadius.circular(8.0), child: CachedNetworkImage( @@ -29,65 +28,44 @@ class Service { height: logoSize, )); - static String _generateRandomString() { - var randomString = ""; - var seed = Random(); - var randomNumber = seed.nextInt(10) * 10; - - for (var i = 0; i < 20 + randomNumber; i++) { - randomString += String.fromCharCode(33 + (seed.nextInt(10) * 94)); - } - return randomString; - } + /// Get full url for OAuth2 + String get authUrl => GetIt.I().getServiceAuthURL(this); Service.spotify() : name = "Spotify", url = "https://www.spotify.com", logoUrl = - "https://www.presse-citron.net/app/uploads/2020/06/spotify-une-.jpg", - authUrl = - "https://accounts.spotify.com/authorize?client_id=${dotenv.env['SPOTIFY_CLIENT_ID']}&response_type=code&redirect_uri=aeris://aeris.com/authorization/spotify"; - Service.gmail() - : name = "Gmail", - url = "https://mail.google.com/", + "https://www.presse-citron.net/app/uploads/2020/06/spotify-une-.jpg"; + Service.anilist() + : name = "AniList", + url = "https://anilist.co", logoUrl = - "https://play-lh.googleusercontent.com/KSuaRLiI_FlDP8cM4MzJ23ml3og5Hxb9AapaGTMZ2GgR103mvJ3AAnoOFz1yheeQBBI", - authUrl = ""; - - ///TODO find + "https://anilist.co/img/icons/android-chrome-512x512.png"; Service.discord() : name = "Discord", url = "https://discord.com/app", logoUrl = - "https://play-lh.googleusercontent.com/fbrWR4LbtB_1Ulgz3_rw8bY3tx_zPU7A9ZOB5WYG_QmqOUUjA6JEzE_20GA4YBDWMx4", - authUrl = - "https://discord.com/api/oauth2/authorize?response_type=code&client_id=${dotenv.env['DISCORD_CLIENT_ID']}&scope=applications.commands%20applications.entitlements%20applications.store.update%20bot%20guilds%20guilds.join%20guilds.members.read%20identify%20messages.read%20webhook.incoming&state=${_generateRandomString()}"; + "https://play-lh.googleusercontent.com/fbrWR4LbtB_1Ulgz3_rw8bY3tx_zPU7A9ZOB5WYG_QmqOUUjA6JEzE_20GA4YBDWMx4"; Service.twitter() : name = "Twitter", url = "https://twitter.com", logoUrl = - "https://f.hellowork.com/blogdumoderateur/2019/11/twitter-logo-1200x1200.jpg", - authUrl = - "https://twitter.com/i/oauth2/authorize?response_type=code&client_id=${dotenv.env['TWITTER_CLIENT_ID']}&redirect_uri=aeris://aeris.com/authorization/twitter&scope=tweet.read%20users.read%20offline.access&state=${_generateRandomString()}&code_challenge=challenge&code_challenge_method=plain"; + "https://f.hellowork.com/blogdumoderateur/2019/11/twitter-logo-1200x1200.jpg"; Service.github() : name = "GitHub", url = "https://github.com/", - logoUrl = "https://avatars.githubusercontent.com/u/9919?s=280&v=4", - authUrl = - "https://github.com/login/oauth/authorize?client_id=${dotenv.env['GITHUB_CLIENT_ID']}&response_type=code&redirect_uri=aeris://aeris.com/authorization/github"; + logoUrl = "https://avatars.githubusercontent.com/u/9919?s=280&v=4"; Service.youtube() : name = "Youtube", url = "https://youtube.com", logoUrl = - "https://play-lh.googleusercontent.com/lMoItBgdPPVDJsNOVtP26EKHePkwBg-PkuY9NOrc-fumRtTFP4XhpUNk_22syN4Datc", - authUrl = - "https://accounts.google.com/o/oauth2/v2/auth?response_type=code&client_id=${dotenv.env['GOOGLE_CLIENT_ID']}&scope=openid%20email&redirect_uri=aeris://aeric.com/authorization/google&state=${_generateRandomString()}"; + "https://play-lh.googleusercontent.com/lMoItBgdPPVDJsNOVtP26EKHePkwBg-PkuY9NOrc-fumRtTFP4XhpUNk_22syN4Datc"; /// Returns a list of all the available services static List all() => [ Service.discord(), Service.github(), - Service.gmail(), + Service.anilist(), Service.youtube(), Service.twitter(), Service.spotify() diff --git a/mobile/pubspec.lock b/mobile/pubspec.lock index a79b985..78a9e6b 100644 --- a/mobile/pubspec.lock +++ b/mobile/pubspec.lock @@ -167,13 +167,6 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "3.3.0" - flutter_dotenv: - dependency: "direct main" - description: - name: flutter_dotenv - url: "https://pub.dartlang.org" - source: hosted - version: "5.0.2" flutter_fadein: dependency: "direct main" description: diff --git a/mobile/pubspec.yaml b/mobile/pubspec.yaml index 2ca084b..ee0fdba 100644 --- a/mobile/pubspec.yaml +++ b/mobile/pubspec.yaml @@ -56,7 +56,6 @@ dependencies: path_provider: ^2.0.9 http: ^0.13.4 tuple: ^2.0.0 - flutter_dotenv: ^5.0.2 loading_indicator: ^3.0.3 dev_dependencies: @@ -95,7 +94,6 @@ flutter: assets: - assets/logo.png - - .env # An image asset can refer to one or more resolution-specific "variants", see # https://flutter.dev/assets-and-images/#resolution-aware. From a4b68d7359e759bce4e4eccaa079698d8cfe711a Mon Sep 17 00:00:00 2001 From: Arthi-chaud Date: Mon, 28 Feb 2022 17:13:43 +0100 Subject: [PATCH 038/109] Mobile Client: OAuth redirect: fix url --- mobile/lib/src/aeris_api.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mobile/lib/src/aeris_api.dart b/mobile/lib/src/aeris_api.dart index e177bac..469f5cc 100644 --- a/mobile/lib/src/aeris_api.dart +++ b/mobile/lib/src/aeris_api.dart @@ -171,7 +171,7 @@ class AerisAPI { String getServiceAuthURL(Service service) { final serviceName = service.name.toLowerCase(); - return "$baseRoute/auth/$serviceName/url?redirect_uri=aeris://aeris.com$serviceName"; + return "$baseRoute/auth/$serviceName/url?redirect_uri=aeris://aeris.com/authorization/$serviceName"; } /// Send PUT request to update Pipeline, returns false if failed From 0e7b6bc6a5bc5898d40ef03570b4752f1d043205 Mon Sep 17 00:00:00 2001 From: Arthi-chaud Date: Tue, 1 Mar 2022 09:21:05 +0100 Subject: [PATCH 039/109] Mobile Client: Action Catalogue provider --- mobile/lib/src/aeris_api.dart | 8 ++- mobile/lib/src/models/action.dart | 5 ++ mobile/lib/src/models/action_template.dart | 3 +- .../providers/action_catalogue_provider.dart | 56 +++++++++++++++++++ .../reorderable_reaction_cards_list.dart | 4 +- 5 files changed, 72 insertions(+), 4 deletions(-) create mode 100644 mobile/lib/src/providers/action_catalogue_provider.dart diff --git a/mobile/lib/src/aeris_api.dart b/mobile/lib/src/aeris_api.dart index ca70416..4783b66 100644 --- a/mobile/lib/src/aeris_api.dart +++ b/mobile/lib/src/aeris_api.dart @@ -155,6 +155,12 @@ class AerisAPI { _connected = false; } + ///Get /about.json + Future> getAbout() async { + var res = await _requestAPI('/about.json', AerisAPIRequestType.get, null); + return jsonDecode(res.body); + } + /// Adds new pipeline to API, returns false if post failed Future createPipeline(Pipeline newPipeline) async { fakeAPI.add(newPipeline); @@ -223,7 +229,7 @@ class AerisAPI { } return [ for (int i = 0; i <= 10; i++) - ActionTemplate(service: service, name: "action$i", parameters: [ + ActionTemplate(service: service, name: "action$i", description: "descr$i", parameters: [ for (int j = 0; j < 3; j++) ActionParameter(name: "key$j", description: "description$j") ]) diff --git a/mobile/lib/src/models/action.dart b/mobile/lib/src/models/action.dart index 36c5bd9..54dd2e9 100644 --- a/mobile/lib/src/models/action.dart +++ b/mobile/lib/src/models/action.dart @@ -14,10 +14,15 @@ abstract class Action { ///Action's parameters List parameters; + + /// Description of the action (used in catalogue) + String? description; + Action( {Key? key, required this.service, required this.name, + this.description, this.parameters = const []}); static Tuple2 parseServiceAndName(String rType) { diff --git a/mobile/lib/src/models/action_template.dart b/mobile/lib/src/models/action_template.dart index d14a784..3f10c2b 100644 --- a/mobile/lib/src/models/action_template.dart +++ b/mobile/lib/src/models/action_template.dart @@ -9,6 +9,7 @@ class ActionTemplate extends Action { {Key? key, required Service service, required String name, + required String description, List parameters = const []}) - : super(service: service, name: name, parameters: parameters); + : super(service: service, name: name, parameters: parameters, description: description); } diff --git a/mobile/lib/src/providers/action_catalogue_provider.dart b/mobile/lib/src/providers/action_catalogue_provider.dart new file mode 100644 index 0000000..1670f57 --- /dev/null +++ b/mobile/lib/src/providers/action_catalogue_provider.dart @@ -0,0 +1,56 @@ +import 'package:aeris/src/models/action_parameter.dart'; +import 'package:aeris/src/models/action_template.dart'; +import 'package:aeris/src/models/service.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:aeris/src/aeris_api.dart'; +import 'package:get_it/get_it.dart'; + +/// Provider class for Action listed in /about.json +class ActionCatalogueProvider extends ChangeNotifier { + + /// Tells if the provers has loaded data at least once + late Map> _triggerTemplates; + late Map> _reactionTemplates; + Map> get triggerTemplates => _triggerTemplates; + Map> get reactionTemplates => _reactionTemplates; + + ActionCatalogueProvider() { + GetIt.I().getAbout().then((Map about) { + Service.all().forEach((element) { + _triggerTemplates.putIfAbsent(element, () => []); + _reactionTemplates.putIfAbsent(element, () => []); + }); + final services = (about['server'] as Map)['services'] as List; + for (var serviceContent in services) { + Service service = Service.factory(serviceContent['name']); + for (var action in (serviceContent['actions'] as List)) { + _triggerTemplates[service]!.add( + ActionTemplate( + name: action['name'], + service: service, + description: action['description'], + parameters: (action['params'] as List).map( + (e) => ActionParameter(name: e['name'], description: e['description']) + ).toList(), + ///TODO manage returned values + ) + ); + } + for (var reaction in serviceContent['reactions']) { + _reactionTemplates[service]!.add( + ActionTemplate( + name: reaction['name'], + service: service, + description: reaction['description'], + parameters: (reaction['params'] as List).map( + (e) => ActionParameter(name: e['name'], description: e['description']) + ).toList(), + ///TODO manage returned values + ) + ); + } + } + notifyListeners(); + }); + } +} \ No newline at end of file diff --git a/mobile/lib/src/widgets/reorderable_reaction_cards_list.dart b/mobile/lib/src/widgets/reorderable_reaction_cards_list.dart index 87a0fc5..1abf198 100644 --- a/mobile/lib/src/widgets/reorderable_reaction_cards_list.dart +++ b/mobile/lib/src/widgets/reorderable_reaction_cards_list.dart @@ -3,14 +3,14 @@ import 'package:flutter/widgets.dart'; import 'package:reorderables/reorderables.dart'; class ReorderableReactionCardsList extends StatefulWidget { - ReorderableReactionCardsList( + const ReorderableReactionCardsList( {Key? key, required this.onReorder, required this.reactionList, required this.itemBuilder}) : super(key: key); - List reactionList; + final List reactionList; // Callback when a list has been reordered final void Function() onReorder; From f5e3be412ffe3c7a9e10b6a2f2bdae0504ca4507 Mon Sep 17 00:00:00 2001 From: Arthi-chaud Date: Tue, 1 Mar 2022 09:21:39 +0100 Subject: [PATCH 040/109] Mobile Client: add Action Catalogue provider to main --- mobile/lib/src/main.dart | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/mobile/lib/src/main.dart b/mobile/lib/src/main.dart index 3b0caee..92aada8 100644 --- a/mobile/lib/src/main.dart +++ b/mobile/lib/src/main.dart @@ -1,4 +1,5 @@ import 'package:aeris/src/aeris_api.dart'; +import 'package:aeris/src/providers/action_catalogue_provider.dart'; import 'package:aeris/src/views/authorization_page.dart'; import 'package:flutter_dotenv/flutter_dotenv.dart'; import 'package:flutter_localizations/flutter_localizations.dart'; @@ -21,7 +22,8 @@ void main() async { await interface.restoreConnection(); runApp(MultiProvider(providers: [ ChangeNotifierProvider(create: (_) => PipelineProvider()), - ChangeNotifierProvider(create: (_) => ServiceProvider()) + ChangeNotifierProvider(create: (_) => ServiceProvider()), + ChangeNotifierProvider(create: (_) => ActionCatalogueProvider()) ], child: const Aeris())); } From a0f93a2801a4a815d25f8972c98a80160c93602a Mon Sep 17 00:00:00 2001 From: Arthi-chaud Date: Tue, 1 Mar 2022 09:23:55 +0100 Subject: [PATCH 041/109] Mobile Client: Setup Action Page: Using ActionTemplate class --- mobile/lib/src/views/setup_action_page.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mobile/lib/src/views/setup_action_page.dart b/mobile/lib/src/views/setup_action_page.dart index 8808102..5490607 100644 --- a/mobile/lib/src/views/setup_action_page.dart +++ b/mobile/lib/src/views/setup_action_page.dart @@ -110,7 +110,7 @@ class _SetupActionPageState extends State { highlightColor: Theme.of(context).colorScheme.secondary ) else - ...[for (aeris.Action availableAction in availableActions!) + ...[for (ActionTemplate availableAction in availableActions!) Card( elevation: 5, shape: cardShape, @@ -125,7 +125,7 @@ class _SetupActionPageState extends State { expanded: Padding( padding: const EdgeInsets.all(20), child: ActionForm( - description: "This is the action's very very very very long description", ///TODO Find actual description + description: availableAction.description!, name: availableAction.name, parameters: availableAction.parameters.map((param) { if (widget.action.service.name == serviceState!.name && widget.action.name == availableAction.name) { From 9cdb140d10c5f14aeb53e8817313441409cb44a1 Mon Sep 17 00:00:00 2001 From: Arthi-chaud Date: Tue, 1 Mar 2022 09:38:47 +0100 Subject: [PATCH 042/109] Mobile Client: API uses catalogue provider --- mobile/lib/src/aeris_api.dart | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/mobile/lib/src/aeris_api.dart b/mobile/lib/src/aeris_api.dart index 4783b66..25392a4 100644 --- a/mobile/lib/src/aeris_api.dart +++ b/mobile/lib/src/aeris_api.dart @@ -1,6 +1,7 @@ import 'dart:async'; import 'dart:convert'; import 'dart:io'; +import 'package:aeris/src/main.dart'; import 'package:aeris/src/models/action.dart'; import 'package:aeris/src/models/action_parameter.dart'; import 'package:aeris/src/models/action_template.dart'; @@ -8,9 +9,11 @@ import 'package:aeris/src/models/pipeline.dart'; import 'package:aeris/src/models/reaction.dart'; import 'package:aeris/src/models/service.dart'; import 'package:aeris/src/models/trigger.dart'; +import 'package:aeris/src/providers/action_catalogue_provider.dart'; import 'package:path_provider/path_provider.dart'; import 'package:http/http.dart' as http; import 'package:flutter_dotenv/flutter_dotenv.dart'; +import 'package:provider/provider.dart'; extension IsOk on http.Response { bool get ok => (statusCode ~/ 100) == 2; @@ -221,19 +224,11 @@ class AerisAPI { Future> getActionsFor( Service service, Action action) async { - await Future.delayed(const Duration(seconds: 3)); + final catalogue = Aeris.materialKey.currentContext?.read(); if (action is Trigger) { - ///TODO get triggers - } else if (action is Reaction) { - ///TODO get reactions + return catalogue!.triggerTemplates[service]!; } - return [ - for (int i = 0; i <= 10; i++) - ActionTemplate(service: service, name: "action$i", description: "descr$i", parameters: [ - for (int j = 0; j < 3; j++) - ActionParameter(name: "key$j", description: "description$j") - ]) - ]; + return catalogue!.reactionTemplates[service]!; } /// Encodes Uri for request From b4ea672a7cfc582d733d24281226c7593b884f4c Mon Sep 17 00:00:00 2001 From: Arthi-chaud Date: Tue, 1 Mar 2022 09:45:55 +0100 Subject: [PATCH 043/109] Mobile Client: Holds returned values from actions from about.json --- mobile/lib/src/aeris_api.dart | 2 ++ mobile/lib/src/models/action_template.dart | 5 +++++ mobile/lib/src/providers/action_catalogue_provider.dart | 8 ++++++-- 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/mobile/lib/src/aeris_api.dart b/mobile/lib/src/aeris_api.dart index 25392a4..75d88b4 100644 --- a/mobile/lib/src/aeris_api.dart +++ b/mobile/lib/src/aeris_api.dart @@ -1,3 +1,5 @@ +// ignore_for_file: unused_import + import 'dart:async'; import 'dart:convert'; import 'dart:io'; diff --git a/mobile/lib/src/models/action_template.dart b/mobile/lib/src/models/action_template.dart index 3f10c2b..53ce020 100644 --- a/mobile/lib/src/models/action_template.dart +++ b/mobile/lib/src/models/action_template.dart @@ -5,11 +5,16 @@ import 'package:flutter/foundation.dart'; /// Template for actions, for forms class ActionTemplate extends Action { + + ///List of values returned by the action + final List returnedValues; + ActionTemplate( {Key? key, required Service service, required String name, required String description, + this.returnedValues = const [], List parameters = const []}) : super(service: service, name: name, parameters: parameters, description: description); } diff --git a/mobile/lib/src/providers/action_catalogue_provider.dart b/mobile/lib/src/providers/action_catalogue_provider.dart index 1670f57..9ea74d5 100644 --- a/mobile/lib/src/providers/action_catalogue_provider.dart +++ b/mobile/lib/src/providers/action_catalogue_provider.dart @@ -32,7 +32,9 @@ class ActionCatalogueProvider extends ChangeNotifier { parameters: (action['params'] as List).map( (e) => ActionParameter(name: e['name'], description: e['description']) ).toList(), - ///TODO manage returned values + returnedValues: (action['returns'] as List).map( + (e) => ActionParameter(name: e['name'], description: e['description']) + ).toList(), ) ); } @@ -45,7 +47,9 @@ class ActionCatalogueProvider extends ChangeNotifier { parameters: (reaction['params'] as List).map( (e) => ActionParameter(name: e['name'], description: e['description']) ).toList(), - ///TODO manage returned values + returnedValues: (reaction['returns'] as List).map( + (e) => ActionParameter(name: e['name'], description: e['description']) + ).toList(), ) ); } From a32993f77c51bdbc37dda72e7c1c95571c66af44 Mon Sep 17 00:00:00 2001 From: Arthi-chaud Date: Tue, 1 Mar 2022 09:59:30 +0100 Subject: [PATCH 044/109] Mobile Client: Service become consts again --- mobile/lib/src/models/service.dart | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/mobile/lib/src/models/service.dart b/mobile/lib/src/models/service.dart index 2896b9d..d177704 100644 --- a/mobile/lib/src/models/service.dart +++ b/mobile/lib/src/models/service.dart @@ -31,38 +31,38 @@ class Service { /// Get full url for OAuth2 String get authUrl => GetIt.I().getServiceAuthURL(this); - Service.spotify() + const Service.spotify() : name = "Spotify", url = "https://www.spotify.com", logoUrl = "https://www.presse-citron.net/app/uploads/2020/06/spotify-une-.jpg"; - Service.anilist() + const Service.anilist() : name = "AniList", url = "https://anilist.co", logoUrl = "https://anilist.co/img/icons/android-chrome-512x512.png"; - Service.discord() + const Service.discord() : name = "Discord", url = "https://discord.com/app", logoUrl = "https://play-lh.googleusercontent.com/fbrWR4LbtB_1Ulgz3_rw8bY3tx_zPU7A9ZOB5WYG_QmqOUUjA6JEzE_20GA4YBDWMx4"; - Service.twitter() + const Service.twitter() : name = "Twitter", url = "https://twitter.com", logoUrl = "https://f.hellowork.com/blogdumoderateur/2019/11/twitter-logo-1200x1200.jpg"; - Service.github() + const Service.github() : name = "GitHub", url = "https://github.com/", logoUrl = "https://avatars.githubusercontent.com/u/9919?s=280&v=4"; - Service.youtube() + const Service.youtube() : name = "Youtube", url = "https://youtube.com", logoUrl = "https://play-lh.googleusercontent.com/lMoItBgdPPVDJsNOVtP26EKHePkwBg-PkuY9NOrc-fumRtTFP4XhpUNk_22syN4Datc"; /// Returns a list of all the available services - static List all() => [ + static List all() => const [ Service.discord(), Service.github(), Service.anilist(), From 1decf562d426180ed1a3e5f67d08b37a48ec7877 Mon Sep 17 00:00:00 2001 From: Arthi-chaud Date: Tue, 1 Mar 2022 10:00:48 +0100 Subject: [PATCH 045/109] Mobile Client: Service become consts again --- mobile/lib/src/aeris_api.dart | 12 ++++++------ mobile/lib/src/models/service.dart | 2 -- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/mobile/lib/src/aeris_api.dart b/mobile/lib/src/aeris_api.dart index 469f5cc..3bf7f42 100644 --- a/mobile/lib/src/aeris_api.dart +++ b/mobile/lib/src/aeris_api.dart @@ -33,21 +33,21 @@ class AerisAPI { AerisAPI() { var trigger1 = Trigger( - service: Service.spotify(), name: "Play song", last: DateTime.now()); + service: const Service.spotify(), name: "Play song", last: DateTime.now()); var trigger3 = Trigger( - service: Service.discord(), + service: const Service.discord(), name: "Send a message", last: DateTime.now()); var trigger2 = Trigger( - service: Service.spotify(), + service: const Service.spotify(), name: "Play song", last: DateTime.parse("2022-01-01")); var reaction = Reaction( - service: Service.twitter(), parameters: [], name: "Post a tweet"); + service: const Service.twitter(), parameters: [], name: "Post a tweet"); var reaction2 = - Reaction(service: Service.anilist(), parameters: [], name: "Do smth"); + Reaction(service: const Service.anilist(), parameters: [], name: "Do smth"); var reaction1 = Reaction( - service: Service.youtube(), parameters: [], name: "Do smth youtube"); + service: const Service.youtube(), parameters: [], name: "Do smth youtube"); var pipeline1 = Pipeline( id: 10, name: "My Action", diff --git a/mobile/lib/src/models/service.dart b/mobile/lib/src/models/service.dart index d177704..69367f2 100644 --- a/mobile/lib/src/models/service.dart +++ b/mobile/lib/src/models/service.dart @@ -1,6 +1,4 @@ // Class for a service (Youtube, Gmail, ...) -import 'dart:math'; - import 'package:aeris/src/aeris_api.dart'; import 'package:cached_network_image/cached_network_image.dart'; import 'package:flutter/material.dart'; From 1cffcab1d8c0e5d8dcd6548a906e3b047fd448e7 Mon Sep 17 00:00:00 2001 From: Arthi-chaud Date: Tue, 1 Mar 2022 10:04:15 +0100 Subject: [PATCH 046/109] Mobile Client: merge --- mobile/lib/src/aeris_api.dart | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/mobile/lib/src/aeris_api.dart b/mobile/lib/src/aeris_api.dart index 69777c3..c3403b0 100644 --- a/mobile/lib/src/aeris_api.dart +++ b/mobile/lib/src/aeris_api.dart @@ -182,6 +182,11 @@ class AerisAPI { return res.ok; } + String getServiceAuthURL(Service service) { + final serviceName = service.name.toLowerCase(); + return "$baseRoute/auth/$serviceName/url?redirect_uri=aeris://aeris.com/authorization/$serviceName"; + } + /// Send PUT request to update Pipeline, returns false if failed Future editPipeline(Pipeline updatedPipeline) async { var res = await _requestAPI('/workflow/${updatedPipeline.id}', From eabb022fc7dca5e8d90ab194d2dc48ef8376f284 Mon Sep 17 00:00:00 2001 From: Arthi-chaud Date: Tue, 1 Mar 2022 10:12:58 +0100 Subject: [PATCH 047/109] Mobile Client: Fix service const error --- mobile/lib/src/aeris_api.dart | 4 +--- mobile/lib/src/models/trigger.dart | 2 +- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/mobile/lib/src/aeris_api.dart b/mobile/lib/src/aeris_api.dart index c3403b0..7d2d11d 100644 --- a/mobile/lib/src/aeris_api.dart +++ b/mobile/lib/src/aeris_api.dart @@ -14,7 +14,6 @@ import 'package:aeris/src/models/trigger.dart'; import 'package:aeris/src/providers/action_catalogue_provider.dart'; import 'package:path_provider/path_provider.dart'; import 'package:http/http.dart' as http; -import 'package:flutter_dotenv/flutter_dotenv.dart'; import 'package:provider/provider.dart'; extension IsOk on http.Response { @@ -35,10 +34,9 @@ class AerisAPI { /// JWT token used to request API late String _jwt; - late final String baseRoute; + final String baseRoute = "localhost:8081"; ///TODO make it modifiable AerisAPI() { - baseRoute = dotenv.env['HOSTNAME']!; var trigger1 = Trigger( service: const Service.spotify(), name: "Play song", last: DateTime.now()); var trigger3 = Trigger( diff --git a/mobile/lib/src/models/trigger.dart b/mobile/lib/src/models/trigger.dart index 6b37b5f..090111d 100644 --- a/mobile/lib/src/models/trigger.dart +++ b/mobile/lib/src/models/trigger.dart @@ -47,7 +47,7 @@ class Trigger extends aeris_action.Action { /// Template trigger, used as an 'empty' trigger Trigger.template({Key? key, this.last}) - : super(service: Service.twitter(), name: '', parameters: []); + : super(service: Service.all()[0], name: '', parameters: []); @override // ignore: avoid_renaming_method_parameters From 6c5a236b699ffa916752249fbe86c1c2e486fabc Mon Sep 17 00:00:00 2001 From: Arthi-chaud Date: Tue, 1 Mar 2022 10:19:18 +0100 Subject: [PATCH 048/109] Mobile Client: When a service group is empty, no label is displayed --- mobile/lib/src/providers/pipelines_provider.dart | 2 ++ mobile/lib/src/views/service_page.dart | 1 + 2 files changed, 3 insertions(+) diff --git a/mobile/lib/src/providers/pipelines_provider.dart b/mobile/lib/src/providers/pipelines_provider.dart index 722a933..3117f27 100644 --- a/mobile/lib/src/providers/pipelines_provider.dart +++ b/mobile/lib/src/providers/pipelines_provider.dart @@ -27,12 +27,14 @@ class PipelineProvider extends ChangeNotifier { _initialized = true; _pipelineCollection.pipelines = pipelines; sortPipelines(); + notifyListeners(); }); } /// Adds a pipeline in the Provider addPipeline(Pipeline newPipeline) async { await GetIt.I().createPipeline(newPipeline); + ///TODO Check newPipeline got the ID _pipelineCollection.pipelines.add(newPipeline); sortPipelines(); diff --git a/mobile/lib/src/views/service_page.dart b/mobile/lib/src/views/service_page.dart index a3f3e93..d35cff8 100644 --- a/mobile/lib/src/views/service_page.dart +++ b/mobile/lib/src/views/service_page.dart @@ -15,6 +15,7 @@ class ServicePage extends StatelessWidget { List getServiceGroup(List services, String groupName, Icon trailingIcon, void Function(Service) onTap, BuildContext context) { + if (services.isEmpty) return []; return [ Text( "$groupName:", From e4621bbd56318886e962aefab3772d5ead865e1a Mon Sep 17 00:00:00 2001 From: Arthi-chaud Date: Tue, 1 Mar 2022 10:24:57 +0100 Subject: [PATCH 049/109] Mobile Client: On setup action page, fix loading animation when the service is changed --- mobile/lib/src/views/setup_action_page.dart | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/mobile/lib/src/views/setup_action_page.dart b/mobile/lib/src/views/setup_action_page.dart index 5490607..973a56d 100644 --- a/mobile/lib/src/views/setup_action_page.dart +++ b/mobile/lib/src/views/setup_action_page.dart @@ -44,13 +44,13 @@ class _SetupActionPageState extends State { elevation: 8, underline: Container(), onChanged: (service) { + setState(() { + serviceState = service; + availableActions = null; + }); GetIt.I().getActionsFor(service!, widget.action).then((actions) => setState(() { availableActions = actions; })); - setState(() { - serviceState = service; - availableActions = []; - }); }, items: Service.all().map>((Service service) { return DropdownMenuItem( From f0ae617e70b7889477e5ff2d58e7206a78df5fed Mon Sep 17 00:00:00 2001 From: Arthi-chaud Date: Tue, 1 Mar 2022 10:26:48 +0100 Subject: [PATCH 050/109] Mobile Client: Fix api call using jwt --- mobile/lib/src/aeris_api.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mobile/lib/src/aeris_api.dart b/mobile/lib/src/aeris_api.dart index 7d2d11d..252a32b 100644 --- a/mobile/lib/src/aeris_api.dart +++ b/mobile/lib/src/aeris_api.dart @@ -245,7 +245,7 @@ class AerisAPI { Future _requestAPI( String route, AerisAPIRequestType requestType, Object? body) async { final Map? header = - _connected ? {'authorization': 'Bearer $_jwt'} : null; + _connected ? {'Authorization': 'Bearer $_jwt'} : null; switch (requestType) { case AerisAPIRequestType.delete: return await http.delete(_encoreUri(route), From 44ec71ac26248166ed5150e787dc6e934817c58b Mon Sep 17 00:00:00 2001 From: Arthi-chaud Date: Tue, 1 Mar 2022 10:37:19 +0100 Subject: [PATCH 051/109] Mobile LCient: fix build workflow --- .github/workflows/build.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 7a122fc..1a8dccc 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -20,7 +20,6 @@ jobs: - name: Run Docker run: docker run -v $PWD:/dist aeris_mobile_build - name: Upload build artifact - if: github.ref == 'refs/head/master' uses: actions/upload-artifact@v2 with: name: aeris_apk From 1b7c117a5f1962ae5ad250451338f7d10be3a790 Mon Sep 17 00:00:00 2001 From: Arthi-chaud Date: Tue, 1 Mar 2022 12:53:24 +0100 Subject: [PATCH 052/109] Mobile Client: fix login --- mobile/lib/src/aeris_api.dart | 4 ++-- mobile/lib/src/views/login_page.dart | 4 ++++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/mobile/lib/src/aeris_api.dart b/mobile/lib/src/aeris_api.dart index 252a32b..511fbda 100644 --- a/mobile/lib/src/aeris_api.dart +++ b/mobile/lib/src/aeris_api.dart @@ -26,7 +26,7 @@ enum AerisAPIRequestType { get, post, put, delete } /// Call to interact with Aeris' Back end class AerisAPI { /// Get Connection state - bool _connected = true; //TODO Will be false later + bool _connected = false; //TODO Will be false later bool get isConnected => _connected; late List fakeAPI; @@ -34,7 +34,7 @@ class AerisAPI { /// JWT token used to request API late String _jwt; - final String baseRoute = "localhost:8081"; ///TODO make it modifiable + final String baseRoute = "http://10.0.2.2:8080"; ///TODO make it modifiable AerisAPI() { var trigger1 = Trigger( diff --git a/mobile/lib/src/views/login_page.dart b/mobile/lib/src/views/login_page.dart index 1c3ce94..8d30b05 100644 --- a/mobile/lib/src/views/login_page.dart +++ b/mobile/lib/src/views/login_page.dart @@ -49,6 +49,10 @@ class LoginPage extends StatelessWidget { primaryColor: Theme.of(context).colorScheme.primary), onLogin: _authUser, onSignup: _signupUser, + userValidator: (input) { + if (input == null || input.trim().length < 4) return "Must be at least 4 chars long"; + return null; + }, onSubmitAnimationCompleted: () { Navigator.of(context).pushNamedAndRemoveUntil('/home', (route) => false); })); From a4d161e91d01b6d04c2b08b432eec275ab1e14aa Mon Sep 17 00:00:00 2001 From: Arthi-chaud Date: Tue, 1 Mar 2022 12:57:13 +0100 Subject: [PATCH 053/109] Mobile Client: fix login --- mobile/lib/src/views/login_page.dart | 1 + 1 file changed, 1 insertion(+) diff --git a/mobile/lib/src/views/login_page.dart b/mobile/lib/src/views/login_page.dart index 8d30b05..35c59f6 100644 --- a/mobile/lib/src/views/login_page.dart +++ b/mobile/lib/src/views/login_page.dart @@ -49,6 +49,7 @@ class LoginPage extends StatelessWidget { primaryColor: Theme.of(context).colorScheme.primary), onLogin: _authUser, onSignup: _signupUser, + userType: LoginUserType.name, userValidator: (input) { if (input == null || input.trim().length < 4) return "Must be at least 4 chars long"; return null; From 227c1c645dfae4e95e73156d5f104c68b1295e54 Mon Sep 17 00:00:00 2001 From: Arthi-chaud Date: Tue, 1 Mar 2022 17:53:59 +0100 Subject: [PATCH 054/109] Mobile Client: fix serializations of object --- mobile/lib/src/models/reaction.dart | 5 ++--- mobile/lib/src/models/trigger.dart | 4 ++-- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/mobile/lib/src/models/reaction.dart b/mobile/lib/src/models/reaction.dart index 2ea9edb..9b85eb3 100644 --- a/mobile/lib/src/models/reaction.dart +++ b/mobile/lib/src/models/reaction.dart @@ -26,14 +26,13 @@ class Reaction extends aeris_action.Action { return Reaction( service: service.item1, name: service.item2, - parameters: ActionParameter.fromJSON((reactionJSON['rParams'] as Map)['contents'] - as Map)); + parameters: ActionParameter.fromJSON((reactionJSON['rParams'] as Map))); } /// Serialize Reaction to JSON Object toJSON() => { "rType": aeris_action.Action.getType(service, name), - "rParams": {"contents": parameters} + "rParams": parameters }; @override diff --git a/mobile/lib/src/models/trigger.dart b/mobile/lib/src/models/trigger.dart index 090111d..1e8b971 100644 --- a/mobile/lib/src/models/trigger.dart +++ b/mobile/lib/src/models/trigger.dart @@ -31,8 +31,8 @@ class Trigger extends aeris_action.Action { service: service.item1, name: service.item2, last: last.year == 0 ? null : last, - parameters: ActionParameter.fromJSON((triggerJSON['pParams'] as Map)['contents'] - as Map)); + parameters: ActionParameter.fromJSON((triggerJSON['pParams'] as Map)) + ); } String lastToString() { From 15a5acca97a29b9e9cc8f50d1e128ebf48c2dda4 Mon Sep 17 00:00:00 2001 From: Arthi-chaud Date: Tue, 1 Mar 2022 18:31:32 +0100 Subject: [PATCH 055/109] Mobile Client: API: login, signup + about.json --- mobile/lib/src/aeris_api.dart | 80 ++++--------------- mobile/lib/src/main.dart | 2 +- .../providers/action_catalogue_provider.dart | 13 +-- 3 files changed, 23 insertions(+), 72 deletions(-) diff --git a/mobile/lib/src/aeris_api.dart b/mobile/lib/src/aeris_api.dart index 511fbda..68c250d 100644 --- a/mobile/lib/src/aeris_api.dart +++ b/mobile/lib/src/aeris_api.dart @@ -29,63 +29,10 @@ class AerisAPI { bool _connected = false; //TODO Will be false later bool get isConnected => _connected; - late List fakeAPI; - /// JWT token used to request API late String _jwt; - final String baseRoute = "http://10.0.2.2:8080"; ///TODO make it modifiable - - AerisAPI() { - var trigger1 = Trigger( - service: const Service.spotify(), name: "Play song", last: DateTime.now()); - var trigger3 = Trigger( - service: const Service.discord(), - name: "Send a message", - last: DateTime.now()); - var trigger2 = Trigger( - service: const Service.spotify(), - name: "Play song", - last: DateTime.parse("2022-01-01")); - var reaction = Reaction( - service: const Service.twitter(), parameters: [], name: "Post a tweet"); - var reaction2 = - Reaction(service: const Service.anilist(), parameters: [], name: "Do smth"); - var reaction1 = Reaction( - service: const Service.youtube(), parameters: [], name: "Do smth youtube"); - var pipeline1 = Pipeline( - id: 10, - name: "My Action", - triggerCount: 1, - enabled: true, - trigger: trigger1, - reactions: [reaction]); - var pipeline2 = Pipeline( - id: 10, - name: "My very long action Action", - triggerCount: 10, - enabled: true, - trigger: trigger2, - reactions: [reaction, reaction1, reaction2]); - var pipeline3 = Pipeline( - id: 10, - name: "Disabled", - triggerCount: 3, - enabled: false, - trigger: trigger3, - reactions: [reaction]); - fakeAPI = [ - pipeline3, - pipeline2, - pipeline1, - pipeline3, - pipeline2, - pipeline1, - pipeline3, - pipeline2, - pipeline1 - ]; - } + final String baseRoute = "http://10.29.124.174:8080"; ///TODO make it modifiable /// Name of the file that contains the JWT used for Aeris' API requestd static const String jwtFile = 'aeris_jwt.txt'; @@ -102,8 +49,8 @@ class AerisAPI { Future signUpUser(String username, String password) async { http.Response response = await _requestAPI('/auth/signup', AerisAPIRequestType.post, { - username: username, - password: password, + 'username': username, + 'password': password, }); if (!response.ok) { return false; @@ -115,8 +62,8 @@ class AerisAPI { Future createConnection(String username, String password) async { http.Response response = await _requestAPI('/auth/login', AerisAPIRequestType.post, { - username: username, - password: password, + 'username': username, + 'password': password, }); if (!response.ok) { return false; @@ -166,7 +113,6 @@ class AerisAPI { /// Adds new pipeline to API, returns false if post failed Future createPipeline(Pipeline newPipeline) async { - fakeAPI.add(newPipeline); var res = await _requestAPI( '/workflow', AerisAPIRequestType.post, newPipeline.toJSON()); newPipeline = Pipeline.fromJSON(jsonDecode(res.body)); @@ -196,10 +142,9 @@ class AerisAPI { Future> getPipelines() async { var res = await _requestAPI('/workflows', AerisAPIRequestType.get, null); if (res.ok == false) return []; - List body = jsonDecode(res.body); + final List body = jsonDecode(res.body); - ///TODO return body.map((e) => Pipeline.fromJSON(e as Map)).toList(); - return fakeAPI; + return body.map((e) => Pipeline.fromJSON(e)).toList(); } /// Fetch the services the user is authenticated to @@ -244,8 +189,13 @@ class AerisAPI { /// Calls API using a HTTP request type, a route and body Future _requestAPI( String route, AerisAPIRequestType requestType, Object? body) async { - final Map? header = - _connected ? {'Authorization': 'Bearer $_jwt'} : null; + final Map header = { + 'Content-type' : 'application/json', + 'Accept': 'application/json', + }; + if (_connected) { + header.addAll({'Authorization': 'Bearer $_jwt'}); + } switch (requestType) { case AerisAPIRequestType.delete: return await http.delete(_encoreUri(route), @@ -253,7 +203,7 @@ class AerisAPI { case AerisAPIRequestType.get: return await http.get(_encoreUri(route), headers: header); case AerisAPIRequestType.post: - return await http.post(_encoreUri(route), body: body, headers: header); + return await http.post(_encoreUri(route), body: jsonEncode(body), headers: header); case AerisAPIRequestType.put: return await http.put(_encoreUri(route), body: body, headers: header); } diff --git a/mobile/lib/src/main.dart b/mobile/lib/src/main.dart index 5324c1c..511014f 100644 --- a/mobile/lib/src/main.dart +++ b/mobile/lib/src/main.dart @@ -21,7 +21,7 @@ void main() async { runApp(MultiProvider(providers: [ ChangeNotifierProvider(create: (_) => PipelineProvider()), ChangeNotifierProvider(create: (_) => ServiceProvider()), - ChangeNotifierProvider(create: (_) => ActionCatalogueProvider()) + ChangeNotifierProvider(create: (_) => ActionCatalogueProvider(), lazy: false) ], child: const Aeris())); } diff --git a/mobile/lib/src/providers/action_catalogue_provider.dart b/mobile/lib/src/providers/action_catalogue_provider.dart index 9ea74d5..f0dc343 100644 --- a/mobile/lib/src/providers/action_catalogue_provider.dart +++ b/mobile/lib/src/providers/action_catalogue_provider.dart @@ -9,17 +9,18 @@ import 'package:get_it/get_it.dart'; class ActionCatalogueProvider extends ChangeNotifier { /// Tells if the provers has loaded data at least once - late Map> _triggerTemplates; - late Map> _reactionTemplates; + final Map> _triggerTemplates = {}; + final Map> _reactionTemplates = {}; Map> get triggerTemplates => _triggerTemplates; Map> get reactionTemplates => _reactionTemplates; ActionCatalogueProvider() { + Service.all().forEach((element) { + _triggerTemplates.putIfAbsent(element, () => []); + _reactionTemplates.putIfAbsent(element, () => []); + }); + notifyListeners(); GetIt.I().getAbout().then((Map about) { - Service.all().forEach((element) { - _triggerTemplates.putIfAbsent(element, () => []); - _reactionTemplates.putIfAbsent(element, () => []); - }); final services = (about['server'] as Map)['services'] as List; for (var serviceContent in services) { Service service = Service.factory(serviceContent['name']); From 8091a5ec7c594a3af2c988d3088f99185c3872f0 Mon Sep 17 00:00:00 2001 From: Arthi-chaud Date: Tue, 1 Mar 2022 18:33:36 +0100 Subject: [PATCH 056/109] Mobile Client: API: login, signup + about.json --- mobile/lib/l10n/app_fr.arb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/mobile/lib/l10n/app_fr.arb b/mobile/lib/l10n/app_fr.arb index 3c7d50c..79b3d5c 100644 --- a/mobile/lib/l10n/app_fr.arb +++ b/mobile/lib/l10n/app_fr.arb @@ -6,8 +6,8 @@ "today": "Aujourd'hui", "nameOfThePipeline": "Nom de la pipeline", "addReaction": "Ajouter une Reaction", - "addTrigger": "Ajouter un Déclancheur", - "setupTrigger": "Gérer une Déclancheur", + "addTrigger": "Ajouter un déclencheur", + "setupTrigger": "Gérer un déclencheur", "setupReaction": "Gérer une Réaction", "action": "Action", "reactions": "Réactions", @@ -33,7 +33,7 @@ "dangerZone": "Zone dangereuse", "createNewPipeline": "Créer une nouvelle pipeline", "save": "Enregistrer", - "pipelineFormMisingAction": "Vous devez selectionner au moins un déclancheur et une réaction", + "pipelineFormMisingAction": "Vous devez selectionner au moins un déclencheur et une réaction", "logoutWarningMessage": "Êtes-vous sûr(e) de voulour vous déconnecter d'Aeris?", "warning": "Attention", "cancel": "Annuler", From c6608096c3114e0599de5c5a833eaa3438a64819 Mon Sep 17 00:00:00 2001 From: Arthi-chaud Date: Tue, 1 Mar 2022 18:39:12 +0100 Subject: [PATCH 057/109] Mobile Client: recase --- mobile/lib/src/views/setup_action_page.dart | 3 ++- mobile/lib/src/widgets/action_form.dart | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/mobile/lib/src/views/setup_action_page.dart b/mobile/lib/src/views/setup_action_page.dart index 973a56d..8af5edf 100644 --- a/mobile/lib/src/views/setup_action_page.dart +++ b/mobile/lib/src/views/setup_action_page.dart @@ -10,6 +10,7 @@ import 'package:aeris/src/widgets/aeris_card_page.dart'; import 'package:expandable/expandable.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:get_it/get_it.dart'; +import 'package:recase/recase.dart'; import 'package:skeleton_loader/skeleton_loader.dart'; ///Page to setup an action @@ -119,7 +120,7 @@ class _SetupActionPageState extends State { header: Padding( padding: const EdgeInsets.only(left: 30, top: 20, bottom: 20), - child: Text(availableAction.name, + child: Text(ReCase(availableAction.name).titleCase, style: const TextStyle(fontSize: 15))), collapsed: Container(), expanded: Padding( diff --git a/mobile/lib/src/widgets/action_form.dart b/mobile/lib/src/widgets/action_form.dart index d563cf3..63a1ce7 100644 --- a/mobile/lib/src/widgets/action_form.dart +++ b/mobile/lib/src/widgets/action_form.dart @@ -3,6 +3,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_form_builder/flutter_form_builder.dart'; import 'package:form_builder_validators/form_builder_validators.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; +import 'package:recase/recase.dart'; /// Form for an action class ActionForm extends StatefulWidget { @@ -42,7 +43,7 @@ class _ActionFormState extends State { initialValue: param.value?.toString(), name: param.name, decoration: InputDecoration( - labelText: param.name, + labelText: ReCase(param.name).titleCase, helperText: param.description ), validator: FormBuilderValidators.compose([ From dc4851464ebd9705a35595e2445d6aa5f90153ef Mon Sep 17 00:00:00 2001 From: Arthi-chaud Date: Wed, 2 Mar 2022 08:39:20 +0100 Subject: [PATCH 058/109] Mobile Client: serizlization of action parameter --- mobile/lib/src/models/action_parameter.dart | 2 ++ 1 file changed, 2 insertions(+) diff --git a/mobile/lib/src/models/action_parameter.dart b/mobile/lib/src/models/action_parameter.dart index 5acae7f..36c1b3e 100644 --- a/mobile/lib/src/models/action_parameter.dart +++ b/mobile/lib/src/models/action_parameter.dart @@ -14,6 +14,8 @@ class ActionParameter { ActionParameter( {Key? key, required this.name, this.description = "", this.value}); + MapEntry toJson() => MapEntry(name, value); + static List fromJSON(Map params) { List actionParameters = []; params.forEach((key, value) => From 37c92ad01f8f2044515d55b4fc6dfa93089ad31b Mon Sep 17 00:00:00 2001 From: Arthi-chaud Date: Wed, 2 Mar 2022 08:39:57 +0100 Subject: [PATCH 059/109] Mobile Client: Pipeline fix unserialize --- mobile/lib/src/models/pipeline.dart | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/mobile/lib/src/models/pipeline.dart b/mobile/lib/src/models/pipeline.dart index b801cbb..211e2ee 100644 --- a/mobile/lib/src/models/pipeline.dart +++ b/mobile/lib/src/models/pipeline.dart @@ -31,9 +31,9 @@ class Pipeline { required this.reactions}); /// Unserialize Pipeline from JSON - static Pipeline fromJSON(Map data) { - var action = data['action'] as Map; - var reactions = data['reactions'] as Map; + static Pipeline fromJSON(Map data) { + var action = data['action'] as Map; + var reactions = data['reactions'] as List; return Pipeline( name: action['name'] as String, @@ -41,7 +41,7 @@ class Pipeline { id: action['id'] as int, triggerCount: action['triggerCount'] as int, trigger: Trigger.fromJSON(action), - reactions: (reactions as List) + reactions: reactions .map((e) => Reaction.fromJSON(e)) .toList()); } @@ -52,7 +52,7 @@ class Pipeline { "id": id, "name": name, "pType": aeris_action.Action.getType(trigger.service, trigger.name), - "pParams": trigger.parameters, + "pParams": { for (var e in trigger.parameters) e.name : e.value }, ///Serialize "enabled": enabled, "lastTrigger": trigger.last?.toIso8601String(), "triggerCount": triggerCount From 02c01d4135eef1b93863817d498b4ad92a12d237 Mon Sep 17 00:00:00 2001 From: Arthi-chaud Date: Wed, 2 Mar 2022 08:40:28 +0100 Subject: [PATCH 060/109] Mobile Client: Reaction fix serialization --- mobile/lib/src/models/reaction.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mobile/lib/src/models/reaction.dart b/mobile/lib/src/models/reaction.dart index 9b85eb3..94c5176 100644 --- a/mobile/lib/src/models/reaction.dart +++ b/mobile/lib/src/models/reaction.dart @@ -32,7 +32,7 @@ class Reaction extends aeris_action.Action { /// Serialize Reaction to JSON Object toJSON() => { "rType": aeris_action.Action.getType(service, name), - "rParams": parameters + "rParams": { for (var e in parameters) e.name : e.value } }; @override From 1b60ecf4507c3f3cb6d360bd8a43df04ed6a8540 Mon Sep 17 00:00:00 2001 From: Arthi-chaud Date: Wed, 2 Mar 2022 09:14:32 +0100 Subject: [PATCH 061/109] Mobile Client: fix form generation --- mobile/lib/src/aeris_api.dart | 9 ++++++--- mobile/lib/src/views/create_pipeline_page.dart | 5 +++-- mobile/lib/src/views/setup_action_page.dart | 12 ++++-------- mobile/lib/src/widgets/action_form.dart | 1 + 4 files changed, 14 insertions(+), 13 deletions(-) diff --git a/mobile/lib/src/aeris_api.dart b/mobile/lib/src/aeris_api.dart index 68c250d..09f3c1c 100644 --- a/mobile/lib/src/aeris_api.dart +++ b/mobile/lib/src/aeris_api.dart @@ -113,8 +113,10 @@ class AerisAPI { /// Adds new pipeline to API, returns false if post failed Future createPipeline(Pipeline newPipeline) async { + return true; var res = await _requestAPI( '/workflow', AerisAPIRequestType.post, newPipeline.toJSON()); + print(jsonDecode(res.body)); ///TODO could not be fetched back newPipeline = Pipeline.fromJSON(jsonDecode(res.body)); return res.ok; } @@ -143,8 +145,9 @@ class AerisAPI { var res = await _requestAPI('/workflows', AerisAPIRequestType.get, null); if (res.ok == false) return []; final List body = jsonDecode(res.body); + print(body); - return body.map((e) => Pipeline.fromJSON(e)).toList(); + return body.map((e) => Pipeline.fromJSON(Map.from(e))).toList(); } /// Fetch the services the user is authenticated to @@ -172,8 +175,8 @@ class AerisAPI { return res.ok; } - Future> getActionsFor( - Service service, Action action) async { + List getActionsFor( + Service service, Action action) { final catalogue = Aeris.materialKey.currentContext?.read(); if (action is Trigger) { return catalogue!.triggerTemplates[service]!; diff --git a/mobile/lib/src/views/create_pipeline_page.dart b/mobile/lib/src/views/create_pipeline_page.dart index 94ba951..e456bde 100644 --- a/mobile/lib/src/views/create_pipeline_page.dart +++ b/mobile/lib/src/views/create_pipeline_page.dart @@ -14,6 +14,7 @@ import 'package:flutter_form_builder/flutter_form_builder.dart'; import 'package:aeris/src/widgets/colored_clickable_card.dart'; import 'package:provider/provider.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; +import 'package:recase/recase.dart'; /// Page to create a new pipeline class CreatePipelinePage extends StatefulWidget { @@ -85,7 +86,7 @@ class _CreatePipelinePageState extends State { }) : ActionCard( leading: trigger.service.getLogo(logoSize: 50), - title: trigger.name, + title: ReCase(trigger.name).titleCase, trailing: ActionCardPopupMenu( deletable: false, action: trigger, @@ -104,7 +105,7 @@ class _CreatePipelinePageState extends State { itemBuilder: (reaction) => ActionCard( key: ValueKey(reactions.indexOf(reaction)), leading: reaction.service.getLogo(logoSize: 50), - title: reaction.name, + title: ReCase(reaction.name).titleCase, trailing: ActionCardPopupMenu( deletable: reactions.length > 1, action: reaction, diff --git a/mobile/lib/src/views/setup_action_page.dart b/mobile/lib/src/views/setup_action_page.dart index 8af5edf..a76fb40 100644 --- a/mobile/lib/src/views/setup_action_page.dart +++ b/mobile/lib/src/views/setup_action_page.dart @@ -32,9 +32,7 @@ class _SetupActionPageState extends State { void initState() { super.initState(); serviceState = widget.action.service; - GetIt.I().getActionsFor(serviceState!, widget.action).then((actions) => setState(() { - availableActions = actions; - })); + availableActions = GetIt.I().getActionsFor(serviceState!, widget.action); } @override @@ -47,11 +45,8 @@ class _SetupActionPageState extends State { onChanged: (service) { setState(() { serviceState = service; - availableActions = null; + availableActions = GetIt.I().getActionsFor(service!, widget.action); }); - GetIt.I().getActionsFor(service!, widget.action).then((actions) => setState(() { - availableActions = actions; - })); }, items: Service.all().map>((Service service) { return DropdownMenuItem( @@ -126,9 +121,10 @@ class _SetupActionPageState extends State { expanded: Padding( padding: const EdgeInsets.all(20), child: ActionForm( + key: Key("${availableAction.name}${availableAction.description}${availableAction.service}"), description: availableAction.description!, name: availableAction.name, - parameters: availableAction.parameters.map((param) { + parameters: availableAction.parameters.map((param) { if (widget.action.service.name == serviceState!.name && widget.action.name == availableAction.name) { var previousParams = widget.action.parameters.where((element) => element.name == param.name); if (previousParams.isNotEmpty) { diff --git a/mobile/lib/src/widgets/action_form.dart b/mobile/lib/src/widgets/action_form.dart index 63a1ce7..deed3eb 100644 --- a/mobile/lib/src/widgets/action_form.dart +++ b/mobile/lib/src/widgets/action_form.dart @@ -37,6 +37,7 @@ class _ActionFormState extends State { return FormBuilder( key: _formKey, child: Column( + crossAxisAlignment: CrossAxisAlignment.center, children: [ Text(widget.description, textAlign: TextAlign.left, style: TextStyle(color: Theme.of(context).colorScheme.onSurface)), ...widget.parameters.map((param) => FormBuilderTextField( From b3a0d5c21938befddef90966330e26ad7b2da3ab Mon Sep 17 00:00:00 2001 From: Arthi-chaud Date: Wed, 2 Mar 2022 09:27:23 +0100 Subject: [PATCH 062/109] Mobile Client: fix pType --- mobile/lib/src/aeris_api.dart | 2 +- mobile/lib/src/models/action.dart | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/mobile/lib/src/aeris_api.dart b/mobile/lib/src/aeris_api.dart index 09f3c1c..df1ae71 100644 --- a/mobile/lib/src/aeris_api.dart +++ b/mobile/lib/src/aeris_api.dart @@ -113,7 +113,7 @@ class AerisAPI { /// Adds new pipeline to API, returns false if post failed Future createPipeline(Pipeline newPipeline) async { - return true; + print(newPipeline.toJSON()); var res = await _requestAPI( '/workflow', AerisAPIRequestType.post, newPipeline.toJSON()); print(jsonDecode(res.body)); ///TODO could not be fetched back diff --git a/mobile/lib/src/models/action.dart b/mobile/lib/src/models/action.dart index 54dd2e9..fca41b2 100644 --- a/mobile/lib/src/models/action.dart +++ b/mobile/lib/src/models/action.dart @@ -33,7 +33,7 @@ abstract class Action { static String getType(Service service, String aName) { String serviceName = ReCase(service.name).pascalCase; - String actionName = ReCase(aName).paramCase; + String actionName = ReCase(aName).pascalCase; return "$serviceName$actionName"; } } From 4301f4083a32815199eca225c2896b93dfadd5b4 Mon Sep 17 00:00:00 2001 From: Arthi-chaud Date: Wed, 2 Mar 2022 15:31:42 +0100 Subject: [PATCH 063/109] Mobile Client: Button + modal to setup api route (no form) --- mobile/lib/src/aeris_api.dart | 13 ++-- mobile/lib/src/views/login_page.dart | 2 + mobile/lib/src/views/startup_page.dart | 2 + mobile/lib/src/widgets/setup_api_route.dart | 77 +++++++++++++++++++++ mobile/pubspec.lock | 7 ++ 5 files changed, 97 insertions(+), 4 deletions(-) create mode 100644 mobile/lib/src/widgets/setup_api_route.dart diff --git a/mobile/lib/src/aeris_api.dart b/mobile/lib/src/aeris_api.dart index 511fbda..9bd6344 100644 --- a/mobile/lib/src/aeris_api.dart +++ b/mobile/lib/src/aeris_api.dart @@ -34,7 +34,9 @@ class AerisAPI { /// JWT token used to request API late String _jwt; - final String baseRoute = "http://10.0.2.2:8080"; ///TODO make it modifiable + String _baseRoute = "http://10.0.2.2:8080"; ///TODO make it modifiable + String get baseRoute => _baseRoute; + set baseRoute(value) => _baseRoute = value; AerisAPI() { var trigger1 = Trigger( @@ -182,7 +184,7 @@ class AerisAPI { String getServiceAuthURL(Service service) { final serviceName = service.name.toLowerCase(); - return "$baseRoute/auth/$serviceName/url?redirect_uri=aeris://aeris.com/authorization/$serviceName"; + return "$_baseRoute/auth/$serviceName/url?redirect_uri=aeris://aeris.com/authorization/$serviceName"; } /// Send PUT request to update Pipeline, returns false if failed @@ -238,7 +240,7 @@ class AerisAPI { /// Encodes Uri for request Uri _encoreUri(String route) { - return Uri.parse('$baseRoute$route'); + return Uri.parse('$_baseRoute$route'); } /// Calls API using a HTTP request type, a route and body @@ -251,7 +253,10 @@ class AerisAPI { return await http.delete(_encoreUri(route), body: body, headers: header); case AerisAPIRequestType.get: - return await http.get(_encoreUri(route), headers: header); + return await http.get(_encoreUri(route), headers: header).timeout(const Duration(seconds: 2), + onTimeout: () { + return http.Response('Error', 408); // Request Timeout response status code + },); case AerisAPIRequestType.post: return await http.post(_encoreUri(route), body: body, headers: header); case AerisAPIRequestType.put: diff --git a/mobile/lib/src/views/login_page.dart b/mobile/lib/src/views/login_page.dart index 35c59f6..a11ae6c 100644 --- a/mobile/lib/src/views/login_page.dart +++ b/mobile/lib/src/views/login_page.dart @@ -1,6 +1,7 @@ import 'package:aeris/src/aeris_api.dart'; import 'package:aeris/src/main.dart'; import 'package:aeris/src/widgets/aeris_page.dart'; +import 'package:aeris/src/widgets/setup_api_route.dart'; import 'package:flutter_login/flutter_login.dart'; import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; @@ -37,6 +38,7 @@ class LoginPage extends StatelessWidget { @override Widget build(BuildContext context) { return AerisPage( + floatingActionButton: const SetupAPIRouteButton(), displayAppbar: false, body: FlutterLogin( disableCustomPageTransformer: true, diff --git a/mobile/lib/src/views/startup_page.dart b/mobile/lib/src/views/startup_page.dart index f42d281..4a9f884 100644 --- a/mobile/lib/src/views/startup_page.dart +++ b/mobile/lib/src/views/startup_page.dart @@ -1,4 +1,5 @@ import 'package:aeris/src/aeris_api.dart'; +import 'package:aeris/src/widgets/setup_api_route.dart'; import 'package:flutter_fadein/flutter_fadein.dart'; import 'package:flutter/material.dart'; import 'package:get_it/get_it.dart'; @@ -20,6 +21,7 @@ class _StartupPageState extends State { bool isConnected = GetIt.I().isConnected; return AerisPage( displayAppbar: false, + floatingActionButton: const SetupAPIRouteButton(), body: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ diff --git a/mobile/lib/src/widgets/setup_api_route.dart b/mobile/lib/src/widgets/setup_api_route.dart new file mode 100644 index 0000000..884393f --- /dev/null +++ b/mobile/lib/src/widgets/setup_api_route.dart @@ -0,0 +1,77 @@ +import 'package:aeris/src/aeris_api.dart'; +import 'package:flutter/material.dart'; +import 'package:get_it/get_it.dart'; + +/// Floating Action button to access the setup API route modal +class SetupAPIRouteButton extends StatefulWidget { + const SetupAPIRouteButton({Key? key}) : super(key: key); + + @override + State createState() => _SetupAPIRouteButtonState(); +} + +class _SetupAPIRouteButtonState extends State { + bool? connected; + + @override + void initState() { + super.initState(); + GetIt.I().getAbout().then((value) { + setState(() { + connected = value.isNotEmpty; + }); + }); + } + @override + Widget build(BuildContext context) { + return FloatingActionButton( + onPressed: () => showDialog( + context: context, + builder: (_) => const SetupAPIRouteModal() + ), + backgroundColor: Theme.of(context).colorScheme.secondary, + elevation: 10, + child: Icon( + connected == true + ? Icons.wifi + : Icons.signal_cellular_connected_no_internet_0_bar_sharp + ), + ); + } +} + +/// Modal to setup route to connect to api +class SetupAPIRouteModal extends StatefulWidget { + const SetupAPIRouteModal({Key? key}) : super(key: key); + + @override + State createState() => _SetupAPIRouteModalState(); +} + +class _SetupAPIRouteModalState extends State { + bool? connected; + + @override + void initState() { + super.initState(); + GetIt.I().getAbout().then((value) { + setState(() { + connected = value.isNotEmpty; + }); + }); + } + + @override + Widget build(BuildContext context) { + return AlertDialog( + title: Text("Setup API Route"),///TODO translate + content: Text("CONTENT"),///TODO form + actions: [ + ElevatedButton( + child: Text(connected == true ? "Save" : "Can't save"), + onPressed: + connected == true ? () => Navigator.of(context).pop() : null, + ) + ]); + } +} diff --git a/mobile/pubspec.lock b/mobile/pubspec.lock index 78a9e6b..a79b985 100644 --- a/mobile/pubspec.lock +++ b/mobile/pubspec.lock @@ -167,6 +167,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "3.3.0" + flutter_dotenv: + dependency: "direct main" + description: + name: flutter_dotenv + url: "https://pub.dartlang.org" + source: hosted + version: "5.0.2" flutter_fadein: dependency: "direct main" description: From f2c935ba2f39bca8bb405c7e50122cadb3f3a0c6 Mon Sep 17 00:00:00 2001 From: Arthi-chaud Date: Wed, 2 Mar 2022 16:26:42 +0100 Subject: [PATCH 064/109] Mobile Client: Check route api status on field change --- mobile/lib/src/aeris_api.dart | 31 +++++++++-------- mobile/lib/src/views/login_page.dart | 1 - mobile/lib/src/widgets/setup_api_route.dart | 37 +++++++++++++++++++-- 3 files changed, 53 insertions(+), 16 deletions(-) diff --git a/mobile/lib/src/aeris_api.dart b/mobile/lib/src/aeris_api.dart index 9bd6344..fb0164d 100644 --- a/mobile/lib/src/aeris_api.dart +++ b/mobile/lib/src/aeris_api.dart @@ -163,6 +163,7 @@ class AerisAPI { ///Get /about.json Future> getAbout() async { var res = await _requestAPI('/about.json', AerisAPIRequestType.get, null); + if (!res.ok) return {}; return jsonDecode(res.body); } @@ -248,19 +249,23 @@ class AerisAPI { String route, AerisAPIRequestType requestType, Object? body) async { final Map? header = _connected ? {'Authorization': 'Bearer $_jwt'} : null; - switch (requestType) { - case AerisAPIRequestType.delete: - return await http.delete(_encoreUri(route), - body: body, headers: header); - case AerisAPIRequestType.get: - return await http.get(_encoreUri(route), headers: header).timeout(const Duration(seconds: 2), - onTimeout: () { - return http.Response('Error', 408); // Request Timeout response status code - },); - case AerisAPIRequestType.post: - return await http.post(_encoreUri(route), body: body, headers: header); - case AerisAPIRequestType.put: - return await http.put(_encoreUri(route), body: body, headers: header); + try { + switch (requestType) { + case AerisAPIRequestType.delete: + return await http.delete(_encoreUri(route), + body: body, headers: header); + case AerisAPIRequestType.get: + return await http.get(_encoreUri(route), headers: header).timeout(const Duration(seconds: 2), + onTimeout: () { + return http.Response('Error', 408); // Request Timeout response status code + },); + case AerisAPIRequestType.post: + return await http.post(_encoreUri(route), body: body, headers: header); + case AerisAPIRequestType.put: + return await http.put(_encoreUri(route), body: body, headers: header); + } + } catch (e) { + return http.Response('{}', 400); } } } diff --git a/mobile/lib/src/views/login_page.dart b/mobile/lib/src/views/login_page.dart index a11ae6c..f4663b1 100644 --- a/mobile/lib/src/views/login_page.dart +++ b/mobile/lib/src/views/login_page.dart @@ -38,7 +38,6 @@ class LoginPage extends StatelessWidget { @override Widget build(BuildContext context) { return AerisPage( - floatingActionButton: const SetupAPIRouteButton(), displayAppbar: false, body: FlutterLogin( disableCustomPageTransformer: true, diff --git a/mobile/lib/src/widgets/setup_api_route.dart b/mobile/lib/src/widgets/setup_api_route.dart index 884393f..6766e5e 100644 --- a/mobile/lib/src/widgets/setup_api_route.dart +++ b/mobile/lib/src/widgets/setup_api_route.dart @@ -1,5 +1,6 @@ import 'package:aeris/src/aeris_api.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_form_builder/flutter_form_builder.dart'; import 'package:get_it/get_it.dart'; /// Floating Action button to access the setup API route modal @@ -28,7 +29,13 @@ class _SetupAPIRouteButtonState extends State { onPressed: () => showDialog( context: context, builder: (_) => const SetupAPIRouteModal() - ), + ).then((_) => setState(() { + GetIt.I().getAbout().then((value) { + setState(() { + connected = value.isNotEmpty; + }); + }); + })), backgroundColor: Theme.of(context).colorScheme.secondary, elevation: 10, child: Icon( @@ -50,6 +57,7 @@ class SetupAPIRouteModal extends StatefulWidget { class _SetupAPIRouteModalState extends State { bool? connected; + final _formKey = GlobalKey(); @override void initState() { @@ -65,7 +73,32 @@ class _SetupAPIRouteModalState extends State { Widget build(BuildContext context) { return AlertDialog( title: Text("Setup API Route"),///TODO translate - content: Text("CONTENT"),///TODO form + content: FormBuilder( + key: _formKey, + child: FormBuilderTextField( + initialValue: GetIt.I().baseRoute , + name: "route", + autovalidateMode: AutovalidateMode.onUserInteraction, + validator: (host) { + if (host == GetIt.I().baseRoute && connected == true) { + return; + } + if (Uri.tryParse(host ?? "") == null) { + setState(() => connected = false); + } else { + GetIt.I().baseRoute = host; + GetIt.I().getAbout().then((value) { + setState(() { + connected = value.isNotEmpty; + }); + }); + } + }, + decoration: InputDecoration( + labelText: "Route to API", ///TODO transalte + helperText: "Ex: http://host:port" + )), + ), actions: [ ElevatedButton( child: Text(connected == true ? "Save" : "Can't save"), From 16836c6dde1c32de4078ee73158044cf3d508c3a Mon Sep 17 00:00:00 2001 From: Arthi-chaud Date: Wed, 2 Mar 2022 16:54:39 +0100 Subject: [PATCH 065/109] Mobile Client: Setup API Route: better route management --- mobile/lib/src/widgets/setup_api_route.dart | 52 ++++++++++++++------- 1 file changed, 34 insertions(+), 18 deletions(-) diff --git a/mobile/lib/src/widgets/setup_api_route.dart b/mobile/lib/src/widgets/setup_api_route.dart index 6766e5e..48f1c7c 100644 --- a/mobile/lib/src/widgets/setup_api_route.dart +++ b/mobile/lib/src/widgets/setup_api_route.dart @@ -1,6 +1,7 @@ import 'package:aeris/src/aeris_api.dart'; import 'package:flutter/material.dart'; import 'package:flutter_form_builder/flutter_form_builder.dart'; +import 'package:form_builder_validators/form_builder_validators.dart'; import 'package:get_it/get_it.dart'; /// Floating Action button to access the setup API route modal @@ -76,32 +77,47 @@ class _SetupAPIRouteModalState extends State { content: FormBuilder( key: _formKey, child: FormBuilderTextField( - initialValue: GetIt.I().baseRoute , + initialValue: GetIt.I().baseRoute, name: "route", - autovalidateMode: AutovalidateMode.onUserInteraction, - validator: (host) { - if (host == GetIt.I().baseRoute && connected == true) { - return; - } - if (Uri.tryParse(host ?? "") == null) { - setState(() => connected = false); - } else { - GetIt.I().baseRoute = host; - GetIt.I().getAbout().then((value) { - setState(() { - connected = value.isNotEmpty; - }); - }); - } - }, + validator: FormBuilderValidators.required(context), decoration: InputDecoration( labelText: "Route to API", ///TODO transalte helperText: "Ex: http://host:port" )), ), + actionsAlignment: MainAxisAlignment.spaceEvenly, actions: [ ElevatedButton( - child: Text(connected == true ? "Save" : "Can't save"), + child: Text("Try to connect"),///TODO translate + onPressed: () { + _formKey.currentState!.save(); + if (_formKey.currentState!.validate()) { + var route = _formKey.currentState!.value['route']; + if (Uri.tryParse(route) == null) { + setState(() => connected = false); + } else { + final oldRoute = GetIt.I().baseRoute; + GetIt.I().baseRoute = route; + setState(() { + connected = null; + }); + GetIt.I().getAbout().then((value) { + setState(() { + connected = value.isNotEmpty; + }); + }, onError: (_) => GetIt.I().baseRoute = oldRoute); + } + } + }, + ), + ElevatedButton( + child: Text( + connected == null + ? "Loading..." + : connected == true + ? "Save" + : "Invalid URL" + ), ///TODO translate onPressed: connected == true ? () => Navigator.of(context).pop() : null, ) From 1d1402c5b44a9c01508f62774d62bfdecdfffb95 Mon Sep 17 00:00:00 2001 From: Arthi-chaud Date: Wed, 2 Mar 2022 17:13:46 +0100 Subject: [PATCH 066/109] Mobile Client: Setup API Route: refresh startup page on modal close --- mobile/lib/src/views/startup_page.dart | 24 ++++++- mobile/lib/src/widgets/setup_api_route.dart | 70 +++++++++------------ 2 files changed, 50 insertions(+), 44 deletions(-) diff --git a/mobile/lib/src/views/startup_page.dart b/mobile/lib/src/views/startup_page.dart index 4a9f884..5f9b8c1 100644 --- a/mobile/lib/src/views/startup_page.dart +++ b/mobile/lib/src/views/startup_page.dart @@ -16,12 +16,30 @@ class StartupPage extends StatefulWidget { } class _StartupPageState extends State { + bool connected = false; + @override + void initState() { + super.initState(); + GetIt.I().getAbout().then((value) { + setState(() { + connected = value.isNotEmpty; + }); + }); + } + @override Widget build(BuildContext context) { bool isConnected = GetIt.I().isConnected; return AerisPage( displayAppbar: false, - floatingActionButton: const SetupAPIRouteButton(), + floatingActionButton: SetupAPIRouteButton( + connected: connected, + onSetup: () => GetIt.I().getAbout().then((value) { + setState(() { + connected = value.isNotEmpty; + }); + }) + ), body: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ @@ -50,13 +68,13 @@ class _StartupPageState extends State { textStyle: const TextStyle(fontSize: 20), primary: Theme.of(context).colorScheme.secondary, ), - onPressed: () { + onPressed: connected ? () { if (isConnected) { Navigator.of(context).pushNamedAndRemoveUntil('/home', (route) => false); } else { Navigator.of(context).pushNamed('/login'); } - }, + } : null, child: Tooltip( message: 'Connexion', child: Text(AppLocalizations.of(context).connect) diff --git a/mobile/lib/src/widgets/setup_api_route.dart b/mobile/lib/src/widgets/setup_api_route.dart index 48f1c7c..1034dfa 100644 --- a/mobile/lib/src/widgets/setup_api_route.dart +++ b/mobile/lib/src/widgets/setup_api_route.dart @@ -6,44 +6,27 @@ import 'package:get_it/get_it.dart'; /// Floating Action button to access the setup API route modal class SetupAPIRouteButton extends StatefulWidget { - const SetupAPIRouteButton({Key? key}) : super(key: key); + ///Can the app access the api with the current baseRoute? + bool connected; + void Function() onSetup; + SetupAPIRouteButton({Key? key, required this.connected, required this.onSetup}) : super(key: key); @override State createState() => _SetupAPIRouteButtonState(); } class _SetupAPIRouteButtonState extends State { - bool? connected; - - @override - void initState() { - super.initState(); - GetIt.I().getAbout().then((value) { - setState(() { - connected = value.isNotEmpty; - }); - }); - } @override Widget build(BuildContext context) { return FloatingActionButton( onPressed: () => showDialog( - context: context, - builder: (_) => const SetupAPIRouteModal() - ).then((_) => setState(() { - GetIt.I().getAbout().then((value) { - setState(() { - connected = value.isNotEmpty; - }); - }); - })), + context: context, builder: (_) => const SetupAPIRouteModal()) + .then((_) => widget.onSetup()), backgroundColor: Theme.of(context).colorScheme.secondary, elevation: 10, - child: Icon( - connected == true + child: Icon(widget.connected == true ? Icons.wifi - : Icons.signal_cellular_connected_no_internet_0_bar_sharp - ), + : Icons.signal_cellular_connected_no_internet_0_bar_sharp), ); } } @@ -73,22 +56,27 @@ class _SetupAPIRouteModalState extends State { @override Widget build(BuildContext context) { return AlertDialog( - title: Text("Setup API Route"),///TODO translate + title: Text("Setup API Route"), + + ///TODO translate content: FormBuilder( key: _formKey, child: FormBuilderTextField( - initialValue: GetIt.I().baseRoute, - name: "route", - validator: FormBuilderValidators.required(context), - decoration: InputDecoration( - labelText: "Route to API", ///TODO transalte - helperText: "Ex: http://host:port" - )), + initialValue: GetIt.I().baseRoute, + name: "route", + validator: FormBuilderValidators.required(context), + decoration: InputDecoration( + labelText: "Route to API", + + ///TODO transalte + helperText: "Ex: http://host:port")), ), actionsAlignment: MainAxisAlignment.spaceEvenly, actions: [ ElevatedButton( - child: Text("Try to connect"),///TODO translate + child: Text("Try to connect"), + + ///TODO translate onPressed: () { _formKey.currentState!.save(); if (_formKey.currentState!.validate()) { @@ -111,13 +99,13 @@ class _SetupAPIRouteModalState extends State { }, ), ElevatedButton( - child: Text( - connected == null - ? "Loading..." - : connected == true - ? "Save" - : "Invalid URL" - ), ///TODO translate + child: Text(connected == null + ? "Loading..." + : connected == true + ? "Save" + : "Invalid URL"), + + ///TODO translate onPressed: connected == true ? () => Navigator.of(context).pop() : null, ) From 75548205c6e22567b952f157e57ee1b8a180ef2e Mon Sep 17 00:00:00 2001 From: Arthi-chaud Date: Wed, 2 Mar 2022 17:43:04 +0100 Subject: [PATCH 067/109] Mobile Client: Using 'shared preferences' t ostore values on diskk --- mobile/ios/Podfile.lock | 6 ++ mobile/lib/src/aeris_api.dart | 26 +++------ mobile/lib/src/main.dart | 4 ++ mobile/lib/src/widgets/setup_api_route.dart | 14 ++++- mobile/pubspec.lock | 63 ++++++++++++++++++--- mobile/pubspec.yaml | 1 + 6 files changed, 86 insertions(+), 28 deletions(-) diff --git a/mobile/ios/Podfile.lock b/mobile/ios/Podfile.lock index fafbab7..c8c8bf3 100644 --- a/mobile/ios/Podfile.lock +++ b/mobile/ios/Podfile.lock @@ -5,6 +5,8 @@ PODS: - FMDB/standard (2.7.5) - path_provider_ios (0.0.1): - Flutter + - shared_preferences_ios (0.0.1): + - Flutter - sqflite (0.0.2): - Flutter - FMDB (>= 2.7.5) @@ -14,6 +16,7 @@ PODS: DEPENDENCIES: - Flutter (from `Flutter`) - path_provider_ios (from `.symlinks/plugins/path_provider_ios/ios`) + - shared_preferences_ios (from `.symlinks/plugins/shared_preferences_ios/ios`) - sqflite (from `.symlinks/plugins/sqflite/ios`) - url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`) @@ -26,6 +29,8 @@ EXTERNAL SOURCES: :path: Flutter path_provider_ios: :path: ".symlinks/plugins/path_provider_ios/ios" + shared_preferences_ios: + :path: ".symlinks/plugins/shared_preferences_ios/ios" sqflite: :path: ".symlinks/plugins/sqflite/ios" url_launcher_ios: @@ -35,6 +40,7 @@ SPEC CHECKSUMS: Flutter: 50d75fe2f02b26cc09d224853bb45737f8b3214a FMDB: 2ce00b547f966261cd18927a3ddb07cb6f3db82a path_provider_ios: 7d7ce634493af4477d156294792024ec3485acd5 + shared_preferences_ios: 548a61f8053b9b8a49ac19c1ffbc8b92c50d68ad sqflite: 6d358c025f5b867b29ed92fc697fd34924e11904 url_launcher_ios: 839c58cdb4279282219f5e248c3321761ff3c4de diff --git a/mobile/lib/src/aeris_api.dart b/mobile/lib/src/aeris_api.dart index fb0164d..c29c43b 100644 --- a/mobile/lib/src/aeris_api.dart +++ b/mobile/lib/src/aeris_api.dart @@ -12,9 +12,11 @@ import 'package:aeris/src/models/reaction.dart'; import 'package:aeris/src/models/service.dart'; import 'package:aeris/src/models/trigger.dart'; import 'package:aeris/src/providers/action_catalogue_provider.dart'; +import 'package:get_it/get_it.dart'; import 'package:path_provider/path_provider.dart'; import 'package:http/http.dart' as http; import 'package:provider/provider.dart'; +import 'package:shared_preferences/shared_preferences.dart'; extension IsOk on http.Response { bool get ok => (statusCode ~/ 100) == 2; @@ -34,7 +36,8 @@ class AerisAPI { /// JWT token used to request API late String _jwt; - String _baseRoute = "http://10.0.2.2:8080"; ///TODO make it modifiable + String _baseRoute = GetIt.I().getString('api') + ?? "http://10.0.2.2:8080"; String get baseRoute => _baseRoute; set baseRoute(value) => _baseRoute = value; @@ -92,13 +95,6 @@ class AerisAPI { /// Name of the file that contains the JWT used for Aeris' API requestd static const String jwtFile = 'aeris_jwt.txt'; - /// Retrieves the file containing the JWT - Future getJWTFile() async { - final directory = await getApplicationDocumentsDirectory(); - final path = directory.path; - return File('$path/$jwtFile.txt'); - } - ///ROUTES /// Registers new user in the database and connects it. Returns false if register failed Future signUpUser(String username, String password) async { @@ -125,8 +121,7 @@ class AerisAPI { } try { final String jwt = jsonDecode(response.body)['jwt']; - final File jwtFile = await getJWTFile(); - jwtFile.writeAsString(jwt); + await GetIt.I().setString('jwt', jwt); _connected = true; _jwt = jwt; } catch (e) { @@ -138,9 +133,8 @@ class AerisAPI { /// Create an API connection using previously created credentials Future restoreConnection() async { try { - final file = await getJWTFile(); - final cred = await file.readAsString(); - if (cred == "") { + final cred = GetIt.I().getString('jwt'); + if (cred == "" || cred == null) { throw Exception("Empty creds"); } _jwt = cred; @@ -152,11 +146,7 @@ class AerisAPI { /// Delete JWT file and disconnect from API Future stopConnection() async { - File credentials = await getJWTFile(); - - if (credentials.existsSync()) { - await credentials.delete(); - } + await GetIt.I().remove('jwt'); _connected = false; } diff --git a/mobile/lib/src/main.dart b/mobile/lib/src/main.dart index 5324c1c..03a9ddd 100644 --- a/mobile/lib/src/main.dart +++ b/mobile/lib/src/main.dart @@ -13,8 +13,12 @@ import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:get_it/get_it.dart'; +import 'package:shared_preferences/shared_preferences.dart'; void main() async { + WidgetsFlutterBinding.ensureInitialized(); + SharedPreferences prefs = await SharedPreferences.getInstance(); + GetIt.I.registerSingleton(prefs); AerisAPI interface = AerisAPI(); GetIt.I.registerSingleton(interface); await interface.restoreConnection(); diff --git a/mobile/lib/src/widgets/setup_api_route.dart b/mobile/lib/src/widgets/setup_api_route.dart index 1034dfa..faa801e 100644 --- a/mobile/lib/src/widgets/setup_api_route.dart +++ b/mobile/lib/src/widgets/setup_api_route.dart @@ -3,13 +3,16 @@ import 'package:flutter/material.dart'; import 'package:flutter_form_builder/flutter_form_builder.dart'; import 'package:form_builder_validators/form_builder_validators.dart'; import 'package:get_it/get_it.dart'; +import 'package:shared_preferences/shared_preferences.dart'; /// Floating Action button to access the setup API route modal class SetupAPIRouteButton extends StatefulWidget { ///Can the app access the api with the current baseRoute? bool connected; void Function() onSetup; - SetupAPIRouteButton({Key? key, required this.connected, required this.onSetup}) : super(key: key); + SetupAPIRouteButton( + {Key? key, required this.connected, required this.onSetup}) + : super(key: key); @override State createState() => _SetupAPIRouteButtonState(); @@ -106,8 +109,13 @@ class _SetupAPIRouteModalState extends State { : "Invalid URL"), ///TODO translate - onPressed: - connected == true ? () => Navigator.of(context).pop() : null, + onPressed: connected == true + ? () { + GetIt.I() + .setString('api', GetIt.I().baseRoute); + Navigator.of(context).pop(); + } + : null, ) ]); } diff --git a/mobile/pubspec.lock b/mobile/pubspec.lock index a79b985..da0bbec 100644 --- a/mobile/pubspec.lock +++ b/mobile/pubspec.lock @@ -167,13 +167,6 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "3.3.0" - flutter_dotenv: - dependency: "direct main" - description: - name: flutter_dotenv - url: "https://pub.dartlang.org" - source: hosted - version: "5.0.2" flutter_fadein: dependency: "direct main" description: @@ -490,6 +483,62 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "0.27.3" + shared_preferences: + dependency: "direct main" + description: + name: shared_preferences + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.13" + shared_preferences_android: + dependency: transitive + description: + name: shared_preferences_android + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.11" + shared_preferences_ios: + dependency: transitive + description: + name: shared_preferences_ios + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.0" + shared_preferences_linux: + dependency: transitive + description: + name: shared_preferences_linux + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.0" + shared_preferences_macos: + dependency: transitive + description: + name: shared_preferences_macos + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.3" + shared_preferences_platform_interface: + dependency: transitive + description: + name: shared_preferences_platform_interface + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.0" + shared_preferences_web: + dependency: transitive + description: + name: shared_preferences_web + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.3" + shared_preferences_windows: + dependency: transitive + description: + name: shared_preferences_windows + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.0" shimmer: dependency: transitive description: diff --git a/mobile/pubspec.yaml b/mobile/pubspec.yaml index ee0fdba..23e00bd 100644 --- a/mobile/pubspec.yaml +++ b/mobile/pubspec.yaml @@ -57,6 +57,7 @@ dependencies: http: ^0.13.4 tuple: ^2.0.0 loading_indicator: ^3.0.3 + shared_preferences: ^2.0.13 dev_dependencies: flutter_launcher_icons: "^0.9.2" From b3de5b7bcdde440fbcc3d1f483e3be2f09eb97db Mon Sep 17 00:00:00 2001 From: Arthi-chaud Date: Wed, 2 Mar 2022 17:56:14 +0100 Subject: [PATCH 068/109] Mobile Client: set timeout --- mobile/lib/src/aeris_api.dart | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/mobile/lib/src/aeris_api.dart b/mobile/lib/src/aeris_api.dart index c29c43b..4ffb65a 100644 --- a/mobile/lib/src/aeris_api.dart +++ b/mobile/lib/src/aeris_api.dart @@ -103,6 +103,8 @@ class AerisAPI { username: username, password: password, }); + print(response.body); + print(response.statusCode); if (!response.ok) { return false; } @@ -239,20 +241,31 @@ class AerisAPI { String route, AerisAPIRequestType requestType, Object? body) async { final Map? header = _connected ? {'Authorization': 'Bearer $_jwt'} : null; + const duration = Duration(seconds: 3); try { switch (requestType) { case AerisAPIRequestType.delete: return await http.delete(_encoreUri(route), - body: body, headers: header); + body: body, headers: header).timeout(duration, + onTimeout: () { + return http.Response('Error', 408); // Request Timeout response status code + },); case AerisAPIRequestType.get: - return await http.get(_encoreUri(route), headers: header).timeout(const Duration(seconds: 2), + return await http.get(_encoreUri(route), headers: header).timeout( + duration, onTimeout: () { return http.Response('Error', 408); // Request Timeout response status code },); case AerisAPIRequestType.post: - return await http.post(_encoreUri(route), body: body, headers: header); + return await http.post(_encoreUri(route), body: body, headers: header).timeout(duration, + onTimeout: () { + return http.Response('Error', 408); // Request Timeout response status code + },); case AerisAPIRequestType.put: - return await http.put(_encoreUri(route), body: body, headers: header); + return await http.put(_encoreUri(route), body: body, headers: header).timeout(duration, + onTimeout: () { + return http.Response('Error', 408); // Request Timeout response status code + },); } } catch (e) { return http.Response('{}', 400); From 22fef8e50660df11583f59c3500eb84b4abb5919 Mon Sep 17 00:00:00 2001 From: Arthi-chaud Date: Wed, 2 Mar 2022 18:05:17 +0100 Subject: [PATCH 069/109] Mobile Client: remove deug in API interfacd --- mobile/lib/src/aeris_api.dart | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/mobile/lib/src/aeris_api.dart b/mobile/lib/src/aeris_api.dart index 4ffb65a..699b33f 100644 --- a/mobile/lib/src/aeris_api.dart +++ b/mobile/lib/src/aeris_api.dart @@ -103,8 +103,6 @@ class AerisAPI { username: username, password: password, }); - print(response.body); - print(response.statusCode); if (!response.ok) { return false; } @@ -248,23 +246,23 @@ class AerisAPI { return await http.delete(_encoreUri(route), body: body, headers: header).timeout(duration, onTimeout: () { - return http.Response('Error', 408); // Request Timeout response status code + return http.Response('Error', 408); },); case AerisAPIRequestType.get: return await http.get(_encoreUri(route), headers: header).timeout( duration, onTimeout: () { - return http.Response('Error', 408); // Request Timeout response status code + return http.Response('Error', 408); },); case AerisAPIRequestType.post: return await http.post(_encoreUri(route), body: body, headers: header).timeout(duration, onTimeout: () { - return http.Response('Error', 408); // Request Timeout response status code + return http.Response('Error', 408); },); case AerisAPIRequestType.put: return await http.put(_encoreUri(route), body: body, headers: header).timeout(duration, onTimeout: () { - return http.Response('Error', 408); // Request Timeout response status code + return http.Response('Error', 408); },); } } catch (e) { From 7cbd7173c463ee2530eed92b3aa02a7a5462264a Mon Sep 17 00:00:00 2001 From: Arthi-chaud Date: Wed, 2 Mar 2022 18:05:37 +0100 Subject: [PATCH 070/109] Mobile LCient: remove unused param --- mobile/lib/src/views/login_page.dart | 1 - 1 file changed, 1 deletion(-) diff --git a/mobile/lib/src/views/login_page.dart b/mobile/lib/src/views/login_page.dart index f4663b1..35c59f6 100644 --- a/mobile/lib/src/views/login_page.dart +++ b/mobile/lib/src/views/login_page.dart @@ -1,7 +1,6 @@ import 'package:aeris/src/aeris_api.dart'; import 'package:aeris/src/main.dart'; import 'package:aeris/src/widgets/aeris_page.dart'; -import 'package:aeris/src/widgets/setup_api_route.dart'; import 'package:flutter_login/flutter_login.dart'; import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; From e87880ea791810ddbe3c8311ac65522d70c008ef Mon Sep 17 00:00:00 2001 From: Arthi-chaud Date: Wed, 2 Mar 2022 18:15:43 +0100 Subject: [PATCH 071/109] Mobile Client: Translation --- mobile/lib/l10n/app_en.arb | 7 ++++++- mobile/lib/l10n/app_fr.arb | 7 ++++++- mobile/lib/src/widgets/setup_api_route.dart | 17 +++++++---------- 3 files changed, 19 insertions(+), 12 deletions(-) diff --git a/mobile/lib/l10n/app_en.arb b/mobile/lib/l10n/app_en.arb index 0732867..04621aa 100644 --- a/mobile/lib/l10n/app_en.arb +++ b/mobile/lib/l10n/app_en.arb @@ -37,5 +37,10 @@ "logoutWarningMessage": "You are about to logout, are you sure?", "warning": "Warning", "cancel": "Cancel", - "errorOnSignup": "An error occured while signing you up, please try again" + "errorOnSignup": "An error occured while signing you up, please try again", + "loading": "Loading...", + "invalidUrl": "Invalid URL", + "tryToConnect": "Try to connect", + "routeToApi": "Route to API", + "setupAPIRoute": "Setup API Route" } \ No newline at end of file diff --git a/mobile/lib/l10n/app_fr.arb b/mobile/lib/l10n/app_fr.arb index 3c7d50c..d3d68b3 100644 --- a/mobile/lib/l10n/app_fr.arb +++ b/mobile/lib/l10n/app_fr.arb @@ -37,5 +37,10 @@ "logoutWarningMessage": "Êtes-vous sûr(e) de voulour vous déconnecter d'Aeris?", "warning": "Attention", "cancel": "Annuler", - "errorOnSignup": "Une erreur est survenue, veuillez réessayer" + "errorOnSignup": "Une erreur est survenue, veuillez réessayer", + "loading": "Chargement...", + "invalidUrl": "URL invalide", + "tryToConnect": "Tester la connection", + "routeToApi": "Route de l'API", + "setupAPIRoute": "Choisir la route de l'API" } \ No newline at end of file diff --git a/mobile/lib/src/widgets/setup_api_route.dart b/mobile/lib/src/widgets/setup_api_route.dart index faa801e..7f08a1f 100644 --- a/mobile/lib/src/widgets/setup_api_route.dart +++ b/mobile/lib/src/widgets/setup_api_route.dart @@ -4,6 +4,7 @@ import 'package:flutter_form_builder/flutter_form_builder.dart'; import 'package:form_builder_validators/form_builder_validators.dart'; import 'package:get_it/get_it.dart'; import 'package:shared_preferences/shared_preferences.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; /// Floating Action button to access the setup API route modal class SetupAPIRouteButton extends StatefulWidget { @@ -59,7 +60,7 @@ class _SetupAPIRouteModalState extends State { @override Widget build(BuildContext context) { return AlertDialog( - title: Text("Setup API Route"), + title: Text(AppLocalizations.of(context).setupAPIRoute), ///TODO translate content: FormBuilder( @@ -69,17 +70,13 @@ class _SetupAPIRouteModalState extends State { name: "route", validator: FormBuilderValidators.required(context), decoration: InputDecoration( - labelText: "Route to API", - - ///TODO transalte + labelText: AppLocalizations.of(context).routeToApi, helperText: "Ex: http://host:port")), ), actionsAlignment: MainAxisAlignment.spaceEvenly, actions: [ ElevatedButton( - child: Text("Try to connect"), - - ///TODO translate + child: Text(AppLocalizations.of(context).tryToConnect), onPressed: () { _formKey.currentState!.save(); if (_formKey.currentState!.validate()) { @@ -103,10 +100,10 @@ class _SetupAPIRouteModalState extends State { ), ElevatedButton( child: Text(connected == null - ? "Loading..." + ? AppLocalizations.of(context).loading : connected == true - ? "Save" - : "Invalid URL"), + ? AppLocalizations.of(context).save + : AppLocalizations.of(context).invalidUrl), ///TODO translate onPressed: connected == true From 3972d1e388ef6eb0bd21dc6bf7dd216255bbba07 Mon Sep 17 00:00:00 2001 From: Arthi-chaud Date: Thu, 3 Mar 2022 08:49:51 +0100 Subject: [PATCH 072/109] Mobile Client: Move Main to lib's root --- .../Assets.xcassets/BrandingImage.imageset/Contents.json | 6 +++--- mobile/lib/{src => }/main.dart | 0 mobile/lib/src/models/trigger.dart | 2 +- mobile/lib/src/views/login_page.dart | 2 +- mobile/test/widget_test.dart | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) rename mobile/lib/{src => }/main.dart (100%) diff --git a/mobile/ios/Runner/Assets.xcassets/BrandingImage.imageset/Contents.json b/mobile/ios/Runner/Assets.xcassets/BrandingImage.imageset/Contents.json index 7f6f7e7..1271227 100644 --- a/mobile/ios/Runner/Assets.xcassets/BrandingImage.imageset/Contents.json +++ b/mobile/ios/Runner/Assets.xcassets/BrandingImage.imageset/Contents.json @@ -1,17 +1,17 @@ { "images" : [ { - "filename" : "icon.jpg", + "filename" : "BrandingImage.png", "idiom" : "universal", "scale" : "1x" }, { - "filename" : "icon-1.jpg", + "filename" : "BrandingImage@2x.png", "idiom" : "universal", "scale" : "2x" }, { - "filename" : "icon-2.jpg", + "filename" : "BrandingImage@3x.png", "idiom" : "universal", "scale" : "3x" } diff --git a/mobile/lib/src/main.dart b/mobile/lib/main.dart similarity index 100% rename from mobile/lib/src/main.dart rename to mobile/lib/main.dart diff --git a/mobile/lib/src/models/trigger.dart b/mobile/lib/src/models/trigger.dart index 1e8b971..33a4603 100644 --- a/mobile/lib/src/models/trigger.dart +++ b/mobile/lib/src/models/trigger.dart @@ -2,7 +2,7 @@ import 'package:aeris/src/models/action_parameter.dart'; import 'package:flutter/material.dart'; -import 'package:aeris/src/main.dart'; +import 'package:aeris/main.dart'; import 'package:aeris/src/models/service.dart'; import 'package:aeris/src/models/action.dart' as aeris_action; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; diff --git a/mobile/lib/src/views/login_page.dart b/mobile/lib/src/views/login_page.dart index 35c59f6..2078fe0 100644 --- a/mobile/lib/src/views/login_page.dart +++ b/mobile/lib/src/views/login_page.dart @@ -1,5 +1,5 @@ import 'package:aeris/src/aeris_api.dart'; -import 'package:aeris/src/main.dart'; +import 'package:aeris/main.dart'; import 'package:aeris/src/widgets/aeris_page.dart'; import 'package:flutter_login/flutter_login.dart'; import 'package:flutter/material.dart'; diff --git a/mobile/test/widget_test.dart b/mobile/test/widget_test.dart index 5b48bd2..a1aa3f6 100644 --- a/mobile/test/widget_test.dart +++ b/mobile/test/widget_test.dart @@ -7,7 +7,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:aeris/src/main.dart'; +import 'package:aeris/main.dart'; void main() { testWidgets('Counter increments smoke test', (WidgetTester tester) async { From 08a79ba15652b0210008ae21a40ecfd8c7573801 Mon Sep 17 00:00:00 2001 From: Arthi-chaud Date: Thu, 3 Mar 2022 08:52:02 +0100 Subject: [PATCH 073/109] Mobile Client: Move Main to lib's root --- mobile/lib/src/aeris_api.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mobile/lib/src/aeris_api.dart b/mobile/lib/src/aeris_api.dart index df1ae71..33cf450 100644 --- a/mobile/lib/src/aeris_api.dart +++ b/mobile/lib/src/aeris_api.dart @@ -3,7 +3,7 @@ import 'dart:async'; import 'dart:convert'; import 'dart:io'; -import 'package:aeris/src/main.dart'; +import 'package:aeris/main.dart'; import 'package:aeris/src/models/action.dart'; import 'package:aeris/src/models/action_parameter.dart'; import 'package:aeris/src/models/action_template.dart'; From ea21a9fdec483829b50a4099c9d8cd313cdafff1 Mon Sep 17 00:00:00 2001 From: Arthi-chaud Date: Thu, 3 Mar 2022 09:00:06 +0100 Subject: [PATCH 074/109] Mobile Client: Fix case in detail page --- mobile/lib/src/views/pipeline_detail_page.dart | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/mobile/lib/src/views/pipeline_detail_page.dart b/mobile/lib/src/views/pipeline_detail_page.dart index 0936c2f..20ad9fd 100644 --- a/mobile/lib/src/views/pipeline_detail_page.dart +++ b/mobile/lib/src/views/pipeline_detail_page.dart @@ -14,6 +14,7 @@ import 'package:flutter/material.dart'; import 'package:get_it/get_it.dart'; import 'package:provider/provider.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; +import 'package:recase/recase.dart'; ///Page for a Pipeline's details class PipelineDetailPage extends StatefulWidget { @@ -135,7 +136,7 @@ class _PipelineDetailPageState extends State { style: const TextStyle(fontWeight: FontWeight.w500)), ActionCard( leading: pipeline.trigger.service.getLogo(logoSize: 50), - title: pipeline.trigger.name, + title: ReCase(pipeline.trigger.name).paramCase, trailing: ActionCardPopupMenu( deletable: false, action: pipeline.trigger, From 048c63fedbaf6dd34cb44d56b818906135a79856 Mon Sep 17 00:00:00 2001 From: Arthi-chaud Date: Thu, 3 Mar 2022 09:23:14 +0100 Subject: [PATCH 075/109] Mobile Client: Fix Merge on docker compose --- docker-compose.dev.yml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml index 4856147..61e64ab 100644 --- a/docker-compose.dev.yml +++ b/docker-compose.dev.yml @@ -44,12 +44,8 @@ services: - "api" environment: - YOUTUBE_KEY=${YOUTUBE_KEY} -<<<<<<< HEAD -======= - WORKER_API_URL=${WORKER_API_URL} - WORKER_API_KEY=${WORKER_API_KEY} ->>>>>>> 6e1880a441c6f538f6a35085b547e8bf93467405 - volumes: apk: From c0be7c8fb78dd090c2d401de47593e440fb6e577 Mon Sep 17 00:00:00 2001 From: Arthi-chaud Date: Thu, 3 Mar 2022 09:23:36 +0100 Subject: [PATCH 076/109] Mobile Client: Dockerfile: fix build command --- mobile/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mobile/Dockerfile b/mobile/Dockerfile index a12dc0e..22c42db 100644 --- a/mobile/Dockerfile +++ b/mobile/Dockerfile @@ -21,5 +21,5 @@ RUN flutter gen-l10n RUN flutter pub run flutter_launcher_icons:main # Generate native splashscreen RUN flutter pub run flutter_native_splash:create -RUN flutter build apk lib/src/main.dart +RUN flutter build apk lib/main.dart CMD cp ./build/app/outputs/flutter-apk/app-release.apk /dist/aeris_android.apk From ad9d4afbdda6ff0a40ba73dc88dda46ff1398611 Mon Sep 17 00:00:00 2001 From: Arthi-chaud Date: Thu, 3 Mar 2022 09:46:51 +0100 Subject: [PATCH 077/109] Mobile Client: Fix case ond etail page --- mobile/lib/src/views/pipeline_detail_page.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mobile/lib/src/views/pipeline_detail_page.dart b/mobile/lib/src/views/pipeline_detail_page.dart index 20ad9fd..f348962 100644 --- a/mobile/lib/src/views/pipeline_detail_page.dart +++ b/mobile/lib/src/views/pipeline_detail_page.dart @@ -136,7 +136,7 @@ class _PipelineDetailPageState extends State { style: const TextStyle(fontWeight: FontWeight.w500)), ActionCard( leading: pipeline.trigger.service.getLogo(logoSize: 50), - title: ReCase(pipeline.trigger.name).paramCase, + title: ReCase(pipeline.trigger.name).titleCase, trailing: ActionCardPopupMenu( deletable: false, action: pipeline.trigger, From 58d9c02cdcb6608d412eebd23c89ec0286ba6f5f Mon Sep 17 00:00:00 2001 From: Arthi-chaud Date: Thu, 3 Mar 2022 09:47:27 +0100 Subject: [PATCH 078/109] Mobile Client: Fix action's (un)serialization + remove debug pritn --- mobile/lib/src/aeris_api.dart | 7 ++----- mobile/lib/src/models/action_parameter.dart | 2 +- mobile/lib/src/models/pipeline.dart | 2 +- mobile/lib/src/models/reaction.dart | 4 ++-- mobile/lib/src/models/service.dart | 2 ++ mobile/lib/src/models/trigger.dart | 14 +++++++++----- mobile/lib/src/providers/pipelines_provider.dart | 2 -- 7 files changed, 17 insertions(+), 16 deletions(-) diff --git a/mobile/lib/src/aeris_api.dart b/mobile/lib/src/aeris_api.dart index 33cf450..69187c7 100644 --- a/mobile/lib/src/aeris_api.dart +++ b/mobile/lib/src/aeris_api.dart @@ -32,7 +32,7 @@ class AerisAPI { /// JWT token used to request API late String _jwt; - final String baseRoute = "http://10.29.124.174:8080"; ///TODO make it modifiable + final String baseRoute = "http://10.29.124.174:81"; ///TODO make it modifiable /// Name of the file that contains the JWT used for Aeris' API requestd static const String jwtFile = 'aeris_jwt.txt'; @@ -113,11 +113,9 @@ class AerisAPI { /// Adds new pipeline to API, returns false if post failed Future createPipeline(Pipeline newPipeline) async { - print(newPipeline.toJSON()); var res = await _requestAPI( '/workflow', AerisAPIRequestType.post, newPipeline.toJSON()); - print(jsonDecode(res.body)); ///TODO could not be fetched back - newPipeline = Pipeline.fromJSON(jsonDecode(res.body)); + newPipeline.id = Pipeline.fromJSON(jsonDecode(res.body)).id; return res.ok; } @@ -145,7 +143,6 @@ class AerisAPI { var res = await _requestAPI('/workflows', AerisAPIRequestType.get, null); if (res.ok == false) return []; final List body = jsonDecode(res.body); - print(body); return body.map((e) => Pipeline.fromJSON(Map.from(e))).toList(); } diff --git a/mobile/lib/src/models/action_parameter.dart b/mobile/lib/src/models/action_parameter.dart index 36c1b3e..5921c8b 100644 --- a/mobile/lib/src/models/action_parameter.dart +++ b/mobile/lib/src/models/action_parameter.dart @@ -16,7 +16,7 @@ class ActionParameter { MapEntry toJson() => MapEntry(name, value); - static List fromJSON(Map params) { + static List fromJSON(Map params) { List actionParameters = []; params.forEach((key, value) => actionParameters.add(ActionParameter(name: key, value: value))); diff --git a/mobile/lib/src/models/pipeline.dart b/mobile/lib/src/models/pipeline.dart index 211e2ee..2706fde 100644 --- a/mobile/lib/src/models/pipeline.dart +++ b/mobile/lib/src/models/pipeline.dart @@ -6,7 +6,7 @@ import 'package:aeris/src/models/trigger.dart'; /// Object representation of a pipeline class Pipeline { ///Unique identifier - final int id; + int id; /// Name of the pipeline, defined by the user final String name; diff --git a/mobile/lib/src/models/reaction.dart b/mobile/lib/src/models/reaction.dart index 94c5176..949a930 100644 --- a/mobile/lib/src/models/reaction.dart +++ b/mobile/lib/src/models/reaction.dart @@ -20,13 +20,13 @@ class Reaction extends aeris_action.Action { : super(service: Service.all()[0], name: '', parameters: []); static Reaction fromJSON(Object reaction) { - var reactionJSON = reaction as Map; + var reactionJSON = reaction as Map; Tuple2 service = aeris_action.Action.parseServiceAndName( reactionJSON['rType'] as String); return Reaction( service: service.item1, name: service.item2, - parameters: ActionParameter.fromJSON((reactionJSON['rParams'] as Map))); + parameters: ActionParameter.fromJSON((reactionJSON['rParams'] as Map))); } /// Serialize Reaction to JSON diff --git a/mobile/lib/src/models/service.dart b/mobile/lib/src/models/service.dart index 69367f2..eb0383f 100644 --- a/mobile/lib/src/models/service.dart +++ b/mobile/lib/src/models/service.dart @@ -71,6 +71,8 @@ class Service { /// Construct a service based on a lowercase string, the name of the service static Service factory(String name) { + if (name.toLowerCase() == "git") return const Service.github(); + if (name.toLowerCase() == "ani") return const Service.anilist(); for (Service service in Service.all()) { if (service.name.toLowerCase() == name.toLowerCase()) return service; } diff --git a/mobile/lib/src/models/trigger.dart b/mobile/lib/src/models/trigger.dart index 33a4603..6d990e8 100644 --- a/mobile/lib/src/models/trigger.dart +++ b/mobile/lib/src/models/trigger.dart @@ -22,16 +22,19 @@ class Trigger extends aeris_action.Action { /// Unserialize static Trigger fromJSON(Object action) { - var triggerJSON = action as Map; + var triggerJSON = action as Map; Tuple2 service = aeris_action.Action.parseServiceAndName(triggerJSON['pType'] as String); - DateTime last = DateTime.parse(action['lastTrigger'] as String); + var lastTriggerField = action['lastTrigger']; + DateTime? last = lastTriggerField == null + ? null + : DateTime.parse(lastTriggerField as String); return Trigger( service: service.item1, name: service.item2, - last: last.year == 0 ? null : last, - parameters: ActionParameter.fromJSON((triggerJSON['pParams'] as Map)) + last: last, + parameters: ActionParameter.fromJSON((triggerJSON['pParams'] as Map)) ); } @@ -56,6 +59,7 @@ class Trigger extends aeris_action.Action { return service.name == other.service.name && name == other.name && last == other.last && - parameters.map((e) => e.name).toString() == other.parameters.map((e) => e.name).toString(); + parameters.map((e) => e.name).toString() == + other.parameters.map((e) => e.name).toString(); } } diff --git a/mobile/lib/src/providers/pipelines_provider.dart b/mobile/lib/src/providers/pipelines_provider.dart index 3117f27..aefdbfd 100644 --- a/mobile/lib/src/providers/pipelines_provider.dart +++ b/mobile/lib/src/providers/pipelines_provider.dart @@ -34,8 +34,6 @@ class PipelineProvider extends ChangeNotifier { /// Adds a pipeline in the Provider addPipeline(Pipeline newPipeline) async { await GetIt.I().createPipeline(newPipeline); - - ///TODO Check newPipeline got the ID _pipelineCollection.pipelines.add(newPipeline); sortPipelines(); notifyListeners(); From a2a37d330f1c39c28f0eb2a2761816c66cd5c477 Mon Sep 17 00:00:00 2001 From: Arthi-chaud Date: Thu, 3 Mar 2022 10:01:16 +0100 Subject: [PATCH 079/109] Mobile Client: Home Page: fix non-scroll when pipeline list is short --- mobile/lib/src/views/home_page.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mobile/lib/src/views/home_page.dart b/mobile/lib/src/views/home_page.dart index e1600da..79b5f10 100644 --- a/mobile/lib/src/views/home_page.dart +++ b/mobile/lib/src/views/home_page.dart @@ -83,7 +83,7 @@ class _HomePageState extends State { .fetchPipelines() .then((_) => setState(() {})), // refresh callback child: ListView.builder( - physics: const BouncingScrollPhysics(), + physics: const AlwaysScrollableScrollPhysics(), padding: const EdgeInsets.only( bottom: 20, top: 20, left: 10, right: 10), controller: listController, From 7e4dbd662edec596ba14ae6d2083d90e6ca1355f Mon Sep 17 00:00:00 2001 From: Arthi-chaud Date: Thu, 3 Mar 2022 10:07:53 +0100 Subject: [PATCH 080/109] Mobile Client: Fix unserialization of p/rType --- mobile/lib/src/models/action.dart | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/mobile/lib/src/models/action.dart b/mobile/lib/src/models/action.dart index fca41b2..3789633 100644 --- a/mobile/lib/src/models/action.dart +++ b/mobile/lib/src/models/action.dart @@ -27,8 +27,9 @@ abstract class Action { static Tuple2 parseServiceAndName(String rType) { var snake = ReCase(rType).snakeCase.split('_'); - return Tuple2(Service.factory(snake.first), - ReCase(snake.getRange(1, snake.length - 1).join('_')).titleCase); + var service = snake.removeAt(0); + return Tuple2(Service.factory(service), + ReCase(snake.join('_')).titleCase); } static String getType(Service service, String aName) { From 62d25f832b5283d620c44d2a34e8ddff3afd429e Mon Sep 17 00:00:00 2001 From: Arthi-chaud Date: Thu, 3 Mar 2022 10:09:23 +0100 Subject: [PATCH 081/109] Mobile Client: Fix case on detail page --- mobile/lib/src/views/pipeline_detail_page.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mobile/lib/src/views/pipeline_detail_page.dart b/mobile/lib/src/views/pipeline_detail_page.dart index f348962..333c153 100644 --- a/mobile/lib/src/views/pipeline_detail_page.dart +++ b/mobile/lib/src/views/pipeline_detail_page.dart @@ -153,7 +153,7 @@ class _PipelineDetailPageState extends State { itemBuilder: (reaction) => ActionCard( key: ValueKey(pipeline.reactions.indexOf(reaction)), leading: reaction.service.getLogo(logoSize: 50), - title: reaction.name, + title: ReCase(reaction.name).titleCase, trailing: ActionCardPopupMenu( deletable: pipeline.reactions.length > 1, action: reaction, From b85986d49988f9f250626bdecdce3f5cfe70c7c5 Mon Sep 17 00:00:00 2001 From: Arthi-chaud Date: Thu, 3 Mar 2022 10:36:46 +0100 Subject: [PATCH 082/109] Mobile Client: Remove 'TODO' --- mobile/lib/src/widgets/home_page_sort_menu.dart | 1 - 1 file changed, 1 deletion(-) diff --git a/mobile/lib/src/widgets/home_page_sort_menu.dart b/mobile/lib/src/widgets/home_page_sort_menu.dart index 24af014..f33b4ef 100644 --- a/mobile/lib/src/widgets/home_page_sort_menu.dart +++ b/mobile/lib/src/widgets/home_page_sort_menu.dart @@ -50,7 +50,6 @@ class HomePageSortMenu extends StatelessWidget { value: !split), ], onSelected: (sortingMethod) { - /// TODO: not clean if (sortingMethod is bool) { collectionProvider.splitDisabled = sortingMethod; } else { From d5ef9ed9c8c283f9c1504b68dc18eddabab0415f Mon Sep 17 00:00:00 2001 From: Arthi-chaud Date: Thu, 3 Mar 2022 11:40:43 +0100 Subject: [PATCH 083/109] Mobile Provider: Making refresher async --- mobile/lib/src/providers/services_provider.dart | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/mobile/lib/src/providers/services_provider.dart b/mobile/lib/src/providers/services_provider.dart index cccd9ec..61cc17c 100644 --- a/mobile/lib/src/providers/services_provider.dart +++ b/mobile/lib/src/providers/services_provider.dart @@ -26,11 +26,9 @@ class ServiceProvider extends ChangeNotifier { } /// Refresh services from API - refreshServices() { - GetIt.I().getConnectedService().then((value) { - _connectedServices = value; - notifyListeners(); - }); + refreshServices() async { + _connectedServices = await GetIt.I().getConnectedService(); + notifyListeners(); } /// Removes a service from the Provider, and calls API From 350607008220c933a2403cd00e78cb70a4a0697e Mon Sep 17 00:00:00 2001 From: Arthi-chaud Date: Thu, 3 Mar 2022 11:41:18 +0100 Subject: [PATCH 084/109] Autho page: notify listeners --- mobile/lib/src/views/authorization_page.dart | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/mobile/lib/src/views/authorization_page.dart b/mobile/lib/src/views/authorization_page.dart index bcb411a..daba6ba 100644 --- a/mobile/lib/src/views/authorization_page.dart +++ b/mobile/lib/src/views/authorization_page.dart @@ -14,9 +14,10 @@ class AuthorizationPage extends StatelessWidget { final serviceName = Uri.parse(route).pathSegments.last; final service = Service.factory(serviceName); - context.read().addService(service, code).then( - (_) => Navigator.pop(context) - ); + Provider.of(context, listen: false).addService(service, code).then((_) { + Provider.of(context, listen: false).notifyListeners(); + Navigator.pop(context); + }); return Container( alignment: Alignment.center, child: LoadingIndicator( From f4934683a1f844e5ad01b5f1c050fa342be39b86 Mon Sep 17 00:00:00 2001 From: Arthi-chaud Date: Thu, 3 Mar 2022 13:32:58 +0100 Subject: [PATCH 085/109] Mobile Client: CRUD on pipeline works --- mobile/ios/Runner.xcodeproj/project.pbxproj | 3 +++ mobile/ios/Runner/RunnerDebug.entitlements | 8 ++++++++ mobile/lib/src/aeris_api.dart | 15 +++++++++------ mobile/lib/src/views/service_page.dart | 4 ++-- 4 files changed, 22 insertions(+), 8 deletions(-) create mode 100644 mobile/ios/Runner/RunnerDebug.entitlements diff --git a/mobile/ios/Runner.xcodeproj/project.pbxproj b/mobile/ios/Runner.xcodeproj/project.pbxproj index 17b7558..3b5299a 100644 --- a/mobile/ios/Runner.xcodeproj/project.pbxproj +++ b/mobile/ios/Runner.xcodeproj/project.pbxproj @@ -18,6 +18,7 @@ /* Begin PBXFileReference section */ 0B9CFEF16F77B4F2467EF56A /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; + 0C6C3D7227D0D7C100B12C20 /* RunnerDebug.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = RunnerDebug.entitlements; sourceTree = ""; }; 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; @@ -99,6 +100,7 @@ 97C146F01CF9000F007C117D /* Runner */ = { isa = PBXGroup; children = ( + 0C6C3D7227D0D7C100B12C20 /* RunnerDebug.entitlements */, 97C146FA1CF9000F007C117D /* Main.storyboard */, 97C146FD1CF9000F007C117D /* Assets.xcassets */, 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, @@ -476,6 +478,7 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/RunnerDebug.entitlements; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; DEVELOPMENT_TEAM = HJ45QP4WWR; ENABLE_BITCODE = NO; diff --git a/mobile/ios/Runner/RunnerDebug.entitlements b/mobile/ios/Runner/RunnerDebug.entitlements new file mode 100644 index 0000000..3fb05ea --- /dev/null +++ b/mobile/ios/Runner/RunnerDebug.entitlements @@ -0,0 +1,8 @@ + + + + + com.apple.developer.default-data-protection + NSFileProtectionComplete + + diff --git a/mobile/lib/src/aeris_api.dart b/mobile/lib/src/aeris_api.dart index 69187c7..c1a70cf 100644 --- a/mobile/lib/src/aeris_api.dart +++ b/mobile/lib/src/aeris_api.dart @@ -127,7 +127,7 @@ class AerisAPI { } String getServiceAuthURL(Service service) { - final serviceName = service.name.toLowerCase(); + final serviceName = service == const Service.youtube() ? "google" : service.name.toLowerCase(); return "$baseRoute/auth/$serviceName/url?redirect_uri=aeris://aeris.com/authorization/$serviceName"; } @@ -135,6 +135,8 @@ class AerisAPI { Future editPipeline(Pipeline updatedPipeline) async { var res = await _requestAPI('/workflow/${updatedPipeline.id}', AerisAPIRequestType.put, updatedPipeline.toJSON()); + print(res.body); + print(res.statusCode); return res.ok; } @@ -142,6 +144,7 @@ class AerisAPI { Future> getPipelines() async { var res = await _requestAPI('/workflows', AerisAPIRequestType.get, null); if (res.ok == false) return []; + print(res.body); final List body = jsonDecode(res.body); return body.map((e) => Pipeline.fromJSON(Map.from(e))).toList(); @@ -152,8 +155,8 @@ class AerisAPI { var res = await _requestAPI('/auth/services', AerisAPIRequestType.get, null); if (!res.ok) return []; - return (jsonDecode(res.body) as List) - .map((e) => Service.factory(e)).toList(); + return (jsonDecode(res.body) as List) + .map((e) => Service.factory(e.toString())).toList(); } /// Disconnects the user from the service @@ -166,7 +169,7 @@ class AerisAPI { /// Connects the user from the service Future connectService(Service service, String code) async { var res = await _requestAPI( - '/auth/${service.name.toLowerCase()}?code=$code', + '/auth/${service.name.toLowerCase()}?code=$code&redirect_uri=aeris://aeris.com/authorization/${service.name.toLowerCase()}', AerisAPIRequestType.get, null); return res.ok; @@ -199,13 +202,13 @@ class AerisAPI { switch (requestType) { case AerisAPIRequestType.delete: return await http.delete(_encoreUri(route), - body: body, headers: header); + body: jsonEncode(body), headers: header); case AerisAPIRequestType.get: return await http.get(_encoreUri(route), headers: header); case AerisAPIRequestType.post: return await http.post(_encoreUri(route), body: jsonEncode(body), headers: header); case AerisAPIRequestType.put: - return await http.put(_encoreUri(route), body: body, headers: header); + return await http.put(_encoreUri(route), body: jsonEncode(body), headers: header); } } } diff --git a/mobile/lib/src/views/service_page.dart b/mobile/lib/src/views/service_page.dart index d35cff8..0489442 100644 --- a/mobile/lib/src/views/service_page.dart +++ b/mobile/lib/src/views/service_page.dart @@ -70,8 +70,8 @@ class ServicePage extends StatelessWidget { serviceProvider.availableServices, AppLocalizations.of(context).available, const Icon(Icons.connect_without_contact, color: Colors.green), - (Service service) => { - launch(Uri.parse(service.authUrl).toString(), forceSafariVC: false) + (Service service) { + launch(Uri.parse(service.authUrl).toString(), forceSafariVC: false); }, context), ], From 946a89125d1e53153be68ab5c526276e4ded5cad Mon Sep 17 00:00:00 2001 From: Arthi-chaud Date: Thu, 3 Mar 2022 13:40:48 +0100 Subject: [PATCH 086/109] Mobile Client: change deep link route --- mobile/android/app/src/main/AndroidManifest.xml | 2 +- mobile/ios/Runner/Info-Debug.plist | 4 ++-- mobile/lib/src/aeris_api.dart | 6 ++++-- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/mobile/android/app/src/main/AndroidManifest.xml b/mobile/android/app/src/main/AndroidManifest.xml index f2755e4..fce3273 100644 --- a/mobile/android/app/src/main/AndroidManifest.xml +++ b/mobile/android/app/src/main/AndroidManifest.xml @@ -31,7 +31,7 @@ - + - + + + + + + + + + + + + + + + + + + + + + + + diff --git a/mobile/android/app/src/main/AndroidManifest.xml b/mobile/android/app/src/main/AndroidManifest.xml index fce3273..4eac845 100644 --- a/mobile/android/app/src/main/AndroidManifest.xml +++ b/mobile/android/app/src/main/AndroidManifest.xml @@ -31,7 +31,9 @@ - + + +