From ec725ff037fad6007533dd9bb6e8e2b6e50ab20c Mon Sep 17 00:00:00 2001 From: Arthi-chaud Date: Sat, 26 Feb 2022 10:15:12 +0100 Subject: [PATCH] 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), + ], ), - ); + ), + ); } }