diff --git a/lib/src/app.dart b/lib/src/app.dart index fbedba3885efb6cbc2ee9f5fc4b948572a12ff32..8253d14fc51e29f06d77c177df9e264c0bc951a9 100644 --- a/lib/src/app.dart +++ b/lib/src/app.dart @@ -1,4 +1,6 @@ // app.dart +import '../config.dart'; +import 'error-alert.dart'; import 'screens/splashscreen.dart'; import 'package:flutter/material.dart'; import 'screens/home.dart'; @@ -6,6 +8,11 @@ import 'package:flutter/services.dart'; import 'dart:async'; import 'dart:developer'; import 'package:shared_preferences/shared_preferences.dart'; +import 'package:flutter_web_browser/flutter_web_browser.dart'; +import 'dart:convert'; +import 'package:url_launcher/url_launcher.dart'; +import 'package:flutter_app_auth_wrapper/flutter_app_auth_wrapper.dart'; +import 'dart:io'; import 'package:uni_links/uni_links.dart'; @@ -52,8 +59,6 @@ class App extends StatelessWidget { navigateAfterFuture: initApplication, ) ); - - } } @@ -76,9 +81,59 @@ class _MainAppState extends State<MainApp> { // Url of the app which invoked OAuth String _invokerURL; + String _host = Config.appFlavor == Flavor.DEVELOPMENT ? Config.HOSTS[0] : Config.DEFAULT_APP_HOST; + + Screen _currentScreen = Screen.App; + @override initState() { super.initState(); + + // Set up app host + if (widget.initialHost != null) { + setState(() { + _host = widget.initialHost; + }); + } + + // Subscribe to authorization events + FlutterAppAuthWrapper.eventStream().listen((data) async { + var token = json.decode(data.toString())["access_token"]; + setScreen(Screen.App); + + log("Open $_invokerURL"); + try { + await launch("$_invokerURL?token=$token&host=$_host"); + } catch (e) { + log("Error launching url $_invokerURL"); + } + + }, onError: (error) { + log("Err $error"); + + String errorMessage = error.message; + String errorDetails = error.details; + + if ( + // Handle cancellation for Android + errorDetails == '{"type":0,"code":1,"errorDescription":"User cancelled flow"}' || + // Handle cancellation for iOS + errorMessage.toLowerCase().contains("the operation couldn") + ) { + if (Platform.isAndroid) { + // Show only for android, because iOS will show the same alert as + // was for Auth request + openVereign(); + } else { + setScreen(Screen.App); + } + } else { + showErrorAlert(context, Platform.isAndroid ? errorDetails : errorMessage, openVereign); + setScreen(Screen.App); + } + }); + + initUniLinks(); } @@ -89,37 +144,87 @@ class _MainAppState extends State<MainApp> { } Future<Null> initUniLinks() async { - updateAppMode(widget.initialUri); + handleLinkChange(widget.initialUri); _sub = getUriLinksStream().listen((Uri uri) { - updateAppMode(uri); + handleLinkChange(uri); }, onError: (err) { log('got err: $err'); }); } - updateAppMode(Uri uri) { + handleLinkChange(Uri uri) { if (uri?.path == "/authorize") { setState(() { _appMode = AppMode.Authorization; _invokerURL = uri.queryParameters["invokerUrl"]; - }); + + startOAuth(); } else { setState(() { _appMode = AppMode.Default; }); + + openVereign(); } } + setScreen(Screen screen) { + setState(() { + _currentScreen = screen; + }); + } + + openVereign() { + setScreen(Screen.Dashboard); + + FlutterWebBrowser.openWebPage(url: _host, androidToolbarColor: Color(0xFFd51d32)); + } + + startOAuth() { + if (_currentScreen == Screen.OAuth) { + return; + } + + setScreen(Screen.OAuth); + + var params = Config.getOAuthParams(host: _host); + + FlutterAppAuthWrapper.startAuth( + AuthConfig( + clientId: params["clientId"], + clientSecret: params["clientSecret"], + redirectUrl: params["redirectUrl"], + state: "login", + prompt: "consent", + endpoint: AuthEndpoint( + auth: params["authEndpoint"], token: params["tokenEndpoint"]), + scopes: [ + "user_account_status", + "user_territory", + "user_profile" + ], + ), + ); + } + + setHost(String host) { + setState(() { + _host = host; + }); + } + @override Widget build(BuildContext context) { return new Scaffold( appBar: new AppBar(title: Text("Vereign")), body: Home( mode: _appMode, - invokerURL: _invokerURL, - host: widget.initialHost + host: _host, + setHost: setHost, + openDashboardClick: openVereign, + authorizeClick: startOAuth, ) ); } diff --git a/lib/src/error-alert.dart b/lib/src/error-alert.dart new file mode 100644 index 0000000000000000000000000000000000000000..a3fd300440bb05e7f45e2219fc518cf05668bb17 --- /dev/null +++ b/lib/src/error-alert.dart @@ -0,0 +1,22 @@ +import 'package:flutter/material.dart'; + +Future<void> showErrorAlert(context, errorString, callback) { + return showDialog<void>( + context: context, + builder: (BuildContext context) { + return AlertDialog( + title: Text('Authorization error'), + content: Text(errorString), + actions: <Widget>[ + FlatButton( + child: Text('Open Dashboard'), + onPressed: () async { + Navigator.of(context).pop(); + callback(); + }, + ), + ], + ); + }, + ); +} \ No newline at end of file diff --git a/lib/src/screens/home.dart b/lib/src/screens/home.dart index 4f3de27332b692f034901bd73ca48b5230a4e94f..6c4f085ac052cbe0c0f2aed44e953332e43af0db 100644 --- a/lib/src/screens/home.dart +++ b/lib/src/screens/home.dart @@ -1,9 +1,4 @@ import 'package:flutter/material.dart'; -import 'package:flutter_web_browser/flutter_web_browser.dart'; -import 'package:flutter_app_auth_wrapper/flutter_app_auth_wrapper.dart'; -import 'package:url_launcher/url_launcher.dart'; -import 'dart:developer'; -import 'dart:convert'; import 'dart:async'; import 'package:shared_preferences/shared_preferences.dart'; @@ -13,46 +8,29 @@ import '../app.dart'; class Home extends StatefulWidget { Home({ @required this.mode, - @required this.invokerURL, @required this.host, + @required this.setHost, + @required this.authorizeClick, + @required this.openDashboardClick, }); final AppMode mode; - final String invokerURL; final String host; + final void Function(String) setHost; + final void Function() openDashboardClick; + final void Function() authorizeClick; @override _HomeState createState() => _HomeState(); } class _HomeState extends State<Home> { - String _host = Config.appFlavor == Flavor.DEVELOPMENT ? Config.HOSTS[0] : Config.DEFAULT_APP_HOST; bool _hidden = true; - Screen _currentScreen = Screen.App; - @override initState() { super.initState(); - // Set up initial host - if (widget.host != null) { - setState(() { - _host = widget.host; - }); - } - - setScreenByMode(widget.mode); - - FlutterAppAuthWrapper.eventStream().listen((data) { - var token = json.decode(data.toString())["access_token"]; - _showAlert(token); - setScreen(Screen.App); - }, onError: (error) { - log("Err $error"); - setScreen(Screen.App); - }); - Timer( Duration(seconds: 3), () { @@ -63,105 +41,6 @@ class _HomeState extends State<Home> { ); } - setScreen(Screen screen) { - if ( - _currentScreen == screen && - screen != Screen.Dashboard // ATM we can not determine whether dashboard was closed or not, so we avoid this case - ) { - return; - } - - setState(() { - _currentScreen = screen; - }); - - if (_currentScreen == Screen.Dashboard) { - openVereign(); - } else if (_currentScreen == Screen.OAuth) { - startOAuth(); - } - } - - Future<void> _showAlert(token) { - return showDialog<void>( - context: context, - builder: (BuildContext context) { - return AlertDialog( - title: Text('Authorization success'), - actions: <Widget>[ - FlatButton( - child: Text('Go back'), - onPressed: () async { - Navigator.of(context).pop(); - - log("Open ${widget.invokerURL}"); - try { - await launch("${widget.invokerURL}?token=$token&host=$_host"); - } catch (e) { - log("Error launching url ${widget.invokerURL}"); - } - }, - ), - ], - ); - }, - ); - } - - @override - void didUpdateWidget(Home oldWidget) { - // this method IS called when parent widget passes new "props" - // unlike React, this method IS called _before_ the build - // unlike React, this method ISN'T called after setState() - - if (widget.host != oldWidget.host && widget.host != null) { - setState(() { - _host = widget.host; - }); - } - - if (widget.mode != oldWidget.mode) { - setScreenByMode(widget.mode); - } - - super.didUpdateWidget(oldWidget); - } - - setScreenByMode(AppMode mode) { - if (mode == AppMode.Authorization) { - setScreen(Screen.OAuth); - } else if (mode == AppMode.Default) { - setScreen(Screen.Dashboard); - } - } - - openVereign() { - FlutterWebBrowser.openWebPage(url: _host, androidToolbarColor: Color(0xFFd51d32)); - } - - startOAuth() { - var params = Config.getOAuthParams(host: _host); - - FlutterAppAuthWrapper.startAuth( - AuthConfig( - clientId: params["clientId"], - clientSecret: params["clientSecret"], - redirectUrl: params["redirectUrl"], - state: "login", - prompt: "consent", - endpoint: AuthEndpoint( - auth: params["authEndpoint"], token: params["tokenEndpoint"]), - scopes: [ - "user_account_status", - "user_territory", - "user_profile" - ], - ), - ); - } - - - @override Widget build(BuildContext context) { if (_hidden) { @@ -169,23 +48,20 @@ class _HomeState extends State<Home> { } var children = <Widget>[ - _urlButton(context, "Open Dashboard", () => setScreen(Screen.Dashboard)), + _urlButton(context, "Open Dashboard", widget.openDashboardClick), ]; if (widget.mode == AppMode.Authorization) { - children.add(_urlButton(context, "Authorize with Vereign", () => setScreen(Screen.OAuth))); + children.add(_urlButton(context, "Authorize with Vereign", widget.authorizeClick)); } if (Config.appFlavor == Flavor.DEVELOPMENT) { children.add( wrapInContainer( DropdownButton<String>( - value: _host, + value: widget.host, onChanged: (String newValue) async { - setState(() { - _host = newValue; - }); - + widget.setHost(newValue); final prefs = await SharedPreferences.getInstance(); prefs.setString("host", newValue); },