From 1d65617d18d699a29095000c59b06529f2be5257 Mon Sep 17 00:00:00 2001 From: mohammad Date: Wed, 31 Jul 2024 16:02:05 +0300 Subject: [PATCH] auth UI and Api --- lib/main.dart | 34 ++- lib/pages/auth/bloc/auth_bloc.dart | 146 +++++++------ lib/pages/auth/bloc/auth_state.dart | 13 ++ .../auth/view/forget_password_web_page.dart | 13 +- lib/pages/auth/view/login_web_page.dart | 205 +++++++++--------- lib/services/auth_api.dart | 18 +- lib/utils/constants/api_const.dart | 1 + lib/utils/constants/strings_manager.dart | 43 ++++ .../helpers/shared_preferences_helper.dart | 58 +++++ macos/Flutter/GeneratedPluginRegistrant.swift | 2 + pubspec.lock | 68 +++++- pubspec.yaml | 2 +- test/widget_test.dart | 30 --- 13 files changed, 405 insertions(+), 228 deletions(-) create mode 100644 lib/utils/constants/strings_manager.dart create mode 100644 lib/utils/helpers/shared_preferences_helper.dart diff --git a/lib/main.dart b/lib/main.dart index 6fbc2cb1..9be8396a 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,32 +1,46 @@ import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; +import 'package:syncrow_web/pages/auth/bloc/auth_bloc.dart'; import 'package:syncrow_web/pages/auth/view/login_page.dart'; +import 'package:syncrow_web/pages/home/view/home_page.dart'; import 'package:syncrow_web/services/locator.dart'; -void main() { + +Future main() async { WidgetsFlutterBinding.ensureInitialized(); - initialSetup(); - runApp(const MyApp()); + initialSetup(); // Perform initial setup, e.g., dependency injection + String res = await AuthBloc.getTokenAndValidate(); + runApp(MyApp( + isLoggedIn: res, + )); } class MyApp extends StatelessWidget { - const MyApp({super.key}); + final dynamic isLoggedIn; + const MyApp({ + super.key, + required this.isLoggedIn, + }); @override Widget build(BuildContext context) { return MaterialApp( - debugShowCheckedModeBanner: false, - scrollBehavior: const MaterialScrollBehavior().copyWith( + debugShowCheckedModeBanner: false, // Hide debug banner + scrollBehavior: const MaterialScrollBehavior().copyWith( dragDevices: { PointerDeviceKind.mouse, PointerDeviceKind.touch, PointerDeviceKind.stylus, - PointerDeviceKind.unknown + PointerDeviceKind.unknown, }, ), theme: ThemeData( - colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple), - useMaterial3: true, + colorScheme: ColorScheme.fromSeed( + seedColor: Colors.deepPurple), // Set up color scheme + useMaterial3: true, // Enable Material 3 ), - home: const LoginPage(), + home: isLoggedIn == 'Success'? + const HomePage() + : + const LoginPage(), ); } } diff --git a/lib/pages/auth/bloc/auth_bloc.dart b/lib/pages/auth/bloc/auth_bloc.dart index f6931e7b..35f47415 100644 --- a/lib/pages/auth/bloc/auth_bloc.dart +++ b/lib/pages/auth/bloc/auth_bloc.dart @@ -9,6 +9,8 @@ import 'package:syncrow_web/pages/auth/model/login_with_email_model.dart'; import 'package:syncrow_web/pages/auth/model/token.dart'; import 'package:syncrow_web/pages/auth/model/user_model.dart'; import 'package:syncrow_web/services/auth_api.dart'; +import 'package:syncrow_web/utils/constants/strings_manager.dart'; +import 'package:syncrow_web/utils/helpers/shared_preferences_helper.dart'; import 'package:syncrow_web/utils/snack_bar.dart'; class AuthBloc extends Bloc { @@ -20,16 +22,16 @@ class AuthBloc extends Bloc { on(_onStopTimer); on(_onUpdateTimer); on(_passwordVisible); - } + + ////////////////////////////// forget password ////////////////////////////////// final TextEditingController forgetEmailController = TextEditingController(); final TextEditingController forgetPasswordController = TextEditingController(); final TextEditingController forgetOtp = TextEditingController(); final forgetFormKey = GlobalKey(); - Timer? _timer; int _remainingTime = 0; @@ -53,7 +55,8 @@ class AuthBloc extends Bloc { add(const UpdateTimerEvent(remainingTime: 0, isButtonEnabled: true)); print("Timer finished"); // Debug print } else { - add(UpdateTimerEvent(remainingTime: _remainingTime, isButtonEnabled: false)); + add(UpdateTimerEvent( + remainingTime: _remainingTime, isButtonEnabled: false)); } }); } @@ -67,40 +70,49 @@ class AuthBloc extends Bloc { ChangePasswordEvent event, Emitter emit) async { try { emit(LoadingForgetState()); - await AuthenticationAPI.forgetPassword( - password: forgetPasswordController.text, email: forgetEmailController.text); + bool response = await AuthenticationAPI.verifyOtp( + email: forgetEmailController.text, otpCode: forgetOtp.text); + if (response == true) { + await AuthenticationAPI.forgetPassword( + password: forgetPasswordController.text, + email: forgetEmailController.text); + _timer?.cancel(); + emit(const TimerState(isButtonEnabled: true, remainingTime: 0)); + } emit(SuccessForgetState()); } catch (failure) { - print(failure); emit(FailureForgetState(error: failure.toString())); } } - void _onUpdateTimer(UpdateTimerEvent event, Emitter emit) { emit(TimerState( isButtonEnabled: event.isButtonEnabled, remainingTime: event.remainingTime)); } + + + + + + ///////////////////////////////////// login ///////////////////////////////////// final TextEditingController loginEmailController = TextEditingController(); final TextEditingController loginPasswordController = TextEditingController(); final loginFormKey = GlobalKey(); bool isChecked = false; - bool obscureText = false; - + bool obscureText = true; String newPassword = ''; String maskedEmail = ''; String otpCode = ''; - static Token token = Token.emptyConstructor(); static UserModel? user; bool showValidationMessage = false; - void _login(LoginButtonPressed event, Emitter emit) async { + void _login(LoginButtonPressed event, Emitter emit) async { emit(LoginLoading()); - if(isChecked) { + if (isChecked) { try { if (event.username.isEmpty || event.password.isEmpty) { CustomSnackBar.displaySnackBar('Please enter your credentials'); @@ -113,8 +125,7 @@ class AuthBloc extends Bloc { password: event.password, ), ); - } - catch (failure) { + } catch (failure) { emit(const LoginFailure(error: 'Something went wrong')); // emit(LoginFailure(error: failure.toString())); return; @@ -124,11 +135,9 @@ class AuthBloc extends Bloc { FlutterSecureStorage storage = const FlutterSecureStorage(); await storage.write( key: Token.loginAccessTokenKey, value: token.accessToken); - const FlutterSecureStorage().write( key: UserModel.userUuidKey, - value: Token.decodeToken(token.accessToken)['uuid'].toString() - ); + value: Token.decodeToken(token.accessToken)['uuid'].toString()); user = UserModel.fromToken(token); loginEmailController.clear(); loginPasswordController.clear(); @@ -136,18 +145,24 @@ class AuthBloc extends Bloc { } else { emit(const LoginFailure(error: 'Something went wrong')); } - } - else{ + } else { emit(const LoginFailure(error: 'Accept terms and condition')); } } - checkBoxToggle(CheckBoxEvent event, Emitter emit,){ + checkBoxToggle(CheckBoxEvent event, Emitter emit,) { emit(LoginLoading()); isChecked = event.newValue!; emit(LoginInitial()); } + checkOtpCode(ChangePasswordEvent event, Emitter emit,) async { + emit(LoadingForgetState()); + await AuthenticationAPI.verifyOtp( + email: forgetEmailController.text, otpCode: forgetOtp.text); + emit(SuccessForgetState()); + } + void _passwordVisible(PasswordVisibleEvent event, Emitter emit) { emit(LoginLoading()); obscureText = !event.newValue!; @@ -161,8 +176,8 @@ class AuthBloc extends Bloc { } - /////////////////////////////////////VALIDATORS///////////////////////////////////// + /////////////////////////////////////VALIDATORS///////////////////////////////////// String? validatePassword(String? value) { if (value == null || value.isEmpty) { return 'Password is required'; @@ -192,25 +207,23 @@ class AuthBloc extends Bloc { emit(LoadingForgetState()); final nameError = validateEmail(forgetEmailController.text); if (nameError != null) { - emit(FailureForgetState(error:nameError )) ; - // CustomSnackBar.displaySnackBar(nameError); + emit(FailureForgetState(error: nameError)); return true; } return false; } String? validateRegion(String? value) { - if (value == null || value.isEmpty) { - return 'Please select a region'; - } - return null; + if (value == null || value.isEmpty) { + return 'Please select a region'; + } + return null; } String? passwordValidator(String? value) { if (value == null || value.isEmpty) { return 'Please enter your password'; } - List validationErrors = []; if (!RegExp(r'^(?=.*[a-z])').hasMatch(value)) { @@ -230,73 +243,30 @@ class AuthBloc extends Bloc { } if (validationErrors.isNotEmpty) { - return 'Password must contain at least:\n' + validationErrors.join('\n'); + return 'Password must contain at least:\n${validationErrors.join('\n')}'; } return null; } - String? fullNameValidator(String? value) { if (value == null) return 'Full name is required'; - final withoutExtraSpaces = value.replaceAll(RegExp(r"\s+"), ' ').trim(); - if (withoutExtraSpaces.length < 2 || withoutExtraSpaces.length > 30) { return 'Full name must be between 2 and 30 characters long'; } - - // Test if it contains anything but alphanumeric spaces and single quote - if (RegExp(r"/[^ a-zA-Z0-9-\']/").hasMatch(withoutExtraSpaces)) { return 'Only alphanumeric characters, space, dash and single quote are allowed'; } - final parts = withoutExtraSpaces.split(' '); - if (parts.length < 2) return 'Full name must contain first and last names'; - if (parts.length > 3) return 'Full name can at most contain 3 parts'; - if (parts.any((part) => part.length < 2 || part.length > 30)) { return 'Full name parts must be between 2 and 30 characters long'; } return null; } - - - String? reEnterPasswordCheckForgetPass(String? value) { - passwordValidator(value); - if (newPassword == value) { - return null; - } else { - return 'Passwords do not match'; - } - } - - String? emailAddressValidator(String? value) { - if (value != null && value.isNotEmpty && value != "") { - if (checkValidityOfEmail(value)) { - return null; - } else { - return 'Please enter a valid email'; - } - } else { - return 'Email address is required'; - } - } - - bool checkValidityOfEmail(String? email) { - if (email != null) { - return RegExp( - r"^[a-zA-Z0-9]+([.!#$%&'*+/=?^_`{|}~-]?[a-zA-Z0-9]+)*@[a-zA-Z0-9]+([.-]?[a-zA-Z0-9]+)*\.[a-zA-Z0-9]{2,}$") - .hasMatch(email); - } else { - return false; - } - } - String maskEmail(String email) { final emailParts = email.split('@'); if (emailParts.length != 2) return email; @@ -324,4 +294,36 @@ class AuthBloc extends Bloc { ]; + static Future getTokenAndValidate() async { + try { + const storage = FlutterSecureStorage(); + final firstLaunch = await SharedPreferencesHelper.readBoolFromSP(StringsManager.firstLaunch) ?? true; + if (firstLaunch) { + storage.deleteAll(); + } + await SharedPreferencesHelper.saveBoolToSP(StringsManager.firstLaunch, false); + final value = await storage.read(key: Token.loginAccessTokenKey) ?? ''; + if (value.isEmpty) { + return 'Token not found'; + } + final tokenData = Token.decodeToken(value); + if (tokenData.containsKey('exp')) { + final exp = tokenData['exp'] ?? 0; + final currentTime = DateTime.now().millisecondsSinceEpoch ~/ 1000; + if (currentTime < exp) { + return 'Success'; + } else { + return 'expired'; + } + } else { + return 'Something went wrong'; + } + } catch (_) { + return 'Something went wrong'; + } + } + } + + + diff --git a/lib/pages/auth/bloc/auth_state.dart b/lib/pages/auth/bloc/auth_state.dart index d8e15d5b..4620c722 100644 --- a/lib/pages/auth/bloc/auth_state.dart +++ b/lib/pages/auth/bloc/auth_state.dart @@ -9,6 +9,8 @@ abstract class AuthState extends Equatable { class LoginInitial extends AuthState {} +class AuthTokenLoading extends AuthState {} + class LoginLoading extends AuthState {} class LoginSuccess extends AuthState {} @@ -38,6 +40,7 @@ class InitialForgetState extends AuthState{} class LoadingForgetState extends AuthState{} class SuccessForgetState extends AuthState{} + class PasswordVisibleState extends AuthState{} class FailureForgetState extends AuthState { @@ -58,8 +61,18 @@ class TimerState extends AuthState { } +class AuthError extends AuthState { + final String message; + String? code; + AuthError({required this.message, this.code}); +} +class AuthTokenError extends AuthError { + AuthTokenError({required super.message, super.code}); +} +class AuthSuccess extends AuthState {} +class AuthTokenSuccess extends AuthSuccess {} // class AuthState extends AuthState {} diff --git a/lib/pages/auth/view/forget_password_web_page.dart b/lib/pages/auth/view/forget_password_web_page.dart index c62541f5..687392c6 100644 --- a/lib/pages/auth/view/forget_password_web_page.dart +++ b/lib/pages/auth/view/forget_password_web_page.dart @@ -9,7 +9,6 @@ import 'package:syncrow_web/pages/common/first_layer.dart'; import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/constants/assets.dart'; import 'package:syncrow_web/utils/style.dart'; - class ForgetPasswordWebPage extends StatelessWidget { const ForgetPasswordWebPage({super.key}); @@ -21,7 +20,11 @@ class ForgetPasswordWebPage extends StatelessWidget { child: BlocConsumer( listener: (context, state) { if (state is LoadingForgetState) { - } else if (state is FailureForgetState) { + + } else if (state is SuccessForgetState){ + Navigator.of(context).pop(); + } + else if (state is FailureForgetState) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text(state.error), @@ -138,10 +141,8 @@ class ForgetPasswordWebPage extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start, children: [ - Text( - "Account", - style: smallTextStyle, - ), + Text("Account", + style: smallTextStyle,), SizedBox( width: MediaQuery.sizeOf(context).width * 0.2, child: TextFormField( diff --git a/lib/pages/auth/view/login_web_page.dart b/lib/pages/auth/view/login_web_page.dart index 81a38322..21457f13 100644 --- a/lib/pages/auth/view/login_web_page.dart +++ b/lib/pages/auth/view/login_web_page.dart @@ -287,108 +287,7 @@ class LoginWebPage extends StatelessWidget { }, ), ), - // Padding( - // padding: const EdgeInsets.all(5.0), - // child: SizedBox( - // width: MediaQuery.sizeOf(context).width * 0.2, - // child: Row( - // mainAxisAlignment: MainAxisAlignment.center, - // crossAxisAlignment: CrossAxisAlignment.center, - // children: [ - // Expanded(child: Image.asset(Assets.liftLine)), - // Expanded( - // child: Padding( - // padding: const EdgeInsets.all(5.0), - // child: Text('Or sign in with', - // style: smallTextStyle.copyWith(fontSize: 10), - // ), - // ) - // ), - // Expanded(child: Image.asset(Assets.rightLine)), - // ], - // ), - // ), - // ), - // SizedBox( - // width: MediaQuery.sizeOf(context).width * 0.2, - // child: Row( - // crossAxisAlignment: CrossAxisAlignment.center, - // children: [ - // Expanded( - // child: Container( - // decoration: containerDecoration, - // child:InkWell( - // child: Padding( - // padding: const EdgeInsets.all(8.0), - // child: Row( - // mainAxisAlignment: MainAxisAlignment.spaceAround, - // crossAxisAlignment: CrossAxisAlignment.center, - // children: [ - // SvgPicture.asset( - // Assets.google, - // fit: BoxFit.cover, - // ), - // const Flexible( - // child: Text('Google', - // style: TextStyle(color: Colors.black), - // ), - // ), - // ], - // ), - // ), - // onTap: () {}, - // ), - // ), - // ), - // SizedBox(width: 10,), - // Expanded( - // child: Container( - // decoration: containerDecoration, - // child:InkWell( - // child: Padding( - // padding: const EdgeInsets.all(8.0), - // child: Row( - // mainAxisAlignment: MainAxisAlignment.spaceAround, - // crossAxisAlignment: CrossAxisAlignment.center, - // children: [ - // SvgPicture.asset( - // Assets.facebook, - // fit: BoxFit.cover, - // ), - // const Flexible( - // child: Text('Facebook', - // style: TextStyle(color: Colors.black), - // ), - // ), - // ], - // ), - // ), - // onTap: () {}, - // ), - // ), - // ), - // - // ], - // ), - // ), - // SizedBox( - // width: MediaQuery.sizeOf(context).width * 0.2, - // child: const Row( - // mainAxisAlignment: MainAxisAlignment.center, - // crossAxisAlignment: CrossAxisAlignment.center, - // children: [ - // Flexible( - // child: Text( - // "Don't you have an account? ", - // style: TextStyle(color: Colors.white), - // )), - // Flexible( - // child: Text( - // "Sign up", - // )), - // ], - // ), - // ) + ], ), ), @@ -403,3 +302,105 @@ class LoginWebPage extends StatelessWidget { ); } } +// Padding( +// padding: const EdgeInsets.all(5.0), +// child: SizedBox( +// width: MediaQuery.sizeOf(context).width * 0.2, +// child: Row( +// mainAxisAlignment: MainAxisAlignment.center, +// crossAxisAlignment: CrossAxisAlignment.center, +// children: [ +// Expanded(child: Image.asset(Assets.liftLine)), +// Expanded( +// child: Padding( +// padding: const EdgeInsets.all(5.0), +// child: Text('Or sign in with', +// style: smallTextStyle.copyWith(fontSize: 10), +// ), +// ) +// ), +// Expanded(child: Image.asset(Assets.rightLine)), +// ], +// ), +// ), +// ), +// SizedBox( +// width: MediaQuery.sizeOf(context).width * 0.2, +// child: Row( +// crossAxisAlignment: CrossAxisAlignment.center, +// children: [ +// Expanded( +// child: Container( +// decoration: containerDecoration, +// child:InkWell( +// child: Padding( +// padding: const EdgeInsets.all(8.0), +// child: Row( +// mainAxisAlignment: MainAxisAlignment.spaceAround, +// crossAxisAlignment: CrossAxisAlignment.center, +// children: [ +// SvgPicture.asset( +// Assets.google, +// fit: BoxFit.cover, +// ), +// const Flexible( +// child: Text('Google', +// style: TextStyle(color: Colors.black), +// ), +// ), +// ], +// ), +// ), +// onTap: () {}, +// ), +// ), +// ), +// SizedBox(width: 10,), +// Expanded( +// child: Container( +// decoration: containerDecoration, +// child:InkWell( +// child: Padding( +// padding: const EdgeInsets.all(8.0), +// child: Row( +// mainAxisAlignment: MainAxisAlignment.spaceAround, +// crossAxisAlignment: CrossAxisAlignment.center, +// children: [ +// SvgPicture.asset( +// Assets.facebook, +// fit: BoxFit.cover, +// ), +// const Flexible( +// child: Text('Facebook', +// style: TextStyle(color: Colors.black), +// ), +// ), +// ], +// ), +// ), +// onTap: () {}, +// ), +// ), +// ), +// +// ], +// ), +// ), +// SizedBox( +// width: MediaQuery.sizeOf(context).width * 0.2, +// child: const Row( +// mainAxisAlignment: MainAxisAlignment.center, +// crossAxisAlignment: CrossAxisAlignment.center, +// children: [ +// Flexible( +// child: Text( +// "Don't you have an account? ", +// style: TextStyle(color: Colors.white), +// )), +// Flexible( +// child: Text( +// "Sign up", +// )), +// ], +// ), +// ) \ No newline at end of file diff --git a/lib/services/auth_api.dart b/lib/services/auth_api.dart index 5f832a14..3fc84c81 100644 --- a/lib/services/auth_api.dart +++ b/lib/services/auth_api.dart @@ -1,3 +1,5 @@ +import 'dart:convert'; + import 'package:syncrow_web/pages/auth/model/token.dart'; import 'package:syncrow_web/services/api/http_service.dart'; import 'package:syncrow_web/utils/constants/api_const.dart'; @@ -43,17 +45,23 @@ class AuthenticationAPI { return response; } - static Future checkOtp({ required var email}) async { + static Future verifyOtp({ required String email, required String otpCode}) async { final response = await HTTPService().post( - path: ApiEndpoints.sendOtp, + path: ApiEndpoints.verifyOtp, body: { - "email": email, - "type": "VERIFICATION" + "email": email, + "type": "VERIFICATION", + "otpCode": otpCode }, showServerMessage: true, expectedResponseModel: (json) { + print('json===${json['message']}'); + if(json['message']=='Otp Verified Successfully'){ + return true; + }else{ + return false; + } }); - return response; } } diff --git a/lib/utils/constants/api_const.dart b/lib/utils/constants/api_const.dart index 8ad54474..93803c16 100644 --- a/lib/utils/constants/api_const.dart +++ b/lib/utils/constants/api_const.dart @@ -7,4 +7,5 @@ abstract class ApiEndpoints { static const String login = '$baseUrl/authentication/user/login'; static const String forgetPassword = '$baseUrl/authentication/user/forget-password'; static const String sendOtp = '$baseUrl/authentication/user/send-otp'; + static const String verifyOtp = '$baseUrl/authentication/user/verify-otp'; } diff --git a/lib/utils/constants/strings_manager.dart b/lib/utils/constants/strings_manager.dart new file mode 100644 index 00000000..f3edde6e --- /dev/null +++ b/lib/utils/constants/strings_manager.dart @@ -0,0 +1,43 @@ +class StringsManager { + static const noRouteFound = 'No route found'; + static const noInternetConnection = 'No internet connection'; + static const String dashboard = 'Dashboard'; + static const String devices = 'Devices'; + static const String routine = 'Routines'; + static const String tapToRunRoutine = 'Tap to run routine'; + static const String wizard = 'Wizard'; + static const String active = 'Active'; + static const String current = 'Current'; + static const String frequency = 'Frequency'; + static const String energyUsage = 'Energy Usage'; + static const String totalConsumption = 'Total Consumption'; + static const String ACConsumption = 'AC Consumption'; + static const String units = 'Units'; + static const String emissions = 'Emissions'; + static const String reductions = 'Reductions'; + static const String winter = 'Winter'; + static const String winterMode = 'Winter Mode'; + static const String summer = 'Summer'; + static const String summerMode = 'Summer Mode'; + static const String on = 'ON'; + static const String off = 'OFF'; + static const String timer = 'Timer'; + static const String dimmerAndColor = "Dimmer & color"; + static const String recentlyUsed = "Recently used colors"; + static const String lightingModes = "Lighting modes"; + static const String doze = "Doze"; + static const String relax = "Relax"; + static const String reading = "Reading"; + static const String energizing = "Energizing"; + static const String createScene = 'Create Scene'; + static const String tapToRun = 'Launch: Tap - To - Run'; + static const String turnOffAllLights = + 'Example: turn off all lights in the with one tap.'; + static const String whenDeviceStatusChanges = 'When device status changes'; + static const String whenUnusualActivityIsDetected = + 'Example: when an unusual activity is detected.'; + static const String functions = "Functions"; + static const String firstLaunch = "firstLaunch"; + static const String deleteScene = 'Delete Scene'; + static const String deleteAutomation = 'Delete Automation'; +} diff --git a/lib/utils/helpers/shared_preferences_helper.dart b/lib/utils/helpers/shared_preferences_helper.dart new file mode 100644 index 00000000..b9c7e0f4 --- /dev/null +++ b/lib/utils/helpers/shared_preferences_helper.dart @@ -0,0 +1,58 @@ +import 'package:shared_preferences/shared_preferences.dart'; + +class SharedPreferencesHelper { + static saveStringToSP(String key, String value) async { + final prefs = await SharedPreferences.getInstance(); + await prefs.setString(key, value); + } + + static saveBoolToSP(String key, bool value) async { + final prefs = await SharedPreferences.getInstance(); + await prefs.setBool(key, value); + } + + static saveIntToSP(String key, int value) async { + final prefs = await SharedPreferences.getInstance(); + await prefs.setInt(key, value); + } + + static saveDoubleToSP(String key, double value) async { + final prefs = await SharedPreferences.getInstance(); + await prefs.setDouble(key, value); + } + + static saveStringListToSP(String key, List value) async { + final prefs = await SharedPreferences.getInstance(); + await prefs.setStringList(key, value); + } + + static Future readStringFromSP(String key) async { + final prefs = await SharedPreferences.getInstance(); + String value = prefs.getString(key) ?? ''; + return value; + } + + static Future readBoolFromSP(String key) async { + final prefs = await SharedPreferences.getInstance(); + bool? value = prefs.getBool(key); + return value; + } + + static Future readIntFromSP(String key) async { + final prefs = await SharedPreferences.getInstance(); + int value = prefs.getInt(key) ?? 0; + return value; + } + + static Future> readStringListFromSP(String key) async { + final prefs = await SharedPreferences.getInstance(); + List? value = prefs.getStringList(key) ?? []; + return value; + } + + static Future removeValueFromSP(String key) async { + final prefs = await SharedPreferences.getInstance(); + await prefs.remove(key); + return true; + } +} diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index 15a1671b..37af1fe0 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -7,8 +7,10 @@ import Foundation import flutter_secure_storage_macos import path_provider_foundation +import shared_preferences_foundation func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { FlutterSecureStoragePlugin.register(with: registry.registrar(forPlugin: "FlutterSecureStoragePlugin")) PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) + SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) } diff --git a/pubspec.lock b/pubspec.lock index d3424a3e..ba0d48bf 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -105,6 +105,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.2" + file: + dependency: transitive + description: + name: file + sha256: "5fc22d7c25582e38ad9a8515372cd9a93834027aacf1801cf01164dac0ffa08c" + url: "https://pub.dev" + source: hosted + version: "7.0.0" flutter: dependency: "direct main" description: flutter @@ -392,6 +400,62 @@ packages: url: "https://pub.dev" source: hosted version: "6.1.2" + shared_preferences: + dependency: "direct main" + description: + name: shared_preferences + sha256: c3f888ba2d659f3e75f4686112cc1e71f46177f74452d40d8307edc332296ead + url: "https://pub.dev" + source: hosted + version: "2.3.0" + shared_preferences_android: + dependency: transitive + description: + name: shared_preferences_android + sha256: "041be4d9d2dc6079cf342bc8b761b03787e3b71192d658220a56cac9c04a0294" + url: "https://pub.dev" + source: hosted + version: "2.3.0" + shared_preferences_foundation: + dependency: transitive + description: + name: shared_preferences_foundation + sha256: "671e7a931f55a08aa45be2a13fe7247f2a41237897df434b30d2012388191833" + url: "https://pub.dev" + source: hosted + version: "2.5.0" + shared_preferences_linux: + dependency: transitive + description: + name: shared_preferences_linux + sha256: "2ba0510d3017f91655b7543e9ee46d48619de2a2af38e5c790423f7007c7ccc1" + url: "https://pub.dev" + source: hosted + version: "2.4.0" + shared_preferences_platform_interface: + dependency: transitive + description: + name: shared_preferences_platform_interface + sha256: "57cbf196c486bc2cf1f02b85784932c6094376284b3ad5779d1b1c6c6a816b80" + url: "https://pub.dev" + source: hosted + version: "2.4.1" + shared_preferences_web: + dependency: transitive + description: + name: shared_preferences_web + sha256: "3a293170d4d9403c3254ee05b84e62e8a9b3c5808ebd17de6a33fe9ea6457936" + url: "https://pub.dev" + source: hosted + version: "2.4.0" + shared_preferences_windows: + dependency: transitive + description: + name: shared_preferences_windows + sha256: "398084b47b7f92110683cac45c6dc4aae853db47e470e5ddcd52cab7f7196ab2" + url: "https://pub.dev" + source: hosted + version: "2.4.0" sky_engine: dependency: transitive description: flutter @@ -497,10 +561,10 @@ packages: dependency: transitive description: name: web - sha256: d43c1d6b787bf0afad444700ae7f4db8827f701bc61c255ac8d328c6f4d52062 + sha256: "97da13628db363c635202ad97068d47c5b8aa555808e7a9411963c533b449b27" url: "https://pub.dev" source: hosted - version: "1.0.0" + version: "0.5.1" win32: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 84c96f79..7f55b18e 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -42,7 +42,7 @@ dependencies: dio: ^5.5.0+1 get_it: ^7.6.7 flutter_secure_storage: ^9.2.2 - + shared_preferences: ^2.3.0 dev_dependencies: flutter_test: diff --git a/test/widget_test.dart b/test/widget_test.dart index 69f36050..e69de29b 100644 --- a/test/widget_test.dart +++ b/test/widget_test.dart @@ -1,30 +0,0 @@ -// This is a basic Flutter widget test. -// -// To perform an interaction with a widget in your test, use the WidgetTester -// utility in the flutter_test package. For example, you can send tap and scroll -// gestures. You can also use WidgetTester to find child widgets in the widget -// tree, read text, and verify that the values of widget properties are correct. - -import 'package:flutter/material.dart'; -import 'package:flutter_test/flutter_test.dart'; - -import 'package:syncrow_web/main.dart'; - -void main() { - testWidgets('Counter increments smoke test', (WidgetTester tester) async { - // Build our app and trigger a frame. - await tester.pumpWidget(const MyApp()); - - // Verify that our counter starts at 0. - expect(find.text('0'), findsOneWidget); - expect(find.text('1'), findsNothing); - - // Tap the '+' icon and trigger a frame. - await tester.tap(find.byIcon(Icons.add)); - await tester.pump(); - - // Verify that our counter has incremented. - expect(find.text('0'), findsNothing); - expect(find.text('1'), findsOneWidget); - }); -}