From 77e8e95907349034ddf474c2a15a3960f86550c7 Mon Sep 17 00:00:00 2001 From: Harshit Seksaria <37345795+letsintegreat@users.noreply.github.com> Date: Sat, 18 Mar 2023 02:17:00 +0530 Subject: [PATCH 1/2] add a page to change password --- lib/src/pages/drawer/change_password.dart | 279 +++++++++++++++++++++ lib/src/pages/home/home.dart | 19 ++ lib/src/routes/routing.dart | 23 ++ lib/src/util/api/auth_api.dart | 46 ++++ lib/src/util/endpoints/auth_endpoints.dart | 2 + 5 files changed, 369 insertions(+) create mode 100644 lib/src/pages/drawer/change_password.dart diff --git a/lib/src/pages/drawer/change_password.dart b/lib/src/pages/drawer/change_password.dart new file mode 100644 index 0000000000..26ef979ece --- /dev/null +++ b/lib/src/pages/drawer/change_password.dart @@ -0,0 +1,279 @@ +import 'package:blt/src/util/api/auth_api.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:google_fonts/google_fonts.dart'; + +import '../../routes/routing.dart'; + +/// The change password page for the app. +class ChangePasswordPage extends StatefulWidget { + const ChangePasswordPage({Key? key}) : super(key: key); + + @override + State createState() => _ChangePasswordPageState(); +} + +class _ChangePasswordPageState extends State { + @override + Widget build(BuildContext context) { + final Size size = MediaQuery.of(context).size; + return Scaffold( + appBar: AppBar( + backgroundColor: Colors.transparent, + elevation: 0, + leading: IconButton( + icon: Icon( + Icons.arrow_back_ios_new_rounded, + color: Colors.white, + ), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + ), + backgroundColor: Colors.transparent, + extendBodyBehindAppBar: true, + body: Stack( + children: [ + Container( + width: size.width, + height: size.height, + color: Color(0xFFDC4654), + child: Column( + children: [ + SizedBox( + width: size.width, + height: size.height * 0.35, + child: Padding( + padding: EdgeInsets.symmetric( + horizontal: 0.125 * size.width, + vertical: 0.085 * size.height, + ), + child: Center( + child: SvgPicture.asset( + 'assets/logo_white.svg', + fit: BoxFit.contain, + height: 192.0, + ), + ), + ), + ), + SizedBox( + width: size.width, + height: size.height * 0.65, + ), + ], + ), + ), + Positioned( + bottom: 0, + child: Container( + width: size.width, + height: size.height * 0.65, + decoration: BoxDecoration( + color: Theme.of(context).canvasColor, + boxShadow: [ + BoxShadow( + spreadRadius: 10, + color: Colors.black.withOpacity(0.1), + blurRadius: 50, + ) + ], + borderRadius: BorderRadius.only( + topLeft: Radius.circular(50), + topRight: Radius.circular(50.0), + ), + ), + child: Center( + child: ChangePasswordForm( + size: size, + ), + ), + ), + ) + ], + ), + ); + } +} + +class ChangePasswordForm extends StatefulWidget { + final Size size; + const ChangePasswordForm({ + Key? key, + required this.size, + }) : super(key: key); + + @override + State createState() => _ChangePasswordFormState(); +} + +class _ChangePasswordFormState extends State { + final _formKey = GlobalKey(); + + late TextEditingController _oldController, + _new1Controller, + _new2Controller; + + @override + void initState() { + super.initState(); + _oldController = TextEditingController(); + _new1Controller = TextEditingController(); + _new2Controller = TextEditingController(); + } + + @override + Widget build(BuildContext context) { + return Form( + key: _formKey, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + SizedBox( + width: 0.8 * widget.size.width, + child: TextFormField( + controller: _oldController, + validator: (value) { + if (value == null || value.isEmpty) { + return "This field is required"; + } + return null; + }, + onChanged: (val) {}, + decoration: InputDecoration( + hintText: "Old password", + prefixIcon: Icon(Icons.key_rounded), + filled: true, + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(10.0), + borderSide: BorderSide( + color: Color(0xFF737373), + ), + ), + fillColor: Colors.white.withOpacity(0.35), + ), + obscureText: true, + ), + ), + SizedBox( + height: 0.025 * widget.size.height, + ), + SizedBox( + width: 0.8 * widget.size.width, + child: TextFormField( + controller: _new1Controller, + validator: (value) { + if (value == null || value.isEmpty) { + return "This field is required"; + } + return null; + }, + decoration: InputDecoration( + hintText: "New password", + prefixIcon: Icon(Icons.password), + filled: true, + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(10.0), + borderSide: BorderSide( + color: Color(0xFF737373), + ), + ), + fillColor: Colors.white.withOpacity(0.35), + ), + obscureText: true, + ), + ), + SizedBox( + height: 0.025 * widget.size.height, + ), + SizedBox( + width: 0.8 * widget.size.width, + child: TextFormField( + controller: _new2Controller, + validator: (value) { + if (value == null || value.isEmpty) { + return "This field is required"; + } else if (_new1Controller.text != _new2Controller.text) { + return "Passwords don't match"; + } + return null; + }, + decoration: InputDecoration( + hintText: "Confirm new password", + prefixIcon: Icon(Icons.password), + filled: true, + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(10.0), + borderSide: BorderSide( + color: Color(0xFF737373), + ), + ), + fillColor: Colors.white.withOpacity(0.35), + ), + obscureText: true, + ), + ), + SizedBox( + height: 0.025 * widget.size.height, + ), + SizedBox( + width: 0.8 * widget.size.width, + height: 50, + child: TextButton( + onPressed: () async { + if (_formKey.currentState!.validate()) { + bool oldPasswordCorrect = await AuthApiClient.checkPassword(_oldController.text); + if (!oldPasswordCorrect) { + SnackBar sentSnack = SnackBar( + content: Text("Incorrect old password."), + ); + ScaffoldMessenger.of(context).showSnackBar(sentSnack); + return; + } + + await AuthApiClient.changePassword( + _new1Controller.text, + _new2Controller.text, + context, + ); + } + }, + child: Text( + "Change password", + style: GoogleFonts.ubuntu( + textStyle: TextStyle( + color: Colors.white, + fontSize: 17.5, + ), + ), + ), + style: ButtonStyle( + elevation: MaterialStateProperty.all(8), + shadowColor: MaterialStateProperty.all( + Colors.black.withOpacity(0.5), + ), + shape: MaterialStateProperty.all( + RoundedRectangleBorder( + borderRadius: BorderRadius.circular(15.0), + ), + ), + backgroundColor: MaterialStateProperty.all( + Color(0xFFDC4654), + ), + ), + ), + ), + ], + ), + ); + } + + @override + void dispose() { + _oldController.dispose(); + _new1Controller.dispose(); + _new2Controller.dispose(); + super.dispose(); + } +} diff --git a/lib/src/pages/home/home.dart b/lib/src/pages/home/home.dart index 73b3fc476e..a98ff9cd1a 100644 --- a/lib/src/pages/home/home.dart +++ b/lib/src/pages/home/home.dart @@ -150,6 +150,24 @@ class _HomeState extends ConsumerState { ); } + Widget changePasswordListTile() { + LoginType loginState = ref.watch(loginProvider); + + if (loginState == LoginType.guest) { + return Container(); + } else { + return ListTile( + title: Text('Change password'), + onTap: () { + Navigator.pushNamed( + context, + RouteManager.changePassword + ); + }, + ); + } + } + NetworkImage? buildAvatar() { LoginType loginState = ref.watch(loginProvider); @@ -239,6 +257,7 @@ class _HomeState extends ConsumerState { await logout(); }, ), + changePasswordListTile(), ListTile( title: Text('Social'), onTap: () { diff --git a/lib/src/routes/routing.dart b/lib/src/routes/routing.dart index 9a6f1e8e5b..42239827fa 100644 --- a/lib/src/routes/routing.dart +++ b/lib/src/routes/routing.dart @@ -1,6 +1,7 @@ import 'package:blt/src/models/issue_model.dart'; import 'package:blt/src/pages/auth/forgot.dart'; import 'package:blt/src/pages/auth/signup.dart'; +import 'package:blt/src/pages/drawer/change_password.dart'; import 'package:blt/src/pages/error.dart'; import 'package:blt/src/pages/home/home.dart'; import 'package:blt/src/pages/drawer/legal.dart'; @@ -39,6 +40,7 @@ class RouteManager { static const String companyScoreboardPage = "/companyBoard"; static const String companyDetailPage = "/companyDetail"; static const String issueDetailPage = "/issueDetail"; + static const String changePassword = "/changePassword"; /// Route generator, finds a requested route or throws the /// error page in case of route not found. @@ -361,6 +363,27 @@ class RouteManager { transitionDuration: const Duration(milliseconds: 750), ); + case changePassword: + return PageRouteBuilder( + pageBuilder: (context, animation, secondaryAnimation) => + const ChangePasswordPage(), + transitionsBuilder: (context, animation, secondaryAnimation, child) { + const begin = Offset(1.0, 0); + const end = Offset.zero; + const curve = Curves.ease; + + var tween = + Tween(begin: begin, end: end).chain(CurveTween(curve: curve)); + + return SlideTransition( + position: animation.drive(tween), + child: child, + ); + }, + transitionDuration: const Duration(milliseconds: 750), + ); + + default: return PageRouteBuilder( pageBuilder: (context, animation, secondaryAnimation) => ErrorPage(), diff --git a/lib/src/util/api/auth_api.dart b/lib/src/util/api/auth_api.dart index e762fd8a96..dfa46040d5 100644 --- a/lib/src/util/api/auth_api.dart +++ b/lib/src/util/api/auth_api.dart @@ -121,4 +121,50 @@ class AuthApiClient { } } catch (e) {} } + + static Future checkPassword(String password) async { + http.Response? response; + try { + response = await http.post( + Uri.parse(AuthEndPoints.emailpasswordLogin), + body: { + "username": currentUser!.username, + "password": password + }, + ); + print(response.body); + return response.statusCode == 200; + } catch (e) {} + return false; + } + + /// Change the password of the logged in user. + static Future changePassword(String newPassword1, String newPassword2, BuildContext context) async { + http.Response? response; + try { + response = await http.post( + Uri.parse(AuthEndPoints.change), + body: { + "new_password1": newPassword1, + "new_password2": newPassword2 + }, + headers: { + "Authorization": "Token ${currentUser!.token!}", + }, + ); + print(response.body); + if (response.statusCode == 200) { + SnackBar sentSnack = SnackBar( + content: Text("Password changed successfully"), + ); + ScaffoldMessenger.of(context).showSnackBar(sentSnack); + } else { + var decodedResponse = jsonDecode(response.body); + SnackBar sentSnack = SnackBar( + content: Text(decodedResponse["new_password2"][0]), + ); + ScaffoldMessenger.of(context).showSnackBar(sentSnack); + } + } catch (e) {} + } } diff --git a/lib/src/util/endpoints/auth_endpoints.dart b/lib/src/util/endpoints/auth_endpoints.dart index 744057e4d8..73d9e5d6e5 100644 --- a/lib/src/util/endpoints/auth_endpoints.dart +++ b/lib/src/util/endpoints/auth_endpoints.dart @@ -13,4 +13,6 @@ class AuthEndPoints { static const String register = authBaseUrl + "registration/"; static const String reset = authBaseUrl + "password/reset/"; + + static const String change = authBaseUrl + "password/change/"; } From 30315dffd0aa263d58bbf0f22ca2a27d64023b1e Mon Sep 17 00:00:00 2001 From: Harshit Seksaria <37345795+letsintegreat@users.noreply.github.com> Date: Sun, 19 Mar 2023 09:45:24 +0530 Subject: [PATCH 2/2] move the feature to user profile --- lib/src/pages/home/home.dart | 19 ---------- lib/src/pages/home/profile.dart | 66 ++++++++++++++++++++++----------- 2 files changed, 45 insertions(+), 40 deletions(-) diff --git a/lib/src/pages/home/home.dart b/lib/src/pages/home/home.dart index a98ff9cd1a..73b3fc476e 100644 --- a/lib/src/pages/home/home.dart +++ b/lib/src/pages/home/home.dart @@ -150,24 +150,6 @@ class _HomeState extends ConsumerState { ); } - Widget changePasswordListTile() { - LoginType loginState = ref.watch(loginProvider); - - if (loginState == LoginType.guest) { - return Container(); - } else { - return ListTile( - title: Text('Change password'), - onTap: () { - Navigator.pushNamed( - context, - RouteManager.changePassword - ); - }, - ); - } - } - NetworkImage? buildAvatar() { LoginType loginState = ref.watch(loginProvider); @@ -257,7 +239,6 @@ class _HomeState extends ConsumerState { await logout(); }, ), - changePasswordListTile(), ListTile( title: Text('Social'), onTap: () { diff --git a/lib/src/pages/home/profile.dart b/lib/src/pages/home/profile.dart index 1167482ef9..de653cbd27 100644 --- a/lib/src/pages/home/profile.dart +++ b/lib/src/pages/home/profile.dart @@ -4,6 +4,7 @@ import 'package:google_fonts/google_fonts.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:image_picker/image_picker.dart'; +import '../../providers/login_provider.dart'; import '../../routes/routing.dart'; import '../../global/variables.dart'; import '../../models/user_model.dart'; @@ -14,6 +15,7 @@ import '../../providers/authstate_provider.dart'; import '../../models/issuedata_model.dart'; import '../../util/endpoints/issue_endpoints.dart'; import '../../pages/welcome.dart'; +import '../../util/enums/login_type.dart'; /// Page that displays the stats of a user registered on BLT, /// shows dummy data for Guest login. @@ -317,26 +319,6 @@ class _UserProfileState extends ConsumerState { ), title: Text(currentUser!.username!), actions: [ - IconButton( - onPressed: () async { - final ImagePicker _picker = ImagePicker(); - final XFile? image = - await _picker.pickImage(source: ImageSource.gallery); - if (image == null) { - return; - } - SnackBar updatingSnack = SnackBar( - duration: const Duration(seconds: 6), - content: Text( - "Updating profile picture", - ), - ); - ScaffoldMessenger.of(context).showSnackBar(updatingSnack); - await UserApiClient.updatePfp(image, currentUser!); - setState(() {}); - }, - icon: Icon(Icons.account_circle_outlined), - ), IconButton( onPressed: () async { await forgetUser(); @@ -349,7 +331,49 @@ class _UserProfileState extends ConsumerState { await logout(); }, icon: Icon(Icons.power_settings_new_rounded), - ) + ), + PopupMenuButton( + onSelected: (String value) async { + switch(value) { + case 'Change picture': + final ImagePicker _picker = ImagePicker(); + final XFile? image = + await _picker.pickImage(source: ImageSource.gallery); + if (image == null) { + return; + } + SnackBar updatingSnack = SnackBar( + duration: const Duration(seconds: 6), + content: Text( + "Updating profile picture", + ), + ); + ScaffoldMessenger.of(context).showSnackBar(updatingSnack); + await UserApiClient.updatePfp(image, currentUser!); + setState(() {}); + break; + + case 'Change password': + Navigator.pushNamed( + context, + RouteManager.changePassword + ); + } + }, + itemBuilder: (BuildContext context) { + List optionsList = ['Change picture']; + LoginType loginState = ref.watch(loginProvider); + if (loginState != LoginType.guest) { + optionsList.add('Change password'); + } + return optionsList.map((String choice) { + return PopupMenuItem( + value: choice, + child: Text(choice), + ); + }).toList(); + }, + ), ], ), body: Container(