Merge branch 'feat/implementing-proper-login-register' of beep/frontend_flutter into master
commit
549522b95d
|
@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
|
|||
|
||||
import 'src/ui/home.dart';
|
||||
import "src/ui/login/welcome.dart";
|
||||
import "src/services/login_manager.dart";
|
||||
import 'themer.dart';
|
||||
|
||||
class Routes {
|
||||
|
@ -11,13 +12,30 @@ class Routes {
|
|||
};
|
||||
|
||||
final theme = buildTheme();
|
||||
final loginManager = LoginManager();
|
||||
|
||||
Routes() {
|
||||
runApp(MaterialApp(
|
||||
title: "Beep",
|
||||
theme: theme,
|
||||
routes: routes,
|
||||
home: Welcome(),
|
||||
));
|
||||
checkIfLogin();
|
||||
}
|
||||
|
||||
checkIfLogin() async {
|
||||
final authToken = await loginManager.getToken();
|
||||
print(authToken);
|
||||
|
||||
if (authToken != "") {
|
||||
runApp(MaterialApp(
|
||||
title: "Beep",
|
||||
theme: theme,
|
||||
routes: routes,
|
||||
home: Home(),
|
||||
));
|
||||
} else {
|
||||
runApp(MaterialApp(
|
||||
title: "Beep",
|
||||
theme: theme,
|
||||
routes: routes,
|
||||
home: Welcome(),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,14 +12,34 @@ class UserApiProvider {
|
|||
CacheHttp cache = CacheHttp();
|
||||
LoginManager loginManager = LoginManager();
|
||||
|
||||
Future<User> createUser(User user) async {
|
||||
Future<User> createUser(
|
||||
String firstName, String lastName, String phoneNumber) async {
|
||||
final jwt = loginManager.getToken();
|
||||
final response = await http.post("$baseUrlCore/user",
|
||||
headers: {
|
||||
HttpHeaders.contentTypeHeader: "application/json",
|
||||
HttpHeaders.authorizationHeader: "Bearer $jwt"
|
||||
},
|
||||
body: user.toJson());
|
||||
body: jsonEncode({
|
||||
"first_name": firstName,
|
||||
"last_name": lastName,
|
||||
"phone_number": phoneNumber
|
||||
}));
|
||||
|
||||
return User.fromJson(jsonDecode(response.body));
|
||||
}
|
||||
|
||||
Future<User> registerUser(
|
||||
String firstName, String lastName, String phoneNumber, String otp, String nonce) async {
|
||||
final response = await http.post("$baseUrlLogin/register/$otp/$nonce",
|
||||
headers: {
|
||||
HttpHeaders.contentTypeHeader: "application/json",
|
||||
},
|
||||
body: jsonEncode({
|
||||
"first_name": firstName,
|
||||
"last_name": lastName,
|
||||
"phone_number": phoneNumber
|
||||
}));
|
||||
|
||||
return User.fromJson(jsonDecode(response.body));
|
||||
}
|
||||
|
@ -40,7 +60,7 @@ class UserApiProvider {
|
|||
}
|
||||
|
||||
Future<User> fetchUserById(String id) async {
|
||||
final jwt = loginManager.getToken();
|
||||
final jwt = await loginManager.getToken();
|
||||
try {
|
||||
final responseBody =
|
||||
await this.cache.fetch("$baseUrlCore/user/id/$id", headers: {
|
||||
|
@ -52,4 +72,20 @@ class UserApiProvider {
|
|||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> updateUser(String firstName, String lastName) async {
|
||||
final jwt = await loginManager.getToken();
|
||||
final user = await loginManager.getUser();
|
||||
final finalFirstName = firstName != "" ? firstName : user != null ? user.firstName : "";
|
||||
final finalLastName = lastName != "" ? lastName : user != null ? user.lastName : "";
|
||||
await http.patch("$baseUrlCore/user",
|
||||
headers: {
|
||||
HttpHeaders.contentTypeHeader: "application/json",
|
||||
HttpHeaders.authorizationHeader: "Bearer $jwt"
|
||||
},
|
||||
body: jsonEncode({
|
||||
"first_name": finalFirstName,
|
||||
"last_name": finalLastName,
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
import "dart:async";
|
||||
import "dart:convert";
|
||||
import "../models/user_model.dart";
|
||||
import "../resources/login_api_provider.dart";
|
||||
import "../resources/user_api_provider.dart";
|
||||
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
|
||||
|
@ -15,6 +18,12 @@ class LoginManager {
|
|||
return token;
|
||||
}
|
||||
|
||||
Future<User> getUser() async {
|
||||
SharedPreferences prefs = await SharedPreferences.getInstance();
|
||||
final userString = prefs.getString("user");
|
||||
return userString != null ? jsonDecode(userString) : null;
|
||||
}
|
||||
|
||||
// Throws error status code if it occurs
|
||||
Future<void> initAuthentication(String phoneNumber) async {
|
||||
try {
|
||||
|
@ -32,6 +41,19 @@ class LoginManager {
|
|||
await loginApiProvider.verifyOtp(otp, this.nonce, this.clientid);
|
||||
SharedPreferences prefs = await SharedPreferences.getInstance();
|
||||
await prefs.setString("token", jwt);
|
||||
// Parse jwt to get userid
|
||||
final parts = jwt.split('.');
|
||||
if (parts.length != 3) {
|
||||
throw Exception('invalid token');
|
||||
}
|
||||
final payload = utf8.decode(base64Url.decode(parts[1]));
|
||||
final payloadMap = json.decode(payload);
|
||||
final userId = payloadMap['userid'];
|
||||
// Get user data
|
||||
final userApiProvider = UserApiProvider();
|
||||
final user =
|
||||
await userApiProvider.fetchUserById(userId);
|
||||
await prefs.setString("user", jsonEncode(user));
|
||||
return jwt;
|
||||
} catch (e) {
|
||||
throw e;
|
||||
|
|
|
@ -1,9 +1,12 @@
|
|||
import "package:flutter/material.dart";
|
||||
|
||||
import "../../services/login_manager.dart";
|
||||
import "../../services/conversation_manager.dart";
|
||||
|
||||
import "./widgets/welcome_page.dart";
|
||||
import "./widgets/login_page.dart";
|
||||
import "./widgets/otp_page.dart";
|
||||
import "./widgets/register_page.dart";
|
||||
|
||||
class Welcome extends StatelessWidget {
|
||||
final String logo = "assets/logo.png";
|
||||
|
@ -26,14 +29,6 @@ class Welcome extends StatelessWidget {
|
|||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: <Widget>[
|
||||
Row(mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[
|
||||
Image.asset(logo,
|
||||
semanticLabel: "Beep logo", width: 30.0, height: 50.0),
|
||||
Padding(
|
||||
padding: EdgeInsets.only(left: 10.0),
|
||||
child: Text("Beep",
|
||||
style: Theme.of(context).accentTextTheme.display3)),
|
||||
]),
|
||||
Expanded(
|
||||
child: Navigator(
|
||||
initialRoute: "welcome/hello",
|
||||
|
@ -43,14 +38,16 @@ class Welcome extends StatelessWidget {
|
|||
case "welcome/hello":
|
||||
builder = (BuildContext _) => WelcomePage();
|
||||
break;
|
||||
case "welcome/register":
|
||||
builder = (BuildContext _) =>
|
||||
RegisterPage(loginManager: loginManager);
|
||||
break;
|
||||
case "welcome/login":
|
||||
builder = (BuildContext _) =>
|
||||
LoginPage(loginManager: loginManager);
|
||||
break;
|
||||
case "welcome/otp":
|
||||
builder = (BuildContext _) =>
|
||||
OtpPage(buttonCallback: (String otp) async {
|
||||
// loginManager.processOtp(otp); disabled for testing
|
||||
builder = (BuildContext _) => OtpPage(buttonCallback: () {
|
||||
Navigator.of(context).pushReplacementNamed("/home");
|
||||
});
|
||||
break;
|
||||
|
|
|
@ -0,0 +1,30 @@
|
|||
import "package:flutter/material.dart";
|
||||
|
||||
class Input extends StatelessWidget {
|
||||
final TextEditingController controller;
|
||||
final String hintText;
|
||||
|
||||
Input({@required this.controller, @required this.hintText});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ClipRRect(
|
||||
borderRadius: BorderRadius.all(Radius.circular(5.0)),
|
||||
child: Row(mainAxisSize: MainAxisSize.max, children: <Widget>[
|
||||
Expanded(
|
||||
child: TextField(
|
||||
controller: controller,
|
||||
autocorrect: false,
|
||||
cursorWidth: 2.0,
|
||||
cursorColor: Colors.white,
|
||||
style: Theme.of(context).accentTextTheme.title,
|
||||
decoration: InputDecoration(
|
||||
border: InputBorder.none,
|
||||
filled: true,
|
||||
fillColor: Color(0x10000000),
|
||||
hintText: hintText,
|
||||
hintStyle: Theme.of(context).accentTextTheme.title,
|
||||
))),
|
||||
]));
|
||||
}
|
||||
}
|
|
@ -3,7 +3,6 @@ import "package:flutter_svg/flutter_svg.dart";
|
|||
|
||||
import "../../widgets/text_button.dart";
|
||||
import "../../../services/login_manager.dart";
|
||||
import "../../../services/conversation_manager.dart";
|
||||
import "phone_input.dart";
|
||||
|
||||
class LoginPage extends StatefulWidget {
|
||||
|
@ -30,39 +29,41 @@ class _LoginPageState extends State<LoginPage> {
|
|||
return Padding(
|
||||
padding: EdgeInsets.only(left: 15.0, right: 15.0),
|
||||
child: Column(mainAxisSize: MainAxisSize.min, children: <Widget>[
|
||||
Expanded(
|
||||
child: SingleChildScrollView(
|
||||
child:
|
||||
Column(mainAxisSize: MainAxisSize.min, children: <Widget>[
|
||||
Padding(
|
||||
padding: EdgeInsets.only(top: 10.0, bottom: 20.0),
|
||||
child: SvgPicture.asset(phoneSvg,
|
||||
height: MediaQuery.of(context).size.height / 5)),
|
||||
Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
Text("First things first.",
|
||||
textAlign: TextAlign.left,
|
||||
style: Theme.of(context).accentTextTheme.display3),
|
||||
Text(
|
||||
"Enter your phone number, to connect to your Beep account.",
|
||||
style: Theme.of(context)
|
||||
.accentTextTheme
|
||||
.title
|
||||
.copyWith(fontWeight: FontWeight.w400)),
|
||||
Padding(
|
||||
padding: EdgeInsets.only(top: 20.0),
|
||||
child: PhoneInput(controller: controller)),
|
||||
])
|
||||
]))),
|
||||
Padding(
|
||||
padding: EdgeInsets.only(top: 10.0, bottom: 20.0),
|
||||
child: SvgPicture.asset(phoneSvg,
|
||||
height: MediaQuery.of(context).size.height / 5)),
|
||||
Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
Text("First things first.",
|
||||
textAlign: TextAlign.left,
|
||||
style: Theme.of(context).accentTextTheme.display3),
|
||||
Padding(
|
||||
padding: EdgeInsets.only(top: 5.0),
|
||||
child: Text(
|
||||
"Enter your phone number, to connect to your existing Beep account.",
|
||||
style: Theme.of(context).accentTextTheme.title)),
|
||||
Padding(
|
||||
padding: EdgeInsets.only(top: 20.0),
|
||||
child: PhoneInput(controller: controller)),
|
||||
]),
|
||||
Spacer(),
|
||||
TextButton(
|
||||
text: "Continue",
|
||||
onClickCallback: () async {
|
||||
final authToken =
|
||||
await widget.loginManager.loginTest(controller.text);
|
||||
|
||||
// Waiting for initialization
|
||||
await ConversationManager.init(authToken);
|
||||
print(authToken);
|
||||
|
||||
Navigator.pushNamed(context, 'welcome/otp');
|
||||
}),
|
||||
padding: EdgeInsets.only(top: 10.0),
|
||||
child: TextButton(
|
||||
text: "Continue",
|
||||
onClickCallback: () async {
|
||||
await widget.loginManager
|
||||
.initAuthentication("+65${controller.text}");
|
||||
Navigator.pushNamed(context, 'welcome/otp');
|
||||
})),
|
||||
]));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,10 +2,13 @@ import "package:flutter/material.dart";
|
|||
import 'package:pin_code_text_field/pin_code_text_field.dart';
|
||||
import "package:flutter_svg/flutter_svg.dart";
|
||||
|
||||
import "../../../services/conversation_manager.dart";
|
||||
import "../../../services/login_manager.dart";
|
||||
|
||||
import "../../widgets/text_button.dart";
|
||||
|
||||
// Callback types
|
||||
typedef void ButtonCallback(String otp);
|
||||
typedef void ButtonCallback();
|
||||
|
||||
class OtpPage extends StatefulWidget {
|
||||
final ButtonCallback buttonCallback;
|
||||
|
@ -18,6 +21,7 @@ class OtpPage extends StatefulWidget {
|
|||
|
||||
class _OtpPageState extends State<OtpPage> {
|
||||
final String phoneSvg = "assets/authenticate.svg";
|
||||
final LoginManager loginManager = LoginManager();
|
||||
final controller = TextEditingController();
|
||||
|
||||
@override
|
||||
|
@ -46,9 +50,18 @@ class _OtpPageState extends State<OtpPage> {
|
|||
child: Center(
|
||||
child: PinCodeTextField(
|
||||
controller: controller,
|
||||
maxLength: 6,
|
||||
highlight: true,
|
||||
autofocus: true,
|
||||
highlightColor: Colors.white,
|
||||
/*hideCharacter: true,*/
|
||||
onTextChanged: (text) {},
|
||||
onDone: (text) {
|
||||
// widget.buttonCallback(text);
|
||||
},
|
||||
pinBoxHeight:
|
||||
(MediaQuery.of(context).size.width / 6) - 15,
|
||||
pinBoxWidth:
|
||||
(MediaQuery.of(context).size.width / 6) - 15,
|
||||
pinBoxDecoration: (Color color) {
|
||||
return BoxDecoration(
|
||||
color: Color(0x10000000),
|
||||
|
@ -61,7 +74,18 @@ class _OtpPageState extends State<OtpPage> {
|
|||
Spacer(),
|
||||
TextButton(
|
||||
text: "Done",
|
||||
onClickCallback: () => widget.buttonCallback(controller.text)),
|
||||
onClickCallback: () async {
|
||||
final authToken =
|
||||
await loginManager.processOtp(controller.text);
|
||||
final user = await loginManager.getUser();
|
||||
|
||||
await ConversationManager.init(authToken);
|
||||
if (user.firstName == "" && user.lastName == "") {
|
||||
Navigator.pushNamed(context, 'welcome/register');
|
||||
} else {
|
||||
widget.buttonCallback();
|
||||
}
|
||||
}),
|
||||
]));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,86 @@
|
|||
import "package:flutter/material.dart";
|
||||
import "package:flutter_svg/flutter_svg.dart";
|
||||
|
||||
import "../../widgets/text_button.dart";
|
||||
import "../../../services/login_manager.dart";
|
||||
import "../../../resources/user_api_provider.dart";
|
||||
|
||||
import "input.dart";
|
||||
|
||||
class RegisterPage extends StatefulWidget {
|
||||
final LoginManager loginManager;
|
||||
|
||||
RegisterPage({@required this.loginManager});
|
||||
|
||||
@override
|
||||
_RegisterPageState createState() => _RegisterPageState();
|
||||
}
|
||||
|
||||
class _RegisterPageState extends State<RegisterPage> {
|
||||
final String phoneSvg = "assets/phoneno.svg";
|
||||
|
||||
final firstNameController = TextEditingController();
|
||||
final lastNameController = TextEditingController();
|
||||
|
||||
final userApiProvider = UserApiProvider();
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
firstNameController.dispose();
|
||||
lastNameController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Padding(
|
||||
padding: EdgeInsets.only(left: 15.0, right: 15.0),
|
||||
child: Column(mainAxisSize: MainAxisSize.min, children: <Widget>[
|
||||
Expanded(
|
||||
child: SingleChildScrollView(
|
||||
child:
|
||||
Column(mainAxisSize: MainAxisSize.min, children: <Widget>[
|
||||
Padding(
|
||||
padding: EdgeInsets.only(top: 10.0, bottom: 20.0),
|
||||
child: SvgPicture.asset(phoneSvg,
|
||||
height: MediaQuery.of(context).size.height / 5)),
|
||||
Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
Text("Let's get you going.",
|
||||
textAlign: TextAlign.left,
|
||||
style: Theme.of(context).accentTextTheme.display3),
|
||||
Text("Enter your info to create your very own Beep account.",
|
||||
style: Theme.of(context)
|
||||
.accentTextTheme
|
||||
.title
|
||||
.copyWith(fontWeight: FontWeight.w400)),
|
||||
Padding(
|
||||
padding: EdgeInsets.only(top: 20.0),
|
||||
child: Input(
|
||||
controller: firstNameController,
|
||||
hintText: "First name")),
|
||||
Padding(
|
||||
padding: EdgeInsets.only(top: 10.0),
|
||||
child: Input(
|
||||
controller: lastNameController,
|
||||
hintText: "Last name")),
|
||||
])
|
||||
]))),
|
||||
Padding(
|
||||
padding: EdgeInsets.only(top: 10.0),
|
||||
child: TextButton(
|
||||
text: "Continue",
|
||||
onClickCallback: () async {
|
||||
final firstName = firstNameController.text;
|
||||
final lastName = lastNameController.text;
|
||||
|
||||
// Creating the new user
|
||||
await userApiProvider.updateUser(
|
||||
firstName, lastName);
|
||||
Navigator.of(context).pushReplacementNamed("/home");
|
||||
})),
|
||||
]));
|
||||
}
|
||||
}
|
|
@ -5,12 +5,21 @@ import "../../widgets/text_button.dart";
|
|||
|
||||
class WelcomePage extends StatelessWidget {
|
||||
final String welcomeSvg = "assets/welcome.svg";
|
||||
final String logo = "assets/logo.png";
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Padding(
|
||||
padding: EdgeInsets.only(left: 15.0, right: 15.0),
|
||||
child: Column(mainAxisSize: MainAxisSize.min, children: <Widget>[
|
||||
Row(mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[
|
||||
Image.asset(logo,
|
||||
semanticLabel: "Beep logo", width: 30.0, height: 50.0),
|
||||
Padding(
|
||||
padding: EdgeInsets.only(left: 10.0),
|
||||
child: Text("Beep",
|
||||
style: Theme.of(context).accentTextTheme.display3)),
|
||||
]),
|
||||
Spacer(),
|
||||
SvgPicture.asset(welcomeSvg,
|
||||
width: MediaQuery.of(context).size.width - 40.0),
|
||||
|
@ -25,7 +34,6 @@ class WelcomePage extends StatelessWidget {
|
|||
text: "Login",
|
||||
onClickCallback: () =>
|
||||
Navigator.pushNamed(context, "welcome/login")),
|
||||
TextButton(text: "Sign Up", onClickCallback: () => print("hello")),
|
||||
]));
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue