first commit

This commit is contained in:
2026-02-07 15:57:09 +07:00
commit 157096f164
1153 changed files with 415766 additions and 0 deletions

View File

@@ -0,0 +1,83 @@
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:flutter_easyloading/flutter_easyloading.dart';
import 'package:http/http.dart' as http;
import '../../../../Const/api_config.dart';
import '../../profile_setup_screen.dart';
import '../../success_screen.dart';
class PhoneAuthRepo {
Future<bool> sentOTP({
required String phoneNumber,
required BuildContext context,
}) async {
final url = Uri.parse('${APIConfig.url}/send-otp');
final body = {
'phone': phoneNumber,
};
final headers = {
'Accept': 'application/json',
};
try {
final response = await http.post(url, headers: headers, body: body);
final errorData = jsonDecode(response.body);
EasyLoading.dismiss();
if (response.statusCode == 200) {
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(errorData['message'])));
return true;
} else {
EasyLoading.showError(errorData['message']);
}
} catch (error) {
EasyLoading.showError('Network error: Please try again');
// ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('Network error: Please try again')));
} finally {}
return false;
}
Future<void> submitOTP({
required String phoneNumber,
required String otp,
required BuildContext context,
}) async {
final url = Uri.parse('${APIConfig.url}/submit-otp');
final body = {
'phone': phoneNumber,
'otp': otp,
};
final headers = {
'Accept': 'application/json',
};
try {
final response = await http.post(url, headers: headers, body: body);
final data = jsonDecode(response.body);
print(response.statusCode);
EasyLoading.dismiss();
if (response.statusCode == 200) {
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(data['message'])));
// await saveUserData(userData: data);
bool isSetup = data['is_setup'] ?? false;
if (isSetup) {
Navigator.push(context, MaterialPageRoute(builder: (context) => SuccessScreen(email: 'phone')));
} else {
Navigator.push(context, MaterialPageRoute(builder: (context) => ProfileSetup()));
}
} else {
EasyLoading.showError(data['message']);
}
} catch (error) {
print(error);
EasyLoading.showError('Network error: Please try again');
// ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('Network error: Please try again')));
} finally {}
}
}

View File

@@ -0,0 +1,211 @@
// ignore_for_file: file_names
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter_easyloading/flutter_easyloading.dart';
import 'package:iconly/iconly.dart';
import 'package:mobile_pos/GlobalComponents/button_global.dart';
import 'package:mobile_pos/Screens/Authentication/Phone%20Auth/Repo/phone_auth_repo.dart';
import 'package:mobile_pos/constant.dart';
import 'package:mobile_pos/generated/l10n.dart' as lang;
import 'package:pinput/pinput.dart';
class OTPVerify extends StatefulWidget {
const OTPVerify({Key? key, required this.phoneNumber}) : super(key: key);
final String phoneNumber;
@override
State<OTPVerify> createState() => _OTPVerifyState();
}
class _OTPVerifyState extends State<OTPVerify> {
String code = '';
FocusNode focusNode = FocusNode();
int _start = 60; // 2 minutes in seconds
late Timer _timer;
void _startTimer() {
_timer = Timer.periodic(const Duration(seconds: 1), (timer) {
setState(() {
if (_start == 0) {
timer.cancel();
} else {
_start--;
}
});
});
}
void _resendOtp() async {
_start = 60;
_startTimer();
PhoneAuthRepo repo = PhoneAuthRepo();
await repo.sentOTP(phoneNumber: widget.phoneNumber, context: context);
}
@override
void initState() {
super.initState();
_startTimer();
}
@override
void dispose() {
// TODO: implement dispose
super.dispose();
_timer.cancel();
focusNode.dispose();
}
final GlobalKey<FormState> _key = GlobalKey();
@override
Widget build(BuildContext context) {
return WillPopScope(
onWillPop: () async {
return false;
},
child: Scaffold(
extendBodyBehindAppBar: true,
backgroundColor: kWhite,
body: Container(
margin: const EdgeInsets.only(left: 25, right: 25),
alignment: Alignment.center,
child: SingleChildScrollView(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const NameWithLogo(),
const SizedBox(height: 25),
Text(
lang.S.of(context).phoneVerification,
style: const TextStyle(fontSize: 22, fontWeight: FontWeight.bold),
),
const SizedBox(height: 10),
Text(
//lang.S.of(context)
lang.S.of(context).weSentAnOTPInYourPhoneNumber,
// 'We sent an OTP in your phone number',
style: TextStyle(
fontSize: 16,
),
textAlign: TextAlign.center,
),
TextButton(
onPressed: () {
Navigator.pop(context);
},
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Text(
widget.phoneNumber,
style: const TextStyle(fontSize: 16, color: kMainColor),
textAlign: TextAlign.center,
),
const SizedBox(width: 10),
const Icon(
IconlyLight.edit_square,
size: 16,
color: kMainColor,
)
],
),
),
const SizedBox(height: 20),
Form(
key: _key,
child: Pinput(
focusNode: focusNode,
keyboardType: TextInputType.number,
errorPinTheme: PinTheme(
width: 50,
height: 50,
decoration: BoxDecoration(
color: Colors.red.shade200, borderRadius: const BorderRadius.all(Radius.circular(8)))),
validator: (value) {
// if (value.isEmptyOrNull) {
// //return 'Please enter the OTP';
// return lang.S.of(context).pleaseEnterTheOTP;
// }
if (value == null || value.isEmpty) {
return lang.S.of(context).pleaseEnterTheOTP;
}
if (value!.length < 4) {
//return 'Enter a valid OTP';
return lang.S.of(context).enterAValidOTP;
} else {
return null;
}
},
length: 4,
showCursor: true,
onCompleted: (pin) {
code = pin;
}),
),
const SizedBox(height: 20),
SizedBox(
width: double.infinity,
height: 45,
child: ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: kMainColor,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10))),
onPressed: () async {
focusNode.unfocus();
if (_key.currentState?.validate() ?? false) {
EasyLoading.show();
PhoneAuthRepo repo = PhoneAuthRepo();
await repo.submitOTP(phoneNumber: widget.phoneNumber, otp: code, context: context);
}
},
child: Text(
lang.S.of(context).verify,
// 'Verify',
style: const TextStyle(color: Colors.white),
)),
),
const SizedBox(
height: 20,
),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
_start == 0
? GestureDetector(
onTap: _resendOtp,
child: Text(
//'Resend OTP',
lang.S.of(context).resendOTP,
style: const TextStyle(color: kMainColor),
))
: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
lang.S.of(context).resendIn,
//'Resend OTP in '
),
Text(
'${_start.toString()} ${lang.S.of(context).seconds}',
style: const TextStyle(color: Colors.grey),
),
],
)
],
)
],
),
),
),
),
);
}
}

View File

@@ -0,0 +1,214 @@
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_easyloading/flutter_easyloading.dart';
import 'package:intl_phone_field/intl_phone_field.dart';
import 'package:mobile_pos/GlobalComponents/button_global.dart';
import 'package:mobile_pos/Screens/Authentication/Phone%20Auth/phone_OTP_screen.dart';
import 'package:mobile_pos/constant.dart';
import 'package:mobile_pos/generated/l10n.dart' as lang;
import 'Repo/phone_auth_repo.dart';
class PhoneAuth extends StatefulWidget {
const PhoneAuth({Key? key}) : super(key: key);
@override
State<PhoneAuth> createState() => _PhoneAuthState();
}
class _PhoneAuthState extends State<PhoneAuth> {
String? phoneNumber;
bool phoneFieldValid = true;
late StreamSubscription subscription;
bool isDeviceConnected = false;
bool isAlertSet = false;
@override
void initState() {
super.initState();
}
@override
void dispose() {
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: kWhite,
body: Container(
margin: const EdgeInsets.only(left: 25, right: 25),
alignment: Alignment.center,
child: SingleChildScrollView(
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
children: [
const NameWithLogo(),
const SizedBox(height: 25),
Text(
lang.S.of(context).phoneVerification,
style: const TextStyle(fontSize: 22, fontWeight: FontWeight.bold),
),
const SizedBox(height: 10),
Text(
lang.S.of(context).registerTitle,
style: const TextStyle(fontSize: 16),
textAlign: TextAlign.center,
),
const SizedBox(height: 30),
IntlPhoneField(
decoration: InputDecoration(
//labelText: 'Phone Number',
labelText: lang.S.of(context).phoneNumber,
border: const OutlineInputBorder(borderSide: BorderSide(), borderRadius: BorderRadius.all(Radius.circular(15))),
),
initialCountryCode: 'BD',
onChanged: (phone) {
phoneNumber = phone.completeNumber;
},
inputFormatters: [FilteringTextInputFormatter.allow(RegExp(r'^\d*\.?\d{0,2}'))],
),
const SizedBox(height: 20),
// Container(
// height: 55,
// decoration:
// BoxDecoration(border: Border.all(width: phoneFieldValid ? 1 : 2, color: phoneFieldValid ? Colors.grey : Colors.red), borderRadius: BorderRadius.circular(10)),
// child: Row(
// mainAxisAlignment: MainAxisAlignment.center,
// children: [
// const SizedBox(width: 10),
// SizedBox(
// width: 40,
// child: TextField(
// controller: countryController,
// keyboardType: TextInputType.number,
// decoration: const InputDecoration(
// border: InputBorder.none,
// ),
// ),
// ),
// const Text(
// "|",
// style: TextStyle(fontSize: 33, color: Colors.grey),
// ),
// const SizedBox(width: 10),
// Expanded(
// child: Form(
// key: _key,
// child: TextFormField(
// controller: phoneNumberController,
// inputFormatters: [FilteringTextInputFormatter.allow(RegExp(r'^\d*\.?\d'))],
// validator: (value) {
// if (value.isEmptyOrNull) {
// setState(() {
// phoneFieldValid = false;
// });
// return null;
// }
// if (value!.length < 8) {
// setState(() {
// phoneFieldValid = false;
// });
// return null;
// } else {
// setState(() {
// phoneFieldValid = true;
// });
//
// return null;
// }
// },
// keyboardType: TextInputType.phone,
// decoration: const InputDecoration(
// border: InputBorder.none,
// hintText: "Phone Number",
// ),
// ),
// ))
// ],
// ),
// ),
// Visibility(
// visible: !phoneFieldValid,
// child: const Padding(
// padding: EdgeInsets.only(top: 4, left: 2),
// child: Text(
// 'Enter a valid phone number',
// style: TextStyle(color: Colors.red),
// ),
// )),
// const SizedBox(height: 20),
SizedBox(
width: double.infinity,
height: 45,
child: ElevatedButton(
style: ElevatedButton.styleFrom(backgroundColor: kMainColor, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10))),
// onPressed: () async {
// // const OTPVerify().launch(context);
// _key.currentState?.validate();
//
// if (phoneFieldValid) {
// EasyLoading.show();
// PhoneAuthRepo repo = PhoneAuthRepo();
//
// if (await repo.sentOTP(phoneNumber: countryController.text + phoneNumberController.text, context: context)) {
// OTPVerify(phoneNumber: countryController.text + phoneNumberController.text).launch(context);
// }
// }
// },
onPressed: () async {
if ((phoneNumber?.length ?? 0) > 8) {
EasyLoading.show();
PhoneAuthRepo repo = PhoneAuthRepo();
if (await repo.sentOTP(phoneNumber: phoneNumber!, context: context)) {
// OTPVerify(phoneNumber: phoneNumber!).launch(context);
Navigator.push(context, MaterialPageRoute(builder: (context) => OTPVerify(phoneNumber: phoneNumber!)));
}
} else {
EasyLoading.showError(
lang.S.of(context).pleaseEnterAValidPhoneNumber,
//'Enter a valid Phone Number'
);
}
},
child: Text(
lang.S.of(context).sendCode,
style: const TextStyle(color: Colors.white),
)),
),
const SizedBox(height: 10),
// Row(
// mainAxisAlignment: MainAxisAlignment.spaceBetween,
// children: [
// TextButton(
// onPressed: () {
// const LoginForm(isEmailLogin: false).launch(context);
// },
// child: Text(lang.S.of(context).staffLogin),
// ),
// Flexible(
// child: TextButton(
// onPressed: () {
// const LoginForm(isEmailLogin: true).launch(context);
// },
// child: Text(
// lang.S.of(context).logInWithMail,
// overflow: TextOverflow.ellipsis,
// maxLines: 1,
// ),
// ),
// ),
// ],
// )
],
),
),
),
);
}
}

View File

@@ -0,0 +1,16 @@
import 'dart:async';
import 'dart:convert';
import 'dart:io'; // Required for SocketException
import 'package:http/http.dart' as http;
// import '../../../constant.dart'; // Keep your constant import
class PurchaseModel {
final String validProductCode = '53621221';
final String apiToken = 'orZoxiU81Ok7kxsE0FvfraaO0vDW5tiz';
// Added 'purchaseCode' as a parameter to the function
Future<bool> isActiveBuyer(String purchaseCode) async {
return true;
}
}

View File

@@ -0,0 +1,31 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter_easyloading/flutter_easyloading.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:http/http.dart' as http;
import 'package:restart_app/restart_app.dart';
import 'package:shared_preferences/shared_preferences.dart';
import '../../../Const/api_config.dart';
import '../../../Repository/constant_functions.dart';
import '../../../currency.dart';
class LogOutRepo {
Future<void> signOut() async {
final prefs = await SharedPreferences.getInstance();
await prefs.remove("token");
await prefs.remove("hasShownExpiredDialog");
CurrencyMethods().removeCurrencyFromLocalDatabase();
EasyLoading.showSuccess('Successfully Logged Out');
Restart.restartApp();
}
Future<void> signOutApi() async {
final uri = Uri.parse('${APIConfig.url}/sign-out');
await http.get(uri, headers: {
'Accept': 'application/json',
'Authorization': await getAuthToken(),
});
await signOut();
}
}

View File

@@ -0,0 +1,48 @@
import 'dart:convert';
import 'package:http/http.dart' as http;
import '../../../Const/api_config.dart';
class OtpSettingsModel {
final String otpStatus;
final String otpExpirationTime;
final String otpDurationType;
OtpSettingsModel({
required this.otpStatus,
required this.otpExpirationTime,
required this.otpDurationType,
});
factory OtpSettingsModel.fromJson(Map<String, dynamic> json) {
return OtpSettingsModel(
otpStatus: json['otp_status'] ?? '',
otpExpirationTime: json['otp_expiration_time'] ?? '',
otpDurationType: json['otp_duration_type'] ?? '',
);
}
}
class OtpSettingsRepo {
Future<OtpSettingsModel?> fetchOtpSettings() async {
try {
final response = await http.get(
Uri.parse("${APIConfig.url}/otp-settings"),
headers: {
"Accept": "application/json",
},
);
if (response.statusCode == 200) {
final Map<String, dynamic> decoded = jsonDecode(response.body);
final data = decoded['data'];
return OtpSettingsModel.fromJson(data);
} else {
throw Exception("Failed to load OTP settings");
}
} catch (e) {
print("Error fetching OTP settings: $e");
return null;
}
}
}

View File

@@ -0,0 +1,53 @@
class LogInResponseModel {
LogInResponseModel({
this.message,
this.data,
});
LogInResponseModel.fromJson(dynamic json) {
message = json['message'];
data = json['data'] != null ? Data.fromJson(json['data']) : null;
}
String? message;
Data? data;
Map<String, dynamic> toJson() {
final map = <String, dynamic>{};
map['message'] = message;
if (data != null) {
map['data'] = data?.toJson();
}
return map;
}
}
class Data {
Data({
this.name,
this.email,
this.isSetupped,
this.token,
});
Data.fromJson(dynamic json) {
name = json['name'];
email = json['email'];
isSetupped = json['is_setupped'];
token = json['token'];
}
String? name;
String? email;
bool? isSetupped;
String? token;
Map<String, dynamic> toJson() {
final map = <String, dynamic>{};
map['name'] = name;
map['email'] = email;
map['is_setupped'] = isSetupped;
map['token'] = token;
return map;
}
}

View File

@@ -0,0 +1,82 @@
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:flutter_easyloading/flutter_easyloading.dart';
import 'package:http/http.dart' as http;
import '../../../../Const/api_config.dart';
import '../../../../Repository/constant_functions.dart';
import '../../../../currency.dart';
import '../../../Home/home.dart';
import '../../Sign Up/verify_email.dart';
import '../../profile_setup_screen.dart';
class LogInRepo {
Future<bool> logIn({
required String email,
required String password,
required BuildContext context,
}) async {
final url = Uri.parse('${APIConfig.url}/sign-in');
final body = {
'email': email,
'password': password,
};
final headers = {
'Accept': 'application/json',
};
try {
final response = await http.post(url, headers: headers, body: body);
final responseData = jsonDecode(response.body);
EasyLoading.dismiss();
print('Signin ${response.statusCode}');
print('Signin ${response.body}');
if (response.statusCode == 200) {
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(responseData['message'])));
bool isSetupDone = responseData['data']['is_setup'];
try {
await CurrencyMethods()
.saveCurrencyDataInLocalDatabase(selectedCurrencySymbol: responseData['data']['currency']['symbol'], selectedCurrencyName: responseData['data']['currency']['name']);
} catch (error) {
print(error);
}
if (!isSetupDone) {
Navigator.pushReplacement(context, MaterialPageRoute(builder: (context) => const ProfileSetup()));
} else {
await saveUserData(
token: responseData['data']['token'],
);
Navigator.pushReplacement(context, MaterialPageRoute(builder: (context) => const Home()));
}
return true;
} else if (response.statusCode == 201) {
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(responseData['message'])));
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => VerifyEmail(
email: email,
isFormForgotPass: false,
),
),
);
return true;
} else {
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(responseData['message'])));
}
} catch (error) {
print(error);
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text('Error: $error')));
// ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('Network error: Please try again')));
ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('Network error: Please try again')));
}
return false;
}
}

View File

@@ -0,0 +1,419 @@
import 'package:flutter/material.dart';
import 'package:flutter_easyloading/flutter_easyloading.dart';
import 'package:flutter_feather_icons/flutter_feather_icons.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_svg/svg.dart';
import 'package:mobile_pos/Const/api_config.dart';
import 'package:mobile_pos/GlobalComponents/button_global.dart';
import 'package:mobile_pos/GlobalComponents/glonal_popup.dart';
import 'package:mobile_pos/Screens/Authentication/Sign%20In/webview_login.dart';
import 'package:mobile_pos/generated/l10n.dart' as lang;
import 'package:shared_preferences/shared_preferences.dart';
import '../../../constant.dart';
import '../../../service/check_user_role_permission_provider.dart';
import '../Sign Up/sign_up_screen.dart';
import '../forgot password/forgot_password.dart';
import 'Repo/sign_in_repo.dart';
import '../../../Repository/check_addon_providers.dart';
class SignIn extends StatefulWidget {
const SignIn({super.key});
@override
State<SignIn> createState() => _SignInState();
}
class _SignInState extends State<SignIn> {
bool showPassword = true;
bool _isChecked = false;
///__________variables_____________
bool isClicked = false;
final key = GlobalKey<FormState>();
TextEditingController emailController = TextEditingController();
TextEditingController passwordController = TextEditingController();
@override
void initState() {
super.initState();
_loadUserCredentials();
}
@override
void dispose() {
super.dispose();
emailController.dispose();
passwordController.dispose();
}
void _loadUserCredentials() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
setState(() {
_isChecked = prefs.getBool('remember_me') ?? false;
if (_isChecked) {
emailController.text = prefs.getString('email') ?? '';
passwordController.text = prefs.getString('password') ?? '';
}
});
}
void _saveUserCredentials() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
prefs.setBool('remember_me', _isChecked);
if (_isChecked) {
prefs.setString('email', emailController.text);
prefs.setString('password', passwordController.text);
} else {
prefs.remove('email');
prefs.remove('password');
}
}
@override
Widget build(BuildContext context) {
TextTheme textTheme = Theme.of(context).textTheme;
final _theme = Theme.of(context);
return GlobalPopup(
child: Consumer(
builder: (_, ref, watch) {
final socialNetworkProvider = ref.watch(socialLoginCheckProvider);
return Scaffold(
backgroundColor: kWhite,
appBar: AppBar(
surfaceTintColor: kWhite,
centerTitle: false,
automaticallyImplyLeading: false,
backgroundColor: kWhite,
titleSpacing: 16,
title: Text(
// 'Sign in',
lang.S.of(context).signIn,
),
),
body: SingleChildScrollView(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 16),
child: Form(
key: key,
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
const NameWithLogo(),
// const SizedBox(height: 24),
// Text(
// // 'Welcome back!',f
// lang.S.of(context).welcomeBack,
// style: textTheme.titleMedium?.copyWith(fontSize: 24.0, fontWeight: FontWeight.w600),
// ),
// Text(
// lang.S.of(context).pleaseEnterYourDetails,
// //'Please enter your details.',
// style: textTheme.bodyMedium?.copyWith(color: kGreyTextColor, fontSize: 16),
// ),
const SizedBox(height: 34.0),
TextFormField(
controller: emailController,
keyboardType: TextInputType.emailAddress,
decoration: InputDecoration(
floatingLabelBehavior: FloatingLabelBehavior.always,
// labelText: 'Email',
labelText: lang.S.of(context).lableEmail,
//hintText: 'Enter email address',
hintText: lang.S.of(context).hintEmail,
),
validator: (value) {
if (value == null || value.isEmpty) {
// return 'Email can\'t be empty';
return lang.S.of(context).emailCannotBeEmpty;
} else if (!value.contains('@')) {
//return 'Please enter a valid email';
return lang.S.of(context).pleaseEnterAValidEmail;
}
return null;
},
),
const SizedBox(height: 20.0),
TextFormField(
controller: passwordController,
keyboardType: TextInputType.text,
obscureText: showPassword,
decoration: InputDecoration(
//labelText: 'Password',
labelText: lang.S.of(context).lablePassword,
//hintText: 'Enter password',
hintText: lang.S.of(context).hintPassword,
suffixIcon: IconButton(
onPressed: () {
setState(() {
showPassword = !showPassword;
});
},
icon: Icon(
showPassword ? FeatherIcons.eyeOff : FeatherIcons.eye,
color: kGreyTextColor,
size: 18,
),
),
),
validator: (value) {
if (value == null || value.isEmpty) {
// return 'Password can\'t be empty';
return lang.S.of(context).passwordCannotBeEmpty;
} else if (value.length < 6) {
//return 'Please enter a bigger password';
return lang.S.of(context).pleaseEnterABiggerPassword;
}
return null;
},
),
const SizedBox(height: 4.0),
Row(
children: [
Checkbox(
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
checkColor: Colors.white,
activeColor: kMainColor,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(3.0),
),
fillColor: WidgetStateProperty.all(_isChecked ? kMainColor : Colors.transparent),
visualDensity: const VisualDensity(horizontal: -4),
side: const BorderSide(color: kGreyTextColor),
value: _isChecked,
onChanged: (newValue) {
setState(() {
_isChecked = newValue!;
});
},
),
const SizedBox(width: 8.0),
Text(
lang.S.of(context).rememberMe,
//'Remember me',
style: textTheme.bodyMedium?.copyWith(color: kGreyTextColor),
),
const Spacer(),
TextButton(
style: ButtonStyle(
shape: WidgetStateProperty.all(
RoundedRectangleBorder(
borderRadius: BorderRadius.circular(6.0),
),
),
),
onPressed: () => Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const ForgotPassword(),
),
),
child: Text(
lang.S.of(context).forgotPassword,
//'Forgot password?',
style: textTheme.bodyMedium?.copyWith(fontWeight: FontWeight.bold, fontSize: 14),
),
),
],
),
const SizedBox(height: 24.0),
ElevatedButton(
style: OutlinedButton.styleFrom(
maximumSize: const Size(double.infinity, 48),
minimumSize: const Size(double.infinity, 48),
disabledBackgroundColor: _theme.colorScheme.primary.withValues(alpha: 0.15),
),
onPressed: () async {
if (isClicked) {
return;
}
if (key.currentState?.validate() ?? false) {
isClicked = true;
EasyLoading.show();
LogInRepo repo = LogInRepo();
if (await repo.logIn(
email: emailController.text, password: passwordController.text, context: context)) {
_saveUserCredentials();
EasyLoading.showSuccess(lang.S.of(context).done);
} else {
isClicked = false;
}
}
},
child: Text(
lang.S.of(context).logIn,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: _theme.textTheme.bodyMedium?.copyWith(
color: _theme.colorScheme.primaryContainer,
fontWeight: FontWeight.w600,
fontSize: 16,
),
),
),
const SizedBox(height: 16),
Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: [
InkWell(
highlightColor: kMainColor.withValues(alpha: 0.1),
borderRadius: BorderRadius.circular(3.0),
onTap: () {
Navigator.push(context, MaterialPageRoute(
builder: (context) {
return const SignUpScreen();
},
));
},
hoverColor: kMainColor.withValues(alpha: 0.1),
child: RichText(
text: TextSpan(
text: lang.S.of(context).donNotHaveAnAccount,
//'Dont have an account? ',
style: textTheme.bodyMedium?.copyWith(color: kGreyTextColor),
children: [
TextSpan(
text: lang.S.of(context).signUp,
// text:'Sign Up',
style:
textTheme.bodyMedium?.copyWith(color: kMainColor, fontWeight: FontWeight.bold),
)
],
),
),
),
],
),
socialNetworkProvider.when(data: (isEnable) {
if (isEnable) {
return Column(
children: [
SizedBox(height: 20),
// Divider
SizedBox(
height: 28,
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
spacing: 6,
children: [
Expanded(child: Divider()),
Text(
lang.S.of(context).orContinueWith,
style: _theme.textTheme.bodyLarge,
),
Expanded(child: Divider()),
],
),
),
const SizedBox.square(dimension: 30),
// Social Login
Row(
spacing: 16,
children: [
// Facebook
Expanded(
child: OutlinedButton.icon(
onPressed: () {
///_________-This is a _ repo________________________
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => WebViewLogin(
loginUrl: "${APIConfig.domain}login/x?platform=app",
),
),
);
},
style: ElevatedButton.styleFrom(
padding: EdgeInsets.symmetric(horizontal: 8),
minimumSize: Size(double.infinity, 48),
side: const BorderSide(color: kBorder),
foregroundColor: _theme.colorScheme.onPrimaryContainer,
),
label: Text(
lang.S.of(context).loginX,
textAlign: TextAlign.center,
style: _theme.textTheme.titleMedium?.copyWith(
fontWeight: FontWeight.w600,
),
),
icon: Container(
height: 26,
width: 26,
decoration: BoxDecoration(
shape: BoxShape.circle,
image:
DecorationImage(fit: BoxFit.cover, image: AssetImage('images/x.png'))),
),
),
),
// Google
Expanded(
child: OutlinedButton.icon(
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => WebViewLogin(
loginUrl: "${APIConfig.domain}login/google?platform=app",
),
),
);
},
style: ElevatedButton.styleFrom(
padding: EdgeInsets.symmetric(
horizontal: 8,
),
minimumSize: Size(double.infinity, 48),
side: const BorderSide(color: kBorder),
foregroundColor: _theme.colorScheme.onPrimaryContainer,
),
label: Text(
lang.S.of(context).loginGoogle,
textAlign: TextAlign.center,
style: _theme.textTheme.titleMedium?.copyWith(
color: Colors.black,
fontWeight: FontWeight.w600,
),
),
icon: SvgPicture.asset(
'assets/google.svg',
width: 26,
),
),
),
],
),
],
);
} else {
return SizedBox.shrink();
}
}, error: (e, stack) {
return Center(
child: Text(e.toString()),
);
}, loading: () {
return Center(
child: Padding(
padding: const EdgeInsets.all(20.0),
child: CircularProgressIndicator(),
),
);
}),
],
),
),
),
),
);
},
),
);
}
}

View File

@@ -0,0 +1,109 @@
import 'package:flutter/material.dart';
import 'package:webview_flutter/webview_flutter.dart';
import 'package:flutter_easyloading/flutter_easyloading.dart';
import 'package:mobile_pos/generated/l10n.dart' as l;
import '../../../../Repository/constant_functions.dart'; // Adjust path accordingly
import '../../../../currency.dart'; // Adjust path accordingly
import '../../Home/home.dart';
import '../profile_setup_screen.dart'; // Adjust path accordingly
class WebViewLogin extends StatefulWidget {
final String loginUrl;
const WebViewLogin({super.key, required this.loginUrl});
@override
_WebViewLoginState createState() => _WebViewLoginState();
}
class _WebViewLoginState extends State<WebViewLogin> {
@override
void initState() {
super.initState();
EasyLoading.show(status: l.S.of(context).loading);
}
void _handleRedirect(String url) async {
if (url.contains('/app-login-or-signup')) {
final uri = Uri.parse(url);
final queryParams = uri.queryParameters;
final token = queryParams['token'];
final isSetup = queryParams['is_setup'] == '1';
final status = queryParams['status'];
final currency = queryParams['currency'] ?? queryParams['currency_id'];
if (status == 'success' && token != null) {
await saveUserData(token: token); // Save token
if (currency != null) {
try {
await CurrencyMethods().saveCurrencyDataInLocalDatabase(
selectedCurrencySymbol: currency,
selectedCurrencyName: currency,
);
} catch (e) {
print('Error saving currency: $e');
}
}
if (mounted) {
if (isSetup) {
Navigator.pushReplacement(
context,
MaterialPageRoute(builder: (_) => const Home()),
);
} else {
Navigator.pushReplacement(
context,
MaterialPageRoute(builder: (_) => const ProfileSetup()),
);
}
}
} else {
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
content: Text(l.S.of(context).loginFailedPleaseTryAgain),
));
Navigator.pop(context); // Close WebView
}
}
}
}
@override
Widget build(BuildContext context) {
return SafeArea(
child: Scaffold(
body: WebViewWidget(
controller: WebViewController()
..setJavaScriptMode(JavaScriptMode.unrestricted)
..setBackgroundColor(const Color(0x00000000))
..setNavigationDelegate(
NavigationDelegate(
// Intercept all navigation requests and load within WebView
onNavigationRequest: (request) {
return NavigationDecision.navigate;
},
onPageFinished: (url) {
EasyLoading.dismiss();
},
onPageStarted: (url) {
_handleRedirect(url);
},
onWebResourceError: (error) {
EasyLoading.dismiss();
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(l.S.of(context).someThingWithWrongWithTheWebPage)),
);
},
),
)
// Set user agent to mimic a browser
..setUserAgent(
'Mozilla/5.0 (Linux; Android 10; Mobile) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.120 Mobile Safari/537.36')
..loadRequest(Uri.parse(widget.loginUrl)),
),
),
);
}
}

View File

@@ -0,0 +1,118 @@
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:flutter_easyloading/flutter_easyloading.dart';
import 'package:http/http.dart' as http;
import '../../../../Const/api_config.dart';
import '../../../../Repository/constant_functions.dart';
import '../../../../currency.dart';
class SignUpRepo {
Future<dynamic> signUp({required String name, required String email, required String password, required BuildContext context}) async {
final url = Uri.parse('${APIConfig.url}/sign-up');
final body = {
'name': name,
'email': email,
'password': password,
};
final headers = {
'Accept': 'application/json',
};
try {
final response = await http.post(url, headers: headers, body: body);
final responseData = jsonDecode(response.body);
EasyLoading.dismiss();
if (response.statusCode == 200) {
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(responseData['message'])));
final token = responseData['token'];
if (token != null) {
await saveUserData(token: token);
return responseData['token'];
}
return true;
} else {
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(responseData['message'])));
}
} catch (error) {
ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('Network error: Please try again')));
} finally {}
return false;
}
Future<bool> verifyOTP({required String email, required String otp, required BuildContext context}) async {
final url = Uri.parse('${APIConfig.url}/submit-otp');
final body = {
'email': email,
'otp': otp,
};
final headers = {
'Accept': 'application/json',
};
try {
final response = await http.post(url, headers: headers, body: body);
final responseData = jsonDecode(response.body);
EasyLoading.dismiss();
if (response.statusCode == 200) {
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(responseData['message'])));
String? token = responseData['token'];
if (responseData['currency'] != null) {
await CurrencyMethods()
.saveCurrencyDataInLocalDatabase(selectedCurrencySymbol: responseData['currency']['symbol'], selectedCurrencyName: responseData['currency']['name']);
}
if (token != null) {
await saveUserData(token: responseData['token']);
}
return true;
} else {
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(responseData['error'])));
}
} catch (error) {
print('Error: $error');
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text('Error: $error')));
// ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('Server error: Please try again')));
}
return false;
}
Future<bool> resendOTP({required String email, required BuildContext context}) async {
final url = Uri.parse('${APIConfig.url}/resend-otp');
final body = {
'email': email,
};
final headers = {
'Accept': 'application/json',
};
try {
final response = await http.post(url, headers: headers, body: body);
final responseData = jsonDecode(response.body);
EasyLoading.dismiss();
if (response.statusCode == 200) {
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(responseData['message'])));
return true;
} else {
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(responseData['error'])));
}
} catch (error) {
ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('Network error: Please try again')));
} finally {}
return false;
}
}

View File

@@ -0,0 +1,260 @@
import 'package:flutter/material.dart';
import 'package:flutter_easyloading/flutter_easyloading.dart';
import 'package:flutter_feather_icons/flutter_feather_icons.dart';
import 'package:mobile_pos/Screens/Authentication/Sign%20Up/repo/sign_up_repo.dart';
import 'package:mobile_pos/Screens/Authentication/Sign%20Up/verify_email.dart';
import 'package:mobile_pos/generated/l10n.dart' as lang;
import '../../../GlobalComponents/button_global.dart';
import '../../../GlobalComponents/glonal_popup.dart';
import '../../../constant.dart';
import '../Wedgets/check_email_for_otp_popup.dart';
import '../profile_setup_screen.dart';
class SignUpScreen extends StatefulWidget {
const SignUpScreen({Key? key}) : super(key: key);
@override
State<SignUpScreen> createState() => _SignUpScreenState();
}
class _SignUpScreenState extends State<SignUpScreen> {
///__________Variables________________________________
bool showPassword = true;
bool isClicked = false;
///________Key_______________________________________
GlobalKey<FormState> key = GlobalKey<FormState>();
///___________Controllers______________________________
TextEditingController nameTextController = TextEditingController();
TextEditingController passwordTextController = TextEditingController();
TextEditingController emailTextController = TextEditingController();
///________Dispose____________________________________
@override
void dispose() {
super.dispose();
nameTextController.dispose();
passwordTextController.dispose();
emailTextController.dispose();
}
@override
Widget build(BuildContext context) {
TextTheme textTheme = Theme.of(context).textTheme;
final _theme = Theme.of(context);
return GlobalPopup(
child: Scaffold(
backgroundColor: Colors.white,
appBar: AppBar(
backgroundColor: kWhite,
titleSpacing: 16,
centerTitle: true,
surfaceTintColor: kWhite,
title: Text(
lang.S.of(context).signUp,
//'Sign Up',
),
),
body: SingleChildScrollView(
child: Padding(
padding: const EdgeInsets.fromLTRB(16.0, 20.0, 16.0, 0.0),
child: Form(
key: key,
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
const SizedBox(
height: 24,
),
const NameWithLogo(),
const SizedBox(
height: 24,
),
Text(
lang.S.of(context).createAFreeAccount,
//'Create A Free Account',
style: textTheme.titleMedium?.copyWith(fontSize: 24.0, fontWeight: FontWeight.w600),
),
Text(
lang.S.of(context).pleaseEnterYourDetails,
//'Please enter your details',
style: textTheme.bodyMedium?.copyWith(color: kGreyTextColor, fontSize: 16),
),
const SizedBox(height: 24.0),
///____________Name______________________________________________
TextFormField(
controller: nameTextController,
keyboardType: TextInputType.name,
decoration: InputDecoration(
//labelText: 'Full Name',
labelText: lang.S.of(context).fullName,
//hintText: 'Enter your full name',
hintText: lang.S.of(context).enterYourFullName,
),
validator: (value) {
if (value == null || value.isEmpty) {
//return 'name can\'n be empty';
return lang.S.of(context).nameCanNotBeEmpty;
}
return null;
},
),
const SizedBox(height: 20.0),
///__________Email______________________________________________
TextFormField(
controller: emailTextController,
keyboardType: TextInputType.emailAddress,
decoration: InputDecoration(
// border: OutlineInputBorder(),
// labelText: 'email',
labelText: lang.S.of(context).lableEmail,
//hintText: 'Enter email address',
hintText: lang.S.of(context).hintEmail,
),
validator: (value) {
if (value == null || value.isEmpty) {
//return 'Email can\'n be empty';
return lang.S.of(context).emailCannotBeEmpty;
} else if (!value.contains('@')) {
//return 'Please enter a valid email';
return lang.S.of(context).pleaseEnterAValidEmail;
}
return null;
},
),
const SizedBox(height: 20.0),
///___________Password_____________________________________________
TextFormField(
controller: passwordTextController,
keyboardType: TextInputType.text,
obscureText: showPassword,
decoration: InputDecoration(
//labelText: 'Password',
labelText: lang.S.of(context).lablePassword,
// hintText: 'Enter password',
hintText: lang.S.of(context).hintPassword,
suffixIcon: IconButton(
onPressed: () {
setState(() {
showPassword = !showPassword;
});
},
icon: Icon(
showPassword ? FeatherIcons.eyeOff : FeatherIcons.eye,
color: kGreyTextColor,
size: 18,
),
),
),
validator: (value) {
if (value == null || value.isEmpty) {
//return 'Password can\'t be empty';
return lang.S.of(context).passwordCannotBeEmpty;
} else if (value.length < 6) {
//return 'Please enter a bigger password';
return lang.S.of(context).pleaseEnterABiggerPassword;
}
return null;
},
),
const SizedBox(height: 24.0),
///________Button___________________________________________________
ElevatedButton(
style: OutlinedButton.styleFrom(
maximumSize: const Size(double.infinity, 48),
minimumSize: const Size(double.infinity, 48),
disabledBackgroundColor: _theme.colorScheme.primary.withValues(alpha: 0.15),
),
onPressed: () async {
if (isClicked) {
return;
}
if (key.currentState?.validate() ?? false) {
isClicked = true;
EasyLoading.show();
SignUpRepo repo = SignUpRepo();
final result = await repo.signUp(name: nameTextController.text, email: emailTextController.text, password: passwordTextController.text, context: context);
if (result is bool && result) {
if (result) {
if (await checkEmailForCodePupUp(email: emailTextController.text, context: context, textTheme: textTheme)) {
Navigator.pushReplacement(
context,
MaterialPageRoute(
builder: (context) => VerifyEmail(
email: emailTextController.text,
isFormForgotPass: false,
),
),
);
}
} else {
isClicked = false;
}
} else if (result is String) {
Navigator.pushReplacement(
context,
MaterialPageRoute(
builder: (context) => const ProfileSetup(),
),
);
} else {
isClicked = false;
}
}
},
child: Text(
lang.S.of(context).signUp,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: _theme.textTheme.bodyMedium?.copyWith(
color: _theme.colorScheme.primaryContainer,
fontWeight: FontWeight.w600,
fontSize: 16,
),
),
),
const SizedBox(
height: 20,
),
Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: [
InkWell(
highlightColor: kMainColor.withOpacity(0.1),
borderRadius: BorderRadius.circular(3.0),
onTap: () => Navigator.pop(context),
hoverColor: kMainColor.withOpacity(0.1),
child: RichText(
text: TextSpan(
text: lang.S.of(context).alreadyHaveAnAccount,
//'Already have an account? ',
style: textTheme.bodyMedium?.copyWith(color: kGreyTextColor),
children: [
TextSpan(
text: lang.S.of(context).signIn,
//'Sign In',
style: textTheme.bodyMedium?.copyWith(color: kMainColor, fontWeight: FontWeight.bold),
)
],
),
),
),
],
)
],
),
),
),
),
),
);
}
}

View File

@@ -0,0 +1,251 @@
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter_easyloading/flutter_easyloading.dart';
import 'package:mobile_pos/Screens/Authentication/Sign%20Up/repo/sign_up_repo.dart';
import 'package:mobile_pos/constant.dart';
import 'package:mobile_pos/generated/l10n.dart' as lang;
import 'package:pinput/pinput.dart' as p;
import '../../../GlobalComponents/glonal_popup.dart';
import '../Repo/otp_settings_repo.dart';
import '../forgot password/repo/forgot_pass_repo.dart';
import '../forgot password/set_new_password.dart';
import '../profile_setup_screen.dart';
class VerifyEmail extends StatefulWidget {
const VerifyEmail({super.key, required this.email, required this.isFormForgotPass});
final String email;
final bool isFormForgotPass;
@override
State<VerifyEmail> createState() => _VerifyEmailNewState();
}
class _VerifyEmailNewState extends State<VerifyEmail> {
bool isClicked = false;
Timer? _timer;
int _start = 180; // default fallback
bool _isButtonEnabled = false;
final pinController = TextEditingController();
final focusNode = FocusNode();
final _pinputKey = GlobalKey<FormState>();
@override
void initState() {
super.initState();
_loadOtpSettings();
}
Future<void> _loadOtpSettings() async {
EasyLoading.show(status: lang.S.of(context).loadingOtpSetting);
final settings = await OtpSettingsRepo().fetchOtpSettings();
print(settings?.otpExpirationTime);
EasyLoading.dismiss();
if (settings != null) {
int durationInSec = int.parse(settings.otpExpirationTime);
if (settings.otpDurationType.toLowerCase().contains("minute")) {
durationInSec *= 60;
} else if (settings.otpDurationType.toLowerCase().contains("hour")) {
durationInSec *= 3600;
}
setState(() {
_start = durationInSec;
});
}
startTimer();
}
void startTimer() {
_isButtonEnabled = false;
_timer?.cancel();
_timer = Timer.periodic(const Duration(seconds: 1), (timer) {
setState(() {
if (_start > 0) {
_start--;
} else {
_isButtonEnabled = true;
_timer?.cancel();
}
});
});
}
@override
void dispose() {
pinController.dispose();
focusNode.dispose();
_timer?.cancel();
super.dispose();
}
static const focusedBorderColor = kMainColor;
static const fillColor = Color(0xFFF3F3F3);
final defaultPinTheme = p.PinTheme(
width: 45,
height: 52,
textStyle: const TextStyle(
fontSize: 20,
color: kTitleColor,
),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(10),
border: Border.all(color: kBorderColor),
),
);
@override
Widget build(BuildContext context) {
TextTheme textTheme = Theme.of(context).textTheme;
return GlobalPopup(
child: Scaffold(
backgroundColor: Colors.white,
appBar: AppBar(
backgroundColor: kWhite,
surfaceTintColor: kWhite,
centerTitle: true,
titleSpacing: 16,
title: Text(lang.S.of(context).verityEmail),
),
body: Padding(
padding: const EdgeInsets.fromLTRB(16.0, 20.0, 16.0, 0.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Text(
lang.S.of(context).verityEmail,
style: textTheme.titleMedium?.copyWith(fontSize: 24.0),
),
const SizedBox(height: 8.0),
RichText(
textAlign: TextAlign.center,
text: TextSpan(
text: lang.S.of(context).digits,
style: textTheme.bodyMedium?.copyWith(color: kGreyTextColor, fontSize: 16),
children: [
TextSpan(
text: widget.email,
style:
textTheme.bodyMedium?.copyWith(color: kTitleColor, fontWeight: FontWeight.bold, fontSize: 16),
)
],
),
),
const SizedBox(height: 24.0),
Form(
key: _pinputKey,
child: p.Pinput(
length: 6,
controller: pinController,
focusNode: focusNode,
defaultPinTheme: defaultPinTheme,
separatorBuilder: (index) => const SizedBox(width: 11),
validator: (value) {
if ((value?.length ?? 0) < 6) {
return lang.S.of(context).enterValidOTP;
}
return null;
},
focusedPinTheme: defaultPinTheme.copyWith(
decoration: defaultPinTheme.decoration!.copyWith(
color: kMainColor.withOpacity(0.1),
border: Border.all(color: focusedBorderColor),
),
),
submittedPinTheme: defaultPinTheme.copyWith(
decoration: defaultPinTheme.decoration!.copyWith(
color: fillColor,
border: Border.all(color: kTitleColor),
),
),
errorPinTheme: defaultPinTheme.copyBorderWith(
border: Border.all(color: Colors.redAccent),
),
),
),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Padding(
padding: const EdgeInsets.only(top: 11, bottom: 11),
child: Text(
_isButtonEnabled
? lang.S.of(context).youCanNowResendYourOtp
: lang.S.of(context).resendOtpSeconds(_start),
),
),
const SizedBox(width: 20),
Visibility(
visible: _isButtonEnabled,
child: TextButton(
onPressed: _isButtonEnabled
? () async {
EasyLoading.show();
SignUpRepo repo = SignUpRepo();
if (await repo.resendOTP(email: widget.email, context: context)) {
_loadOtpSettings();
}
}
: null,
child: Text(
lang.S.of(context).resendOTP,
style: TextStyle(color: kMainColor),
),
),
),
],
),
const SizedBox(height: 24.0),
ElevatedButton(
onPressed: widget.isFormForgotPass
? () async {
if (isClicked) return;
focusNode.unfocus();
if (_pinputKey.currentState?.validate() ?? false) {
isClicked = true;
EasyLoading.show();
ForgotPassRepo repo = ForgotPassRepo();
if (await repo.verifyOTPForgotPass(
email: widget.email, otp: pinController.text, context: context)) {
Navigator.pushReplacement(
context,
MaterialPageRoute(
builder: (context) => SetNewPassword(email: widget.email),
),
);
} else {
isClicked = false;
}
}
}
: () async {
if (isClicked) return;
focusNode.unfocus();
if (_pinputKey.currentState?.validate() ?? false) {
isClicked = true;
EasyLoading.show();
SignUpRepo repo = SignUpRepo();
if (await repo.verifyOTP(email: widget.email, otp: pinController.text, context: context)) {
Navigator.pushReplacement(
context,
MaterialPageRoute(
builder: (context) => const ProfileSetup(),
),
);
} else {
isClicked = false;
}
}
},
child: Text(lang.S.of(context).continueE),
),
],
),
),
),
);
}
}

View File

@@ -0,0 +1,69 @@
import 'dart:ui';
import 'package:flutter/material.dart';
import 'package:mobile_pos/generated/l10n.dart' as lang;
import '../../../constant.dart';
Future<dynamic> checkEmailForCodePupUp({required String email, required BuildContext context, required TextTheme textTheme}) {
return showDialog(
barrierDismissible: false,
context: context,
builder: (BuildContext contextPopUp) {
return WillPopScope(
onWillPop: () async => false,
child: BackdropFilter(
filter: ImageFilter.blur(sigmaX: 4, sigmaY: 4),
child: Dialog(
backgroundColor: kWhite,
surfaceTintColor: kWhite,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16.0),
),
child: Padding(
padding: const EdgeInsets.all(20),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
lang.S.of(context).verifyYourEmail,
// 'Verify Your Email',
style: textTheme.titleMedium?.copyWith(fontSize: 24.0),
),
const SizedBox(height: 10.0),
Text(
lang.S.of(context).weHaveSentAConfirmationEmailTo,
//'We have sent a confirmation email to',
textAlign: TextAlign.center,
style: textTheme.bodyMedium?.copyWith(fontWeight: FontWeight.normal, color: kGreyTextColor, fontSize: 16),
),
Text(
email,
style: textTheme.bodyMedium?.copyWith(fontWeight: FontWeight.bold, fontSize: 16),
),
const SizedBox(height: 16.0),
Text(
lang.S.of(context).folder,
// 'It May be that the mail ended up in your spam folder.',
textAlign: TextAlign.center,
style: textTheme.bodyMedium?.copyWith(fontWeight: FontWeight.normal, color: kGreyTextColor, fontSize: 16),
),
const SizedBox(height: 17.0),
ElevatedButton(
onPressed: () {
Navigator.pop(contextPopUp, true);
},
child: Text(lang.S.of(context).gotIt),
//'Got It !',
),
],
),
),
),
),
);
},
);
}

View File

@@ -0,0 +1,199 @@
import 'package:flutter/material.dart';
import 'package:flutter_easyloading/flutter_easyloading.dart';
import 'package:flutter_feather_icons/flutter_feather_icons.dart';
import 'package:mobile_pos/Screens/Authentication/change%20password/repo/change_pass_repo.dart';
import 'package:mobile_pos/generated/l10n.dart' as lang;
import '../../../GlobalComponents/glonal_popup.dart';
import '../../../constant.dart';
class ChangePasswordScreen extends StatefulWidget {
const ChangePasswordScreen({super.key});
@override
State<ChangePasswordScreen> createState() => _ChangePasswordScreenState();
}
class _ChangePasswordScreenState extends State<ChangePasswordScreen> {
final _formKey = GlobalKey<FormState>();
bool isClicked = false;
final TextEditingController _oldPasswordController = TextEditingController();
final TextEditingController _newPasswordController = TextEditingController();
final TextEditingController _confirmPasswordController = TextEditingController();
bool showOldPassword = true;
bool showPassword = true;
bool showConfirmPassword = true;
@override
void dispose() {
_newPasswordController.dispose();
_confirmPasswordController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
TextTheme textTheme = Theme.of(context).textTheme;
final _lang = lang.S.of(context);
return GlobalPopup(
child: Scaffold(
backgroundColor: Colors.white,
appBar: AppBar(
surfaceTintColor: kWhite,
backgroundColor: kWhite,
centerTitle: true,
titleSpacing: 16,
title: Text(
lang.S.of(context).changePassword,
//'Create New Password',
style: textTheme.titleMedium?.copyWith(fontSize: 18),
),
),
body: SingleChildScrollView(
child: Padding(
padding: const EdgeInsets.fromLTRB(16.0, 20.0, 16.0, 0.0),
child: Form(
key: _formKey,
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
// Text(
// lang.S.of(context).setUpNewPassword,
// // 'Set Up New Password',
// style: textTheme.titleMedium?.copyWith(fontSize: 24.0),
// ),
// const SizedBox(height: 8.0),
// Text(
// lang.S.of(context).resetPassword,
// //'Reset your password to recovery and log in your account',
// style: textTheme.bodyMedium?.copyWith(color: kGreyTextColor, fontSize: 16), textAlign: TextAlign.center,
// ),
// const SizedBox(height: 24.0),
TextFormField(
controller: _oldPasswordController,
keyboardType: TextInputType.text,
obscureText: showOldPassword,
decoration: kInputDecoration.copyWith(
// border: const OutlineInputBorder(),
hintText: '********',
labelText: _lang.oldPassword,
suffixIcon: IconButton(
onPressed: () {
setState(() {
showOldPassword = !showOldPassword;
});
},
icon: Icon(
showOldPassword ? FeatherIcons.eyeOff : FeatherIcons.eye,
color: kGreyTextColor,
),
),
),
validator: (value) {
if (value == null || value.isEmpty) {
return _lang.oldPasswordCanNotBeEmpty;
} else if (value.length < 6) {
//return 'Please enter a bigger password';
return lang.S.of(context).pleaseEnterABiggerPassword;
}
return null;
},
),
const SizedBox(height: 20.0),
TextFormField(
controller: _newPasswordController,
keyboardType: TextInputType.text,
obscureText: showPassword,
decoration: kInputDecoration.copyWith(
// border: const OutlineInputBorder(),
hintText: '********',
//labelText: 'New Password',
labelText: lang.S.of(context).newPassword,
suffixIcon: IconButton(
onPressed: () {
setState(() {
showPassword = !showPassword;
});
},
icon: Icon(
showPassword ? FeatherIcons.eyeOff : FeatherIcons.eye,
color: kGreyTextColor,
),
),
),
validator: (value) {
if (value == null || value.isEmpty) {
//return 'Password can\'t be empty';
return lang.S.of(context).passwordCannotBeEmpty;
} else if (value.length < 6) {
//return 'Please enter a bigger password';
return lang.S.of(context).pleaseEnterABiggerPassword;
}
return null;
},
),
const SizedBox(height: 20.0),
TextFormField(
controller: _confirmPasswordController,
keyboardType: TextInputType.text,
obscureText: showConfirmPassword,
decoration: kInputDecoration.copyWith(
border: const OutlineInputBorder(),
//labelText: 'Confirm Password',
labelText: lang.S.of(context).confirmPassword,
hintText: '********',
suffixIcon: IconButton(
onPressed: () {
setState(() {
showConfirmPassword = !showConfirmPassword;
});
},
icon: Icon(
showConfirmPassword ? FeatherIcons.eyeOff : FeatherIcons.eye,
color: kGreyTextColor,
),
),
),
validator: (value) {
if (value == null || value.isEmpty) {
//return 'Password can\'t be empty';
return lang.S.of(context).passwordCannotBeEmpty;
} else if (value != _newPasswordController.text) {
//return 'Passwords do not match';
return lang.S.of(context).passwordsDoNotMatch;
}
return null;
},
),
const SizedBox(height: 24.0),
ElevatedButton(
onPressed: () async {
if (isClicked) {
return;
}
if (_formKey.currentState?.validate() ?? false) {
isClicked = true;
EasyLoading.show();
ChangePassRepo repo = ChangePassRepo();
if (await repo.changePass(
oldPass: _oldPasswordController.text,
newPass: _confirmPasswordController.text,
context: context)) {
Navigator.pop(context);
} else {
isClicked = false;
}
}
},
child: Text(lang.S.of(context).save),
//'Save',
),
],
),
),
),
),
),
);
}
}

View File

@@ -0,0 +1,43 @@
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:flutter_easyloading/flutter_easyloading.dart';
import 'package:http/http.dart' as http;
import '../../../../Const/api_config.dart';
import '../../../../Repository/constant_functions.dart';
class ChangePassRepo {
Future<bool> changePass({required String oldPass, required String newPass, required BuildContext context}) async {
final url = Uri.parse('${APIConfig.url}/change-password');
final body = {
'current_password': oldPass,
'password': newPass,
};
final headers = {
'Accept': 'application/json',
'Authorization': await getAuthToken(),
};
try {
final response = await http.post(url, headers: headers, body: body);
final responseData = jsonDecode(response.body);
print('ChangePass: $responseData');
EasyLoading.dismiss();
if (response.statusCode == 200) {
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(responseData['message'])));
return true;
} else {
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(responseData['message'])));
}
} catch (error) {
print(error);
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text('Error: $error')));
}
return false;
}
}

View File

@@ -0,0 +1,89 @@
import 'package:flutter/material.dart';
import 'package:mobile_pos/constant.dart';
import 'package:mobile_pos/generated/l10n.dart' as lang;
class CheckEMail extends StatefulWidget {
const CheckEMail({super.key});
@override
// ignore: library_private_types_in_public_api
_CheckEMailState createState() => _CheckEMailState();
}
class _CheckEMailState extends State<CheckEMail> {
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
return SafeArea(
child: Scaffold(
body: Padding(
padding: const EdgeInsets.all(10.0),
child: Column(
children: [
Expanded(
flex: 5,
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: [
const SizedBox(
height: 100.0,
width: 100.0,
child: Image(
image: AssetImage('images/mailbox.png'),
),
),
Text(
lang.S.of(context).gotEmail,
style: theme.textTheme.titleLarge?.copyWith(
fontWeight: FontWeight.w600,
fontSize: 25,
),
),
Container(
padding: const EdgeInsets.all(20.0),
width: MediaQuery.of(context).size.width,
child: Text(
lang.S.of(context).sendEmail,
textAlign: TextAlign.center,
style: theme.textTheme.titleLarge?.copyWith(
fontWeight: FontWeight.w600,
),
),
),
Text(
'example@johndoe.com',
style: theme.textTheme.titleLarge,
),
],
),
),
Expanded(
flex: 1,
child: Column(
children: [
ElevatedButton(
onPressed: null,
child: Text(lang.S.of(context).checkEmail),
),
TextButton(
onPressed: () {
Navigator.pushNamed(context, '/otp');
},
child: Text(
lang.S.of(context).checkEmail,
style: theme.textTheme.bodyMedium?.copyWith(
color: kMainColor,
),
),
),
],
),
)
],
),
),
),
);
}
}

View File

@@ -0,0 +1,140 @@
import 'package:flutter/material.dart';
import 'package:flutter_easyloading/flutter_easyloading.dart';
import 'package:mobile_pos/Screens/Authentication/forgot%20password/repo/forgot_pass_repo.dart';
import 'package:mobile_pos/generated/l10n.dart' as lang;
import '../../../GlobalComponents/glonal_popup.dart';
import '../../../constant.dart';
import '../Sign Up/verify_email.dart';
import '../Wedgets/check_email_for_otp_popup.dart';
class ForgotPassword extends StatefulWidget {
const ForgotPassword({
Key? key,
}) : super(key: key);
@override
State<ForgotPassword> createState() => _ForgotPasswordState();
}
class _ForgotPasswordState extends State<ForgotPassword> {
final _formKey = GlobalKey<FormState>();
bool isClicked = false;
final TextEditingController _emailController = TextEditingController();
@override
void dispose() {
_emailController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
final _theme = Theme.of(context);
TextTheme textTheme = Theme.of(context).textTheme;
return GlobalPopup(
child: Scaffold(
backgroundColor: Colors.white,
appBar: AppBar(
titleSpacing: 16,
backgroundColor: kWhite,
surfaceTintColor: kWhite,
centerTitle: true,
title: Text(
// 'Forgot Password',
lang.S.of(context).forgotPassword,
style: textTheme.titleMedium?.copyWith(fontSize: 18),
),
),
body: Padding(
padding: const EdgeInsets.fromLTRB(16.0, 20.0, 16.0, 0.0),
child: Form(
key: _formKey,
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Text(
// 'Forgot Password',
lang.S.of(context).forgotPassword,
style: textTheme.titleMedium?.copyWith(fontSize: 24.0),
),
const SizedBox(height: 8.0),
Text(
//'Reset password by using your email or phone number',
lang.S.of(context).reset,
style: textTheme.bodyMedium?.copyWith(color: kGreyTextColor, fontSize: 16),
textAlign: TextAlign.center,
),
const SizedBox(height: 24.0),
TextFormField(
controller: _emailController,
keyboardType: TextInputType.emailAddress,
decoration: kInputDecoration.copyWith(
// labelText: 'Email',
labelText: lang.S.of(context).lableEmail,
// hintText: 'Enter email address',
hintText: lang.S.of(context).hintEmail,
),
validator: (value) {
if (value == null || value.isEmpty) {
//return 'Email can\'t be empty';
return lang.S.of(context).emailCannotBeEmpty;
} else if (!value.contains('@')) {
// return 'Please enter a valid email';
return lang.S.of(context).pleaseEnterAValidEmail;
}
return null;
},
),
const SizedBox(height: 24.0),
ElevatedButton(
style: OutlinedButton.styleFrom(
maximumSize: const Size(double.infinity, 48),
minimumSize: const Size(double.infinity, 48),
disabledBackgroundColor: _theme.colorScheme.primary.withValues(alpha: 0.15),
),
onPressed: () async {
if (isClicked) {
return;
}
if (_formKey.currentState?.validate() ?? false) {
isClicked = true;
EasyLoading.show();
ForgotPassRepo repo = ForgotPassRepo();
if (await repo.forgotPass(email: _emailController.text, context: context)) {
if (await checkEmailForCodePupUp(
email: _emailController.text, context: context, textTheme: textTheme)) {
Navigator.pushReplacement(
context,
MaterialPageRoute(
builder: (context) => VerifyEmail(
email: _emailController.text,
isFormForgotPass: true,
),
),
);
}
} else {
isClicked = false;
}
}
},
child: Text(
lang.S.of(context).continueE,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: _theme.textTheme.bodyMedium?.copyWith(
color: _theme.colorScheme.primaryContainer,
fontWeight: FontWeight.w600,
fontSize: 16,
),
),
),
],
),
),
),
),
);
}
}

View File

@@ -0,0 +1,102 @@
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:flutter_easyloading/flutter_easyloading.dart';
import 'package:http/http.dart' as http;
import '../../../../Const/api_config.dart';
class ForgotPassRepo {
Future<bool> forgotPass({required String email, required BuildContext context}) async {
final url = Uri.parse('${APIConfig.url}/send-reset-code');
final body = {
'email': email,
};
final headers = {
'Accept': 'application/json',
};
try {
final response = await http.post(url, headers: headers, body: body);
final responseData = jsonDecode(response.body);
EasyLoading.dismiss();
if (response.statusCode == 200) {
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(responseData['message'])));
return true;
} else {
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(responseData['message'])));
}
} catch (error) {
print(error);
ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('Network error: Please try again')));
} finally {}
return false;
}
Future<bool> verifyOTPForgotPass({required String email, required String otp, required BuildContext context}) async {
final url = Uri.parse('${APIConfig.url}/verify-reset-code');
final body = {
'email': email,
'code': otp,
};
final headers = {
'Accept': 'application/json',
};
try {
final response = await http.post(url, headers: headers, body: body);
final responseData = jsonDecode(response.body);
print(response.body);
EasyLoading.dismiss();
if (response.statusCode == 200) {
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(responseData['message'])));
return true;
} else {
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(responseData['error'])));
}
} catch (error) {
print(error);
ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('Network error: Please try again')));
} finally {}
return false;
}
Future<bool> resetPass({required String email, required String password, required BuildContext context}) async {
final url = Uri.parse('${APIConfig.url}/password-reset');
final body = {
'email': email,
"password": password,
};
final headers = {
'Accept': 'application/json',
};
try {
final response = await http.post(url, headers: headers, body: body);
final responseData = jsonDecode(response.body);
EasyLoading.dismiss();
if (response.statusCode == 200) {
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(responseData['message'])));
return true;
} else {
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(responseData['message'])));
}
} catch (error) {
print(error);
ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('Network error: Please try again')));
} finally {}
return false;
}
}

View File

@@ -0,0 +1,165 @@
import 'package:flutter/material.dart';
import 'package:flutter_easyloading/flutter_easyloading.dart';
import 'package:flutter_feather_icons/flutter_feather_icons.dart';
import 'package:mobile_pos/Screens/Authentication/forgot%20password/repo/forgot_pass_repo.dart';
import 'package:mobile_pos/generated/l10n.dart' as lang;
import '../../../GlobalComponents/glonal_popup.dart';
import '../../../constant.dart';
class SetNewPassword extends StatefulWidget {
const SetNewPassword({super.key, required this.email});
final String email;
@override
State<SetNewPassword> createState() => _SetNewPasswordState();
}
class _SetNewPasswordState extends State<SetNewPassword> {
final _formKey = GlobalKey<FormState>();
bool isClicked = false;
final TextEditingController _passwordController = TextEditingController();
final TextEditingController _confirmPasswordController = TextEditingController();
bool showPassword = true;
bool showConfirmPassword = true;
@override
void dispose() {
_passwordController.dispose();
_confirmPasswordController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
TextTheme textTheme = Theme.of(context).textTheme;
return GlobalPopup(
child: Scaffold(
backgroundColor: Colors.white,
appBar: AppBar(
surfaceTintColor: kWhite,
backgroundColor: kWhite,
centerTitle: true,
titleSpacing: 16,
title: Text(
lang.S.of(context).createNewPassword,
//'Create New Password',
style: textTheme.titleMedium?.copyWith(fontSize: 18),
),
),
body: SingleChildScrollView(
child: Padding(
padding: const EdgeInsets.fromLTRB(16.0, 20.0, 16.0, 0.0),
child: Form(
key: _formKey,
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Text(
lang.S.of(context).setUpNewPassword,
// 'Set Up New Password',
style: textTheme.titleMedium?.copyWith(fontSize: 24.0),
),
const SizedBox(height: 8.0),
Text(
lang.S.of(context).resetPassword,
//'Reset your password to recovery and log in your account',
style: textTheme.bodyMedium?.copyWith(color: kGreyTextColor, fontSize: 16), textAlign: TextAlign.center,
),
const SizedBox(height: 24.0),
TextFormField(
controller: _passwordController,
keyboardType: TextInputType.text,
obscureText: showPassword,
decoration: kInputDecoration.copyWith(
// border: const OutlineInputBorder(),
hintText: '********',
//labelText: 'New Password',
labelText: lang.S.of(context).newPassword,
suffixIcon: IconButton(
onPressed: () {
setState(() {
showPassword = !showPassword;
});
},
icon: Icon(
showPassword ? FeatherIcons.eyeOff : FeatherIcons.eye,
color: kGreyTextColor,
),
),
),
validator: (value) {
if (value == null || value.isEmpty) {
//return 'Password can\'t be empty';
return lang.S.of(context).passwordCannotBeEmpty;
} else if (value.length < 6) {
//return 'Please enter a bigger password';
return lang.S.of(context).pleaseEnterABiggerPassword;
}
return null;
},
),
const SizedBox(height: 20.0),
TextFormField(
controller: _confirmPasswordController,
keyboardType: TextInputType.text,
obscureText: showConfirmPassword,
decoration: kInputDecoration.copyWith(
border: const OutlineInputBorder(),
//labelText: 'Confirm Password',
labelText: lang.S.of(context).confirmPassword,
hintText: '********',
suffixIcon: IconButton(
onPressed: () {
setState(() {
showConfirmPassword = !showConfirmPassword;
});
},
icon: Icon(
showConfirmPassword ? FeatherIcons.eyeOff : FeatherIcons.eye,
color: kGreyTextColor,
),
),
),
validator: (value) {
if (value == null || value.isEmpty) {
//return 'Password can\'t be empty';
return lang.S.of(context).passwordCannotBeEmpty;
} else if (value != _passwordController.text) {
//return 'Passwords do not match';
return lang.S.of(context).passwordsDoNotMatch;
}
return null;
},
),
const SizedBox(height: 24.0),
ElevatedButton(
onPressed: () async {
if (isClicked) {
return;
}
if (_formKey.currentState?.validate() ?? false) {
isClicked = true;
EasyLoading.show();
ForgotPassRepo repo = ForgotPassRepo();
if (await repo.resetPass(email: widget.email, password: _confirmPasswordController.text, context: context)) {
Navigator.pop(context);
} else {
isClicked = false;
}
}
},
child: Text(lang.S.of(context).save),
//'Save',
),
],
),
),
),
),
),
);
}
}

View File

@@ -0,0 +1,119 @@
import 'package:flutter/material.dart';
import 'package:mobile_pos/generated/l10n.dart' as lang;
import '../../constant.dart';
class ForgotPassword extends StatefulWidget {
const ForgotPassword({Key? key}) : super(key: key);
@override
// ignore: library_private_types_in_public_api
_ForgotPasswordState createState() => _ForgotPasswordState();
}
class _ForgotPasswordState extends State<ForgotPassword> {
bool showProgress = false;
late String email;
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
return SafeArea(
child: Scaffold(
body: Center(
child: SingleChildScrollView(
child: Column(
children: [
Text(
lang.S.of(context).forgotPassword,
style: theme.textTheme.headlineSmall?.copyWith(
fontWeight: FontWeight.w600,
),
),
Padding(
padding: const EdgeInsets.all(20.0),
child: Text(
lang.S.of(context).enterEmail,
maxLines: 2,
overflow: TextOverflow.ellipsis,
textAlign: TextAlign.center,
style: theme.textTheme.titleLarge?.copyWith(
color: kGreyTextColor,
),
),
),
Padding(
padding: const EdgeInsets.all(10.0),
child: TextFormField(
keyboardType: TextInputType.emailAddress,
onChanged: (value) {
setState(() {
email = value;
});
},
decoration: InputDecoration(
labelText: lang.S.of(context).email,
border: const OutlineInputBorder(),
floatingLabelBehavior: FloatingLabelBehavior.always,
hintText: 'example@example.com'),
),
),
ElevatedButton(
onPressed: () {},
// onPressed: () async {
// setState(() {
// showProgress = true;
// });
// try {
// await FirebaseAuth.instance.sendPasswordResetEmail(
// email: email,
// );
// // ScaffoldMessenger.of(context).showSnackBar(
// // const SnackBar(
// // content: Text('Check your Inbox'),
// // duration: Duration(seconds: 3),
// // ),
// // );
// if (!mounted) return;
// const LoginForm(
// isEmailLogin: true,
// ).launch(context);
// } on FirebaseAuthException catch (e) {
// if (e.code == 'user-not-found') {
// ScaffoldMessenger.of(context).showSnackBar(
// const SnackBar(
// content: Text('No user found for that email.'),
// duration: Duration(seconds: 3),
// ),
// );
// } else if (e.code == 'wrong-password') {
// ScaffoldMessenger.of(context).showSnackBar(
// const SnackBar(
// content: Text('Wrong password provided for that user.'),
// duration: Duration(seconds: 3),
// ),
// );
// }
// } catch (e) {
// ScaffoldMessenger.of(context).showSnackBar(
// SnackBar(
// content: Text(e.toString()),
// duration: const Duration(seconds: 3),
// ),
// );
// }
// setState(
// () {
// showProgress = false;
// },
// );
// },
child: Text(lang.S.of(context).sendLink)),
],
),
),
),
),
);
}
}

View File

@@ -0,0 +1,173 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:mobile_pos/Screens/Authentication/register_screen.dart';
import 'package:mobile_pos/generated/l10n.dart' as lang;
import '../../constant.dart';
import 'forgot_password.dart';
class LoginForm extends StatefulWidget {
const LoginForm({Key? key, required this.isEmailLogin}) : super(key: key);
final bool isEmailLogin;
@override
// ignore: library_private_types_in_public_api
_LoginFormState createState() => _LoginFormState();
}
class _LoginFormState extends State<LoginForm> {
bool showPassword = true;
late String email, password;
GlobalKey<FormState> globalKey = GlobalKey<FormState>();
bool validateAndSave() {
final form = globalKey.currentState;
if (form!.validate()) {
form.save();
return true;
}
return false;
}
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
return SafeArea(
child: Scaffold(
body: Consumer(builder: (context, ref, child) {
return Center(
child: SingleChildScrollView(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Image.asset('images/logoandname.png'),
const SizedBox(
height: 30.0,
),
Padding(
padding: const EdgeInsets.all(10.0),
child: Form(
key: globalKey,
child: Column(
children: [
const SizedBox(height: 20),
TextFormField(
keyboardType: TextInputType.emailAddress,
decoration: InputDecoration(
border: const OutlineInputBorder(),
labelText: lang.S.of(context).emailText,
hintText: lang.S.of(context).enterYourEmailAddress,
),
validator: (value) {
if (value == null || value.isEmpty) {
//return 'Email can\'n be empty';
return lang.S.of(context).emailCannotBeEmpty;
} else if (!value.contains('@')) {
//return 'Please enter a valid email';
return lang.S.of(context).pleaseEnterAValidEmail;
}
return null;
},
onSaved: (value) {
// loginProvider.email = value!;
},
),
const SizedBox(height: 20),
TextFormField(
keyboardType: TextInputType.text,
obscureText: showPassword,
decoration: InputDecoration(
border: const OutlineInputBorder(),
labelText: lang.S.of(context).password,
hintText: lang.S.of(context).pleaseEnterAPassword,
suffixIcon: IconButton(
onPressed: () {
setState(() {
showPassword = !showPassword;
});
},
icon: Icon(showPassword ? Icons.visibility_off : Icons.visibility),
),
),
validator: (value) {
if (value == null || value.isEmpty) {
//return 'Password can\'t be empty';
return lang.S.of(context).passwordCannotBeEmpty;
} else if (value.length < 4) {
//return 'Please enter a bigger password';
return lang.S.of(context).pleaseEnterABiggerPassword;
}
return null;
},
onSaved: (value) {
// loginProvider.password = value!;
},
),
],
),
),
),
Visibility(
visible: widget.isEmailLogin,
child: Row(
children: [
const Spacer(),
TextButton(
onPressed: () {
Navigator.push(context, MaterialPageRoute(builder: (context) => const ForgotPassword()));
// const ForgotPassword().launch(context);
},
child: Text(
lang.S.of(context).forgotPassword,
style: theme.textTheme.bodyLarge?.copyWith(
color: kGreyTextColor,
),
),
),
],
),
),
ElevatedButton(
child: Text(lang.S.of(context).logIn),
onPressed: () {
if (validateAndSave()) {
// loginProvider.signIn(context);
}
},
),
Visibility(
visible: widget.isEmailLogin,
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
lang.S.of(context).noAcc,
style: theme.textTheme.bodyLarge?.copyWith(color: kGreyTextColor),
),
TextButton(
onPressed: () {
// Navigator.pushNamed(context, '/signup');
// const RegisterScreen().launch(context);
Navigator.push(context, MaterialPageRoute(builder: (context) => const RegisterScreen()));
},
child: Text(
lang.S.of(context).register,
style: theme.textTheme.titleMedium?.copyWith(
color: kMainColor,
fontWeight: FontWeight.w600,
),
),
),
],
),
),
],
),
),
);
}),
),
);
}
}

View File

@@ -0,0 +1,406 @@
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter_easyloading/flutter_easyloading.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:image_picker/image_picker.dart';
import 'package:mobile_pos/generated/l10n.dart' as lang;
import 'package:nb_utils/nb_utils.dart';
import '../../GlobalComponents/glonal_popup.dart';
import '../../Provider/shop_category_provider.dart';
import '../../Repository/API/business_setup_repo.dart';
import '../../constant.dart';
import '../../model/business_category_model.dart';
import '../../model/lalnguage_model.dart';
class ProfileSetup extends StatefulWidget {
const ProfileSetup({super.key});
@override
State<ProfileSetup> createState() => _ProfileSetupState();
}
class _ProfileSetupState extends State<ProfileSetup> {
@override
void initState() {
// TODO: implement initState
super.initState();
}
// Language? selectedLanguage;
BusinessCategory? selectedBusinessCategory;
List<Language> language = [];
final ImagePicker _picker = ImagePicker();
XFile? pickedImage;
TextEditingController addressController = TextEditingController();
TextEditingController openingBalanceController = TextEditingController();
TextEditingController phoneController = TextEditingController();
TextEditingController nameController = TextEditingController();
TextEditingController vatGstTitleController = TextEditingController();
TextEditingController vatGstNumberController = TextEditingController();
DropdownButton<BusinessCategory> getCategory({required List<BusinessCategory> list}) {
List<DropdownMenuItem<BusinessCategory>> dropDownItems = [];
for (BusinessCategory category in list) {
var item = DropdownMenuItem(
value: category,
child: Text(category.name),
);
dropDownItems.add(item);
}
return DropdownButton(
isExpanded: true,
hint: Text(lang.S.of(context).selectBusinessCategory
//'Select Business Category'
),
items: dropDownItems,
value: selectedBusinessCategory,
onChanged: (value) {
setState(() {
selectedBusinessCategory = value!;
});
},
);
}
final GlobalKey<FormState> _formKey = GlobalKey();
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
final _lang = lang.S.of(context);
return WillPopScope(
onWillPop: () async => false,
child: Consumer(builder: (context, ref, __) {
final businessCategoryList = ref.watch(businessCategoryProvider);
return businessCategoryList.when(data: (categoryList) {
return GlobalPopup(
child: Scaffold(
backgroundColor: kWhite,
appBar: AppBar(
iconTheme: const IconThemeData(color: Colors.black),
title: Text(
lang.S.of(context).setUpProfile,
),
centerTitle: true,
backgroundColor: Colors.white,
elevation: 0.0,
),
bottomNavigationBar: Padding(
padding: const EdgeInsets.all(8.0),
child: ElevatedButton.icon(
iconAlignment: IconAlignment.end,
onPressed: () async {
if (selectedBusinessCategory != null) {
if (_formKey.currentState!.validate()) {
try {
BusinessSetupRepo businessSetupRepo = BusinessSetupRepo();
await businessSetupRepo.businessSetup(
context: context,
name: nameController.text,
phone: phoneController.text,
address: addressController.text.isEmptyOrNull ? null : addressController.text,
categoryId: selectedBusinessCategory!.id.toString(),
image: pickedImage == null ? null : File(pickedImage!.path),
vatGstNumber: vatGstNumberController.text,
vatGstTitle: vatGstTitleController.text,
openingBalance: openingBalanceController.text,
);
} catch (e) {
EasyLoading.dismiss();
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(e.toString())));
}
}
} else {
ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('Select a Business Category')));
}
},
icon: const Icon(
Icons.arrow_forward,
color: Colors.white,
),
label: Text(lang.S.of(context).continueButton),
),
),
body: SingleChildScrollView(
child: Center(
child: Form(
key: _formKey,
child: Column(
children: [
///________Image______________________________
GestureDetector(
onTap: () {
showDialog(
context: context,
builder: (BuildContext context) {
return Dialog(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12.0),
),
// ignore: sized_box_for_whitespace
child: Container(
height: 200.0,
width: MediaQuery.of(context).size.width - 80,
child: Center(
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
GestureDetector(
onTap: () async {
pickedImage = await _picker.pickImage(source: ImageSource.gallery);
setState(() {});
Navigator.pop(context);
},
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Icon(
Icons.photo_library_rounded,
size: 60.0,
color: kMainColor,
),
Text(
lang.S.of(context).gallery,
style: theme.textTheme.titleMedium?.copyWith(
color: kMainColor,
),
),
],
),
),
const SizedBox(width: 40.0),
GestureDetector(
onTap: () async {
pickedImage = await _picker.pickImage(source: ImageSource.camera);
setState(() {});
Navigator.pop(context);
},
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Icon(
Icons.camera,
size: 60.0,
color: kGreyTextColor,
),
Text(
lang.S.of(context).camera,
style: theme.textTheme.titleMedium?.copyWith(color: kGreyTextColor),
),
],
),
),
],
),
),
),
);
});
},
child: Stack(
children: [
Container(
height: 120,
width: 120,
decoration: BoxDecoration(
shape: BoxShape.circle,
// border: Border.all(color: Colors.black54, width: 1),
// borderRadius: const BorderRadius.all(Radius.circular(120)),
image: pickedImage == null
? const DecorationImage(
image: AssetImage('images/noImage.png'),
fit: BoxFit.cover,
)
: DecorationImage(
image: FileImage(File(pickedImage!.path)),
fit: BoxFit.cover,
),
),
),
Positioned(
bottom: 0,
right: 0,
child: Container(
height: 35,
width: 35,
decoration: BoxDecoration(
border: Border.all(color: Colors.white, width: 2),
// borderRadius: const BorderRadius.all(Radius.circular(120)),
shape: BoxShape.circle,
color: kMainColor,
),
child: const Icon(
Icons.camera_alt_outlined,
size: 20,
color: Colors.white,
),
),
)
],
),
),
const SizedBox(height: 20.0),
Padding(
padding: const EdgeInsets.all(10.0),
child: SizedBox(
height: 60.0,
child: FormField(
builder: (FormFieldState<dynamic> field) {
return InputDecorator(
decoration: kInputDecoration.copyWith(
floatingLabelBehavior: FloatingLabelBehavior.always,
labelText: lang.S.of(context).businessCat,
border: OutlineInputBorder(borderRadius: BorderRadius.circular(5.0))),
child: DropdownButtonHideUnderline(child: getCategory(list: categoryList)),
);
},
),
),
),
///_________Name________________________
Padding(
padding: const EdgeInsets.all(10.0),
child: AppTextField(
// Optional
textFieldType: TextFieldType.NAME,
controller: nameController,
validator: (value) {
if (value == null || value.isEmpty) {
// return 'Please enter a valid business name';
return lang.S.of(context).pleaseEnterAValidBusinessName;
}
return null;
},
decoration: kInputDecoration.copyWith(
labelText: lang.S.of(context).businessName,
border: const OutlineInputBorder(),
//hintText: 'Enter Business/Store Name'
hintText: lang.S.of(context).enterBusiness,
),
),
),
///__________Phone_________________________
Padding(
padding: const EdgeInsets.all(10.0),
child: SizedBox(
height: 60.0,
child: AppTextField(
controller: phoneController,
validator: (value) {
return null;
},
textFieldType: TextFieldType.PHONE,
decoration: kInputDecoration.copyWith(
labelText: lang.S.of(context).phone,
hintText: lang.S.of(context).enterYourPhoneNumber,
border: const OutlineInputBorder(),
),
),
),
),
///_________Address___________________________
Padding(
padding: const EdgeInsets.all(10.0),
child: AppTextField(
// ignore: deprecated_member_use
textFieldType: TextFieldType.ADDRESS,
controller: addressController,
decoration: kInputDecoration.copyWith(
focusedBorder: const OutlineInputBorder(
borderSide: BorderSide(color: kGreyTextColor),
),
labelText: lang.S.of(context).companyAddress,
hintText: lang.S.of(context).enterFullAddress,
border: const OutlineInputBorder(),
),
),
),
///________Opening_balance_______________________
Padding(
padding: const EdgeInsets.all(10.0),
child: AppTextField(
validator: (value) {
return null;
},
controller: openingBalanceController, // Optional
textFieldType: TextFieldType.PHONE,
decoration: kInputDecoration.copyWith(
//hintText: 'Enter opening balance',
hintText: lang.S.of(context).enterOpeningBalance,
labelText: lang.S.of(context).openingBalance,
border: const OutlineInputBorder(),
),
),
),
// ///_______Gst_number____________________________
// Row(
// children: [
// ///_______title__________________________________
// Expanded(
// child: Padding(
// padding: const EdgeInsets.only(top: 10, left: 10, bottom: 10),
// child: AppTextField(
// validator: (value) {
// return null;
// },
// controller: vatGstTitleController,
// textFieldType: TextFieldType.NAME,
// decoration: kInputDecoration.copyWith(
// labelText: _lang.vatGstTitle,
// hintText: _lang.enterVatGstTitle,
// border: const OutlineInputBorder(),
// ),
// ),
// ),
// ),
//
// ///______Vat_and_Gst_Number__________________________________
// Expanded(
// child: Padding(
// padding: const EdgeInsets.all(10.0),
// child: AppTextField(
// validator: (value) {
// return null;
// },
// controller: vatGstNumberController, // Optional
// textFieldType: TextFieldType.NAME,
// decoration: kInputDecoration.copyWith(
// hintText: _lang.enterVatGstNumber,
// labelText: _lang.vatGstNumber,
// border: const OutlineInputBorder(),
// ),
// ),
// ),
// ),
// ],
// )
],
),
),
),
),
),
);
}, error: (e, stack) {
return Center(
child: Text(e.toString()),
);
}, loading: () {
return const Center(
child: CircularProgressIndicator(),
);
});
}),
);
}
}

View File

@@ -0,0 +1,221 @@
// ignore_for_file: curly_braces_in_flow_control_structures
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:mobile_pos/Screens/Authentication/Phone%20Auth/phone_auth_screen.dart';
import 'package:mobile_pos/Screens/Authentication/profile_setup_screen.dart';
import 'package:mobile_pos/generated/l10n.dart' as lang;
import 'package:nb_utils/nb_utils.dart';
import '../../Repository/API/register_repo.dart';
import '../../constant.dart';
import 'login_form.dart';
class RegisterScreen extends StatefulWidget {
const RegisterScreen({Key? key}) : super(key: key);
@override
State<RegisterScreen> createState() => _RegisterScreenState();
}
class _RegisterScreenState extends State<RegisterScreen> {
bool showPass1 = true;
bool showPass2 = true;
GlobalKey<FormState> globalKey = GlobalKey<FormState>();
bool passwordShow = false;
String? givenPassword;
String? givenPassword2;
late String email;
late String password;
late String passwordConfirmation;
bool validateAndSave() {
final form = globalKey.currentState;
if (form!.validate() && givenPassword == givenPassword2) {
form.save();
return true;
}
return false;
}
@override
void initState() {
// TODO: implement initState
super.initState();
}
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
return SafeArea(
child: Scaffold(
body: Consumer(builder: (context, ref, child) {
return Center(
child: SingleChildScrollView(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Image.asset('images/logoandname.png'),
const SizedBox(
height: 30.0,
),
Padding(
padding: const EdgeInsets.all(10.0),
child: Form(
key: globalKey,
child: Column(
children: [
const SizedBox(height: 20),
TextFormField(
keyboardType: TextInputType.emailAddress,
decoration: InputDecoration(
border: const OutlineInputBorder(),
labelText: lang.S.of(context).emailText,
hintText: lang.S.of(context).enterYourEmailAddress,
),
validator: (value) {
if (value == null || value.isEmpty) {
// return 'Email can\'n be empty';
return lang.S.of(context).emailCannotBeEmpty;
} else if (!value.contains('@')) {
//return 'Please enter a valid email';
return lang.S.of(context).pleaseEnterAValidEmail;
}
return null;
},
onSaved: (value) {
email = value!;
},
),
const SizedBox(height: 20),
TextFormField(
keyboardType: TextInputType.text,
obscureText: showPass1,
decoration: InputDecoration(
border: const OutlineInputBorder(),
labelText: lang.S.of(context).password,
hintText: lang.S.of(context).pleaseEnterAPassword,
suffixIcon: IconButton(
onPressed: () {
setState(() {
showPass1 = !showPass1;
});
},
icon: Icon(showPass1 ? Icons.visibility_off : Icons.visibility),
),
),
onChanged: (value) {
givenPassword = value;
},
validator: (value) {
if (value == null || value.isEmpty) {
//return 'Password can\'t be empty';
return lang.S.of(context).passwordCannotBeEmpty;
} else if (value.length < 4) {
//return 'Please enter a bigger password';
return lang.S.of(context).pleaseEnterABiggerPassword;
} else if (value.length < 4) {
//return 'Please enter a bigger password';
return lang.S.of(context).pleaseEnterABiggerPassword;
}
return null;
},
onSaved: (value) {
password = value!;
},
),
const SizedBox(height: 20),
TextFormField(
keyboardType: TextInputType.emailAddress,
obscureText: showPass2,
decoration: InputDecoration(
border: const OutlineInputBorder(),
labelText: lang.S.of(context).confirmPass,
hintText: lang.S.of(context).pleaseEnterAConfirmPassword,
suffixIcon: IconButton(
onPressed: () {
setState(() {
showPass2 = !showPass2;
});
},
icon: Icon(showPass2 ? Icons.visibility_off : Icons.visibility),
),
),
onChanged: (value) {
givenPassword2 = value;
},
validator: (value) {
if (value == null || value.isEmpty) {
//return 'Password can\'t be empty';
return lang.S.of(context).passwordCannotBeEmpty;
} else if (value.length < 4) {
// return 'Please enter a bigger password';
return lang.S.of(context).pleaseEnterABiggerPassword;
} else if (givenPassword != givenPassword2) {
//return 'Password Not mach';
return lang.S.of(context).passwordsDoNotMatch;
}
return null;
},
),
],
),
),
),
ElevatedButton(
onPressed: () async {
if (validateAndSave()) {
RegisterRepo reg = RegisterRepo();
if (await reg.registerRepo(email: email, context: context, password: password, confirmPassword: password))
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const ProfileSetup(),
));
// auth.signUp(context);
}
},
child: Text(lang.S.of(context).register),
),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
lang.S.of(context).haveAcc,
style: theme.textTheme.bodyLarge?.copyWith(
color: kMainColor,
),
),
TextButton(
onPressed: () {
const LoginForm(
isEmailLogin: true,
).launch(context);
// Navigator.pushNamed(context, '/loginForm');
},
child: Text(
lang.S.of(context).logIn,
style: theme.textTheme.titleSmall?.copyWith(
color: kMainColor,
fontWeight: FontWeight.w600,
),
),
),
],
),
TextButton(
onPressed: () {
const PhoneAuth().launch(context);
},
child: Text(lang.S.of(context).loginWithPhone),
),
],
),
),
);
}),
),
);
}
}

View File

@@ -0,0 +1,79 @@
// ignore_for_file: unused_result
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:mobile_pos/Provider/profile_provider.dart';
import 'package:mobile_pos/generated/l10n.dart' as lang;
import 'package:nb_utils/nb_utils.dart';
import '../../GlobalComponents/glonal_popup.dart';
import '../../constant.dart';
import '../Home/home.dart';
class SuccessScreen extends StatelessWidget {
const SuccessScreen({Key? key, required this.email}) : super(key: key);
final String? email;
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
return Consumer(builder: (context, ref, _) {
final userRoleData = ref.watch(businessInfoProvider);
ref.watch(getExpireDateProvider(ref));
return userRoleData.when(data: (data) {
return GlobalPopup(
child: Scaffold(
backgroundColor: kWhite,
resizeToAvoidBottomInset: true,
body: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Image(image: AssetImage('images/success.png')),
const SizedBox(height: 40.0),
Text(
lang.S.of(context).congratulation,
),
Padding(
padding: const EdgeInsets.all(20.0),
child: Text(
lang.S.of(context).loremIpsumDolorSitAmetConsecteturElitInterdumCons,
maxLines: 2,
overflow: TextOverflow.ellipsis,
textAlign: TextAlign.center,
style: theme.textTheme.bodyLarge?.copyWith(
fontSize: 16.0,
),
),
),
Padding(
padding: const EdgeInsets.all(8.0),
child: ElevatedButton(
onPressed: () {
const Home().launch(context);
// Navigator.pushNamed(context, '/home');
},
child: Text(lang.S.of(context).continueButton),
),
)
],
// ),
// bottomNavigationBar: ButtonGlobalWithoutIcon(
// buttontext: lang.S.of(context).continueButton,
// buttonDecoration: kButtonDecoration.copyWith(color: kMainColor),
// onPressed: () {
// const Home().launch(context);
// // Navigator.pushNamed(context, '/home');
// },
// buttonTextColor: Colors.white,
// ),
)),
);
}, error: (e, stack) {
return Text(e.toString());
}, loading: () {
return const Center(child: CircularProgressIndicator());
});
});
}
}