diff --git a/lib/client.dart b/lib/client.dart index d202bef9..db8b23c4 100644 --- a/lib/client.dart +++ b/lib/client.dart @@ -7,9 +7,7 @@ import 'dart:convert'; import 'dart:io'; class Client { - static final protocol = 'https'; - static final domain = 'api.anypayx.com'; - static final host = "$protocol://$domain"; + static Uri apiUri = Uri(scheme: 'https', host: 'api.anypayx.com'); static String humanize(String str) { return StringUtils.capitalize(str); @@ -132,7 +130,7 @@ class Client { var response = await makeRequest('get', unauthorized: (() => Authentication.logout()), - uri: Uri.https(domain, '/invoices', { + uri: Uri.https(apiUri.host, '/invoices', { 'limit': perPage.toString(), 'offset': offset.toString(), 'complete': 'true', @@ -192,7 +190,7 @@ class Client { static Future> makeRequest(method, {path, uri, headers, body, requireAuth, basicAuth, unauthorized, genericErrorCodes}) async { try { - http.Request request = http.Request(method, uri ?? Uri.parse('$host$path')); + http.Request request = http.Request(method, uri ?? Uri.parse('${apiUri.toString()}$path')); if (requireAuth ?? false) request.headers['authorization'] = buildAuthHeader(); if (basicAuth != null) request.headers['authorization'] = basicAuth; if (genericErrorCodes == null) genericErrorCodes = [500]; @@ -238,4 +236,8 @@ class Client { }; } } + + static updateUri({required Uri uri}) { + apiUri = uri; + } } diff --git a/lib/main.dart b/lib/main.dart index e8920bec..e4ce9f8b 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -4,9 +4,12 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:app/app_controller.dart'; import 'package:app/router.dart'; +import 'client.dart'; +import 'native_storage.dart'; void main() { WidgetsFlutterBinding.ensureInitialized(); + setDefaultUrl(); Authentication.checkForAuth().then((isAuthenticated) { AnyFluroRouter.setupRouter(); runApp(Anypay(isAuthenticated)); @@ -39,7 +42,6 @@ class Anypay extends StatelessWidget { ), fontFamily: 'Ubuntu', ); - var darkTheme = ThemeData( primaryColorDark: Color(0xffCCCCCC), primaryColorLight: Color(0xFFFFFFFF), @@ -47,6 +49,9 @@ class Anypay extends StatelessWidget { bodyMedium: TextStyle(color: Color(0xFFFFFFFF)), bodyLarge: TextStyle(color: Color(0xFFFFFFFF)), ), + dialogTheme: DialogTheme( + titleTextStyle: TextStyle(color: Color(0xFFFFFFFF),fontSize: 24), + ), inputDecorationTheme: const InputDecorationTheme( labelStyle: TextStyle(color: Color(0xFFFFFFFF)), enabledBorder: @@ -87,3 +92,10 @@ class Anypay extends StatelessWidget { }); } } + +void setDefaultUrl() async { + final storedUrl = await Storage.read("backend_url"); + if (storedUrl != null) { + Client.updateUri(uri: Uri.parse(storedUrl)); + } +} diff --git a/lib/models/invoice.dart b/lib/models/invoice.dart index 1834fd3c..319162ec 100644 --- a/lib/models/invoice.dart +++ b/lib/models/invoice.dart @@ -131,7 +131,7 @@ class Invoice { String urlStyleUri([useCurrency]) { useCurrency = useCurrency ?? currency; - String host = Client.host; + String host = Client.apiUri.toString(); String protocol = { 'BTC': 'bitcoin', 'BCH': 'bitcoincash', @@ -143,7 +143,7 @@ class Invoice { } String uriFor(currency, {format}) { - if (format == 'pay') return "pay:?r=${Client.host}/r/$uid"; + if (format == 'pay') return "pay:?r=${Client.apiUri.toString()}/r/$uid"; if (format == 'url') return urlStyleUri(currency); return paymentOptionFor(currency)['uri']; diff --git a/lib/router.dart b/lib/router.dart index 9fd10076..f6099805 100644 --- a/lib/router.dart +++ b/lib/router.dart @@ -1,3 +1,4 @@ +import 'package:app/routes/edit_backend_url.dart'; import 'package:flutter/material.dart'; import 'package:fluro/fluro.dart'; @@ -84,6 +85,11 @@ class AnyFluroRouter { handler: newHandler(() => SetCurrency(), []), transitionType: TransitionType.inFromBottom, ); + router.define( + 'settings/backend_url', + handler: newHandler(() => EditBackEndUrl(), []), + transitionType: TransitionType.inFromBottom, + ); router.define( 'settings/addresses', handler: newHandler(() => Addresses(), []), diff --git a/lib/routes/edit_backend_url.dart b/lib/routes/edit_backend_url.dart new file mode 100644 index 00000000..828176f0 --- /dev/null +++ b/lib/routes/edit_backend_url.dart @@ -0,0 +1,145 @@ +import 'package:app/authentication.dart'; +import 'package:flutter/material.dart'; +import 'package:app/back_button.dart'; +import 'package:app/app_controller.dart'; +import 'package:app/currencies.dart'; +import '../client.dart'; +import '../native_storage.dart'; + +class EditBackEndUrl extends StatelessWidget { + @override + Widget build(BuildContext context) { + return EditBackEndUrlPage(title: "Edit Backend Url"); + } +} + +class EditBackEndUrlPage extends StatefulWidget { + EditBackEndUrlPage({Key? key, required this.title}) : super(key: key); + + final String title; + + @override + _EditBackEndUrlState createState() => _EditBackEndUrlState(); +} + +class _EditBackEndUrlState extends State { + var urlController = TextEditingController(); + + GlobalKey _formKey = GlobalKey(); + + + @override + void initState() { + super.initState(); + setBackendUrl(); + } + + void setBackendUrl() { + urlController.text = Client.apiUri.toString(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + body: Center( + child: SingleChildScrollView( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Container( + width: 300, + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + _EditUrlLink(), + CircleBackButton( + margin: EdgeInsets.only(top: 20.0), + backPath: 'navigation', + ), + ], + )), + ], + ), + ), + ), + ); + } + + Widget _EditUrlLink() { + return Form( + key: _formKey, + child: Column( + children: [ + TextFormField( + controller: urlController, + decoration: InputDecoration( + labelText: 'Backend Url', + hintText: "http:// or https://"), + validator: (value) { + if (value != null && Uri.parse(value).isAbsolute) { + return null; + } else { + return "Please provide valid url"; + } + }), + Container( + margin: EdgeInsets.only(top: 40.0), + child: GestureDetector( + child: Text('SAVE', style: TextStyle( + fontWeight: FontWeight.bold, + color: AppController.blue, + fontSize: 18, + )), + onTap: () async { + if (_formKey.currentState!.validate()) { + showAlertDialog( + context: context, + title: "Confirmation", + desc: "Are you sure you want to change the backend API url?", + onOkPressed: () async { + await Storage.write( + "backend_url", urlController.text); + Client.updateUri( + uri: Uri.parse(urlController.text)); + Authentication.logout(); + }); + } + }, + ), + ), + ], + ), + ); + } + showAlertDialog( + {required BuildContext context, + required String title, + required String desc, + required onOkPressed}) { + Widget okButton = TextButton( + child: Text("OK"), + onPressed: onOkPressed, + ); + Widget cancelButton = TextButton( + child: Text("Cancel"), + onPressed: () { + Navigator.pop(context); + }, + ); + AlertDialog alert = AlertDialog( + title: Text(title), + content: Text(desc), + actions: [ + cancelButton, + okButton, + ], + ); + showDialog( + context: context, + builder: (BuildContext context) { + return alert; + }, + ); + } +} diff --git a/lib/routes/login.dart b/lib/routes/login.dart index 41e4851c..471e5668 100644 --- a/lib/routes/login.dart +++ b/lib/routes/login.dart @@ -1,6 +1,7 @@ import 'dart:convert'; import 'package:email_validator/email_validator.dart'; +import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:flutter_spinkit/flutter_spinkit.dart'; import 'package:app/app_controller.dart'; import 'package:flutter/material.dart'; @@ -68,6 +69,13 @@ class _LoginPageState extends State { }); Client.authenticate(email.text, password.text).then((response) { _submitting = false; + if (response['body'] == null || response['body'].isEmpty) { + setState(() { + _errorMessage = + "An unknown error occured, try changing the backend url."; + }); + return; + } if (response['success']) { AppController.closeUntilPath('/new-invoice'); } @@ -141,7 +149,7 @@ class _LoginPageState extends State { child: Column( children: [ Container( - margin: EdgeInsets.only(bottom: _submitting ? 20.0 : 40.0), + margin: EdgeInsets.only(bottom: 20.0), child: _submitting ? SpinKitCircle(color: AppController.blue) : GestureDetector( @@ -183,9 +191,26 @@ class _LoginPageState extends State { ] ), ), + Container( + margin: EdgeInsets.only(top: 20.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + GestureDetector( + child: Text('Settings', style: TextStyle( + fontWeight: FontWeight.bold, + color: AppController.blue, + fontSize: 18, + )), + onTap: () { + Navigator.pushNamed(context, 'settings'); + } + ), + ] + ), + ), ], ), ); } - } diff --git a/lib/routes/settings.dart b/lib/routes/settings.dart index cbcf4b50..abbdc30d 100644 --- a/lib/routes/settings.dart +++ b/lib/routes/settings.dart @@ -3,6 +3,8 @@ import 'package:flutter/material.dart'; import 'package:app/back_button.dart'; import 'package:app/app_controller.dart'; import 'package:app/currencies.dart'; +import 'package:flutter_secure_storage/flutter_secure_storage.dart'; +import '../client.dart'; class Settings extends StatelessWidget { @override @@ -24,14 +26,17 @@ class _SettingsPageState extends State { var _successMessage = ''; var denomination; var symbol; - + bool? isUserLoggedIn; @override void initState() { _rebuild(); super.initState(); - Authentication.getAccount().then((account) { - _rebuild(); - }); + isUserLoggedIn = Authentication.currentAccount.email != null; + if (isUserLoggedIn == true) { + Authentication.getAccount().then((account) { + _rebuild(); + }); + } } @override @@ -51,9 +56,10 @@ class _SettingsPageState extends State { crossAxisAlignment: CrossAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center, children: [ - _SelectCurrencyLink(context), - _BusinessInfoLink(context), - _AddressesLink(context), + _EditUrlLink(context), + if (isUserLoggedIn == true) _SelectCurrencyLink(context), + if (isUserLoggedIn == true) _BusinessInfoLink(context), + if (isUserLoggedIn == true) _AddressesLink(context), CircleBackButton( margin: EdgeInsets.only(top: 20.0), backPath: 'navigation', @@ -68,6 +74,29 @@ class _SettingsPageState extends State { ); } + Widget _EditUrlLink(context) { + return Container( + margin: EdgeInsets.all(10.0), + child: GestureDetector( + behavior: HitTestBehavior.translucent, + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Container( + margin: EdgeInsets.all(AppController.scale(20.0)), + child: Text("Backend URL", + style: TextStyle( + fontSize: 22, + ))), + Icon(Icons.edit), + ], + ), + onTap: () { + Navigator.pushNamed(context, 'settings/backend_url'); + }), + ); + } + void _rebuild() { setState(() { if (Authentication.currentAccount.denomination != null) {