diff --git a/ios/Flutter/Debug.xcconfig b/ios/Flutter/Debug.xcconfig index 592ceee8..ec97fc6f 100644 --- a/ios/Flutter/Debug.xcconfig +++ b/ios/Flutter/Debug.xcconfig @@ -1 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" #include "Generated.xcconfig" diff --git a/ios/Flutter/Release.xcconfig b/ios/Flutter/Release.xcconfig index 592ceee8..c4855bfe 100644 --- a/ios/Flutter/Release.xcconfig +++ b/ios/Flutter/Release.xcconfig @@ -1 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" #include "Generated.xcconfig" diff --git a/ios/Podfile b/ios/Podfile new file mode 100644 index 00000000..d97f17e2 --- /dev/null +++ b/ios/Podfile @@ -0,0 +1,44 @@ +# Uncomment this line to define a global platform for your project +# platform :ios, '12.0' + +# CocoaPods analytics sends network stats synchronously affecting flutter build latency. +ENV['COCOAPODS_DISABLE_STATS'] = 'true' + +project 'Runner', { + 'Debug' => :debug, + 'Profile' => :release, + 'Release' => :release, +} + +def flutter_root + generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__) + unless File.exist?(generated_xcode_build_settings_path) + raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first" + end + + File.foreach(generated_xcode_build_settings_path) do |line| + matches = line.match(/FLUTTER_ROOT\=(.*)/) + return matches[1].strip if matches + end + raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get" +end + +require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) + +flutter_ios_podfile_setup + +target 'Runner' do + use_frameworks! + use_modular_headers! + + flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) + target 'RunnerTests' do + inherit! :search_paths + end +end + +post_install do |installer| + installer.pods_project.targets.each do |target| + flutter_additional_ios_build_settings(target) + end +end diff --git a/lib/main.dart b/lib/main.dart index 22b0ee28..09a1c6ab 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,10 +1,14 @@ import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; -import 'package:syncrow_web/pages/home/view/home_page.dart'; +import 'package:syncrow_web/pages/auth/view/login_page.dart'; +import 'package:syncrow_web/services/locator.dart'; void main() { + WidgetsFlutterBinding.ensureInitialized(); + initialSetup(); runApp(const MyApp()); } + class MyApp extends StatelessWidget { const MyApp({super.key}); @override @@ -23,7 +27,7 @@ class MyApp extends StatelessWidget { colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple), useMaterial3: true, ), - home: const HomePage(), + home: const LoginPage(), ); } } diff --git a/lib/pages/auth/bloc/login_bloc.dart b/lib/pages/auth/bloc/login_bloc.dart index 70e9cf7d..83443eea 100644 --- a/lib/pages/auth/bloc/login_bloc.dart +++ b/lib/pages/auth/bloc/login_bloc.dart @@ -1,19 +1,201 @@ +import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:syncrow_web/pages/auth/bloc/login_event.dart'; import 'package:syncrow_web/pages/auth/bloc/login_state.dart'; +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/snack_bar.dart'; class LoginBloc extends Bloc { LoginBloc() : super(LoginInitial()) { - on(_onPress); + on(_login); } - void _onPress(LoginButtonPressed event, Emitter emit) async { - emit(LoginLoading()); - await Future.delayed(const Duration(seconds: 2)); - if (event.username == 'admin' && event.password == 'password') { - emit(LoginSuccess()); + + final TextEditingController emailController = TextEditingController(); + final TextEditingController passwordController = TextEditingController(); + + String fullName = ''; + String email = ''; + String forgetPasswordEmail = ''; + String signUpPassword = ''; + String newPassword = ''; + String maskedEmail = ''; + String otpCode = ''; + + final loginFormKey = GlobalKey(); + final signUpFormKey = GlobalKey(); + final checkEmailFormKey = GlobalKey(); + final createNewPasswordKey = GlobalKey(); + static Token token = Token.emptyConstructor(); + static UserModel? user; + + bool isPasswordVisible = false; + bool showValidationMessage = false; + + + /////////////////////////////////////VALIDATORS///////////////////////////////////// + String? passwordValidator(String? value) { + if (value != null) { + if (value.isEmpty) { + return 'Please enter your password'; + } + if (value.isNotEmpty) { + if (!RegExp(r'^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{8,}$') + .hasMatch(value)) { + return 'Password must contain at least:\n - one uppercase letter.\n - one lowercase letter.\n - one number. \n - special character'; + } + } + } + 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? reEnterPasswordCheck(String? value) { + passwordValidator(value); + if (signUpPassword == value) { + return null; } else { - emit(const LoginFailure(error: 'Invalid credentials')); + return 'Passwords do not match'; } } + + 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; + + final localPart = emailParts[0]; + final domainPart = emailParts[1]; + + if (localPart.length < 3) return email; + + final start = localPart.substring(0, 2); + final end = localPart.substring(localPart.length - 1); + + final maskedLocalPart = '$start******$end'; + return '$maskedLocalPart@$domainPart'; + } + + _login(LoginButtonPressed event, Emitter emit) async { + emit(LoginLoading()); + try { + if (event.username.isEmpty ||event.password.isEmpty) { + CustomSnackBar.displaySnackBar('Please enter your credentials'); + emit(const LoginFailure(error: 'Something went wrong')); + return; + } + + token = await AuthenticationAPI.loginWithEmail( + model: LoginWithEmailModel( + email: event.username, + password: event.password, + ), + ); + } catch (failure) { + emit( LoginFailure(error: failure.toString())); + return; + } + if (token.accessTokenIsNotEmpty) { + debugPrint('token: ${token.accessToken}'); + 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() + ); + + user = UserModel.fromToken(token); + emailController.clear(); + passwordController.clear(); + emit(LoginSuccess()); + } else { + emit(const LoginFailure(error: 'Something went wrong')); + } + } + + // signUp() async { + // emit(LoginLoading()); + // final response; + // try { + // List userFullName = fullName.split(' '); + // response = await AuthenticationAPI.signUp( + // model: SignUpModel( + // email: email.toLowerCase(), + // password: signUpPassword, + // firstName: userFullName[0], + // lastName: userFullName[1]), + // ); + // } catch (failure) { + // emit(AuthLoginError(message: failure.toString())); + // return; + // } + // if (response) { + // maskedEmail = maskEmail(email); + // await sendOtp(); + // } else { + // emit(AuthLoginError(message: 'Something went wrong')); + // } + // } + } diff --git a/lib/pages/auth/model/login_with_email_model.dart b/lib/pages/auth/model/login_with_email_model.dart new file mode 100644 index 00000000..c387b0d2 --- /dev/null +++ b/lib/pages/auth/model/login_with_email_model.dart @@ -0,0 +1,23 @@ +class LoginWithEmailModel { + final String email; + final String password; + + LoginWithEmailModel({ + required this.email, + required this.password, + }); + + factory LoginWithEmailModel.fromJson(Map json) { + return LoginWithEmailModel( + email: json['email'], + password: json['password'], + ); + } + + Map toJson() { + return { + 'email': email, + 'password': password, + }; + } +} diff --git a/lib/pages/auth/model/signup_model.dart b/lib/pages/auth/model/signup_model.dart new file mode 100644 index 00000000..c4a0adf7 --- /dev/null +++ b/lib/pages/auth/model/signup_model.dart @@ -0,0 +1,29 @@ +class SignUpModel { + final String email; + final String password; + final String firstName; + final String lastName; + + SignUpModel( + {required this.email, + required this.password, + required this.firstName, + required this.lastName}); + + factory SignUpModel.fromJson(Map json) { + return SignUpModel( + email: json['email'], + password: json['password'], + firstName: json['firstName'], + lastName: json['lastName']); + } + + Map toJson() { + return { + 'email': email, + 'password': password, + 'firstName': firstName, + 'lastName': lastName, + }; + } +} diff --git a/lib/pages/auth/model/token.dart b/lib/pages/auth/model/token.dart new file mode 100644 index 00000000..3e0728bc --- /dev/null +++ b/lib/pages/auth/model/token.dart @@ -0,0 +1,69 @@ +import 'dart:convert'; +import 'package:flutter_secure_storage/flutter_secure_storage.dart'; +import 'package:syncrow_web/utils/helpers/decodeBase64.dart'; + +class Token { + static const String loginAccessTokenKey = 'accessToken'; + static const String loginRefreshTokenKey = 'refreshToken'; + final String accessToken; + final String refreshToken; + final String sessionId; + final int iat; + final int exp; + + Token.emptyConstructor() + : accessToken = '', + refreshToken = '', + sessionId = '', + iat = 0, + exp = 0; + + bool get accessTokenIsNotEmpty => accessToken.isNotEmpty; + + bool get refreshTokenIsNotEmpty => refreshToken.isNotEmpty; + + bool get isNotEmpty => accessToken.isNotEmpty && refreshToken.isNotEmpty; + + Token( + this.accessToken, + this.refreshToken, + this.sessionId, + this.iat, + this.exp, + ); + + Token.refreshToken(this.refreshToken) + : accessToken = '', + sessionId = '', + iat = 0, + exp = 0; + + factory Token.fromJson(Map json) { + //save token to secure storage + var storage = const FlutterSecureStorage(); + storage.write( + key: loginAccessTokenKey, + value: json[loginAccessTokenKey] ?? ''); + storage.write( + key: loginRefreshTokenKey, + value: json[loginRefreshTokenKey] ?? ''); + //create token object ? + return Token( + json[loginAccessTokenKey] ?? '', + json[loginRefreshTokenKey] ?? '', '', 0, 0); + } + + Map refreshTokenToJson() => + {loginRefreshTokenKey: refreshToken}; + + Map accessTokenToJson() => {loginAccessTokenKey: accessToken}; + + static Map decodeToken(String accessToken) { + final parts = accessToken.split('.'); + if (parts.length != 3) { + throw Exception('invalid access token'); + } + final payload = decodeBase64(parts[1]); + return json.decode(payload); + } +} diff --git a/lib/pages/auth/model/user_model.dart b/lib/pages/auth/model/user_model.dart new file mode 100644 index 00000000..5ca08d1c --- /dev/null +++ b/lib/pages/auth/model/user_model.dart @@ -0,0 +1,66 @@ +import 'package:syncrow_web/pages/auth/model/token.dart'; + +class UserModel { + static String userUuidKey = 'userUuid'; + final String? uuid; + final String? email; + final String? name; + final String? photoUrl; + + final String? phoneNumber; + + final bool? isEmailVerified; + + final bool? isAgreementAccepted; + + UserModel({ + required this.uuid, + required this.email, + required this.name, + required this.photoUrl, + required this.phoneNumber, + required this.isEmailVerified, + required this.isAgreementAccepted, + }); + + factory UserModel.fromJson(Map json) { + return UserModel( + uuid: json['id'], + email: json['email'], + name: json['name'], + photoUrl: json['photoUrl'], + phoneNumber: json['phoneNumber'], + isEmailVerified: json['isEmailVerified'], + isAgreementAccepted: json['isAgreementAccepted'], + ); + } + + //uuid to json + + //from token + factory UserModel.fromToken(Token token) { + Map tempJson = Token.decodeToken(token.accessToken); + + return UserModel( + uuid: tempJson['uuid'].toString(), + email: tempJson['email'], + name: null, + photoUrl: null, + phoneNumber: null, + isEmailVerified: null, + isAgreementAccepted: null, + ); + } + + Map toJson() { + return { + 'id': uuid, + 'email': email, + 'name': name, + 'photoUrl': photoUrl, + 'phoneNumber': phoneNumber, + 'isEmailVerified': isEmailVerified, + 'isAgreementAccepted': isAgreementAccepted, + }; + } +} diff --git a/lib/pages/auth/model/verify_code.dart b/lib/pages/auth/model/verify_code.dart new file mode 100644 index 00000000..da29c25b --- /dev/null +++ b/lib/pages/auth/model/verify_code.dart @@ -0,0 +1,27 @@ +class VerifyPassCode { + static const String verificationPhone = 'phone'; + static const String verificationPassCode = 'passCode'; + static const String verificationAgent = 'agent'; + static const String verificationDeviceId = 'deviceId'; + + final String phone; + final String passCode; + final String agent; + final String deviceId; + + VerifyPassCode( + {required this.phone, required this.passCode, required this.agent, required this.deviceId}); + + factory VerifyPassCode.fromJson(Map json) => VerifyPassCode( + phone: json[verificationPhone], + passCode: json[verificationPassCode], + agent: json[verificationAgent], + deviceId: json[verificationDeviceId]); + + Map toJson() => { + verificationPhone: phone, + verificationPassCode: passCode, + verificationAgent: agent, + verificationDeviceId: deviceId, + }; +} diff --git a/lib/pages/auth/view/login_mobile_page.dart b/lib/pages/auth/view/login_mobile_page.dart index 9dc81efe..ccccb916 100644 --- a/lib/pages/auth/view/login_mobile_page.dart +++ b/lib/pages/auth/view/login_mobile_page.dart @@ -2,7 +2,7 @@ import 'dart:ui'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_svg/svg.dart'; -import 'package:syncrow_web/utils/assets.dart'; +import 'package:syncrow_web/utils/constants/assets.dart'; import 'package:syncrow_web/pages/auth/bloc/login_bloc.dart'; import 'package:syncrow_web/pages/auth/bloc/login_event.dart'; import 'package:syncrow_web/pages/auth/bloc/login_state.dart'; diff --git a/lib/pages/auth/view/login_web_page.dart b/lib/pages/auth/view/login_web_page.dart index e367f96a..92f24028 100644 --- a/lib/pages/auth/view/login_web_page.dart +++ b/lib/pages/auth/view/login_web_page.dart @@ -1,11 +1,12 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_svg/svg.dart'; -import 'package:syncrow_web/utils/assets.dart'; +import 'package:syncrow_web/utils/constants/assets.dart'; import 'package:syncrow_web/pages/auth/bloc/login_bloc.dart'; import 'package:syncrow_web/pages/auth/bloc/login_event.dart'; import 'package:syncrow_web/pages/auth/bloc/login_state.dart'; import 'package:syncrow_web/pages/home/view/home_page.dart'; +import 'package:syncrow_web/utils/style.dart'; class LoginWebPage extends StatelessWidget { const LoginWebPage({super.key}); @@ -13,9 +14,6 @@ class LoginWebPage extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( - // appBar: AppBar( - // title: const Text('Login'), - // ), body: BlocProvider( create: (context) => LoginBloc(), child: BlocConsumer( @@ -130,24 +128,7 @@ class LoginWebPage extends StatelessWidget { width: MediaQuery.sizeOf(context).width * 0.2, child: TextFormField( controller: _usernameController, - decoration: InputDecoration( - labelText: 'Email', - labelStyle: const TextStyle(color: Colors.white), - hintText: 'username@gmail.com', - hintStyle: const TextStyle(color: Colors.grey), - enabledBorder: OutlineInputBorder( - borderRadius: BorderRadius.circular(8.0), - borderSide: const BorderSide(color: Colors.white), - ), - focusedBorder: OutlineInputBorder( - borderRadius: BorderRadius.circular(8.0), - borderSide: const BorderSide(color: Colors.white), - ), - filled: true, - fillColor: Colors.white, - contentPadding: const EdgeInsets.symmetric( - vertical: 16.0, horizontal: 12.0), - ), + decoration: textBoxDecoration()!.copyWith(hintText: 'Email'), style: const TextStyle(color: Colors.black), ), ), @@ -169,24 +150,7 @@ class LoginWebPage extends StatelessWidget { width: MediaQuery.sizeOf(context).width * 0.2, child: TextFormField( controller: _passwordController, - decoration: InputDecoration( - labelText: 'Password', - labelStyle: const TextStyle(color: Colors.white), - hintText: 'Password', - hintStyle: const TextStyle(color: Colors.grey), - enabledBorder: OutlineInputBorder( - borderRadius: BorderRadius.circular(8.0), - borderSide: const BorderSide(color: Colors.white), - ), - focusedBorder: OutlineInputBorder( - borderRadius: BorderRadius.circular(8.0), - borderSide: const BorderSide(color: Colors.white), - ), - filled: true, - fillColor: Colors.white, - contentPadding: const EdgeInsets.symmetric( - vertical: 16.0, horizontal: 12.0), - ), + decoration: textBoxDecoration()!.copyWith(hintText: 'Password' ,), style: const TextStyle(color: Colors.black), ), ), diff --git a/lib/pages/home/view/tree_page.dart b/lib/pages/home/view/tree_page.dart index a213c84e..6038dadf 100644 --- a/lib/pages/home/view/tree_page.dart +++ b/lib/pages/home/view/tree_page.dart @@ -73,7 +73,9 @@ class TreeWidget extends StatelessWidget { child: GraphView( graph: state.graph, algorithm: BuchheimWalkerAlgorithm( - state.builder, TreeEdgeRenderer(state.builder)), + state.builder, + TreeEdgeRenderer(state.builder) + ), paint: Paint() ..color = Colors.green ..strokeWidth = 1 @@ -136,14 +138,50 @@ Widget rectangleWidget(String text, Node node, BuildContext blocContext) { ); }, child: Container( - padding: EdgeInsets.all(16), - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(4), - boxShadow: [ - BoxShadow(color: Colors.blue[100]!, spreadRadius: 1), - ], - ), - child: Text(text) + width: MediaQuery.of(blocContext).size.width*0.2, + margin: EdgeInsets.symmetric(vertical: 10.0, horizontal: 20.0), + padding: EdgeInsets.all(20.0), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(10.0), + boxShadow: [ + BoxShadow( + color: Colors.grey.withOpacity(0.5), + spreadRadius: 2, + blurRadius: 5, + offset: Offset(0, 3), // changes position of shadow + ), + ], + ), + child: Row( + children: [ + const SizedBox( + child: Icon( + Icons.location_on, + color: Colors.blue, + size: 40.0, + ), + ), + const SizedBox(width: 10.0), + SizedBox( + child: Text( + text, + style: const TextStyle( + fontSize: 24.0, + fontWeight: FontWeight.bold, + ), + ), + ), + const Spacer(), + Container( + child: const Icon( + Icons.add_circle_outline, + color: Colors.grey, + size: 24.0, + ), + ), + ], + ), ), ); } diff --git a/lib/services/api/http_interceptor.dart b/lib/services/api/http_interceptor.dart new file mode 100644 index 00000000..71fbaba3 --- /dev/null +++ b/lib/services/api/http_interceptor.dart @@ -0,0 +1,79 @@ +import 'dart:io'; +import 'package:dio/dio.dart'; +import 'package:flutter_secure_storage/flutter_secure_storage.dart'; +import 'package:syncrow_web/pages/auth/model/token.dart'; +import 'dart:async'; +import 'package:syncrow_web/services/api/network_exception.dart'; +import 'package:syncrow_web/utils/constants/api_const.dart'; +import 'package:syncrow_web/utils/snack_bar.dart'; + +class HTTPInterceptor extends InterceptorsWrapper { + List headerExclusionList = []; + + List headerExclusionListOfAddedParameters = [ + ApiEndpoints.login, + ]; + + @override + void onResponse(Response response, ResponseInterceptorHandler handler) async { + if (await validateResponse(response)) { + super.onResponse(response, handler); + } else { + handler.reject(DioException(requestOptions: response.requestOptions, response: response)); + } + } + + @override + void onRequest(RequestOptions options, RequestInterceptorHandler handler) async { + var storage = const FlutterSecureStorage(); + var token = await storage.read(key: Token.loginAccessTokenKey); + if (checkHeaderExclusionListOfAddedParameters(options.path)) { + options.headers.putIfAbsent(HttpHeaders.authorizationHeader, () => "Bearer $token"); + } + options.headers['Authorization'] = 'Bearer ${token!}'; + super.onRequest(options, handler); + } + + @override + void onError(DioException err, ErrorInterceptorHandler handler) async { + ServerFailure failure = ServerFailure.fromDioError(err); + if (failure.toString().isNotEmpty) { + CustomSnackBar.displaySnackBar(failure.toString()); + } + var storage = const FlutterSecureStorage(); + var token = await storage.read(key: Token.loginAccessTokenKey); + if (err.response?.statusCode == 401 && token != null) { + // await AuthCubit.get(NavigationService.navigatorKey.currentContext!).logout(); + } + super.onError(err, handler); + } + + /// Validates the response and returns true if it is successful (status code 2xx). + Future validateResponse(Response response) async { + if (response.statusCode != null) { + if (response.statusCode! >= 200 && response.statusCode! < 300) { + // If the response status code is within the successful range (2xx), + // return true indicating a successful response. + return true; + } else { + // If the response status code is not within the successful range (2xx), + // return false indicating an unsuccessful response. + return false; + } + } else { + // If the response status code is null, return false indicating an unsuccessful response. + return false; + } + } + + checkHeaderExclusionListOfAddedParameters(String path) { + bool shouldAddHeader = true; + + for (var urlConstant in headerExclusionListOfAddedParameters) { + if (path.contains(urlConstant)) { + shouldAddHeader = false; + } + } + return shouldAddHeader; + } +} diff --git a/lib/services/api/http_service.dart b/lib/services/api/http_service.dart new file mode 100644 index 00000000..b75f05cf --- /dev/null +++ b/lib/services/api/http_service.dart @@ -0,0 +1,134 @@ +import 'package:dio/dio.dart'; +import 'package:syncrow_web/services/api/http_interceptor.dart'; +import 'package:syncrow_web/services/locator.dart'; +import 'package:syncrow_web/utils/constants/api_const.dart'; + +class HTTPService { + final Dio client = serviceLocator.get(); + + // final navigatorKey = GlobalKey(); + + String certificateString = ""; + + static Dio setupDioClient() { + Dio client = Dio( + BaseOptions( + baseUrl: ApiEndpoints.baseUrl, + receiveDataWhenStatusError: true, + followRedirects: false, + connectTimeout: const Duration(seconds: 30), + receiveTimeout: const Duration(seconds: 30), + ), + ); + + client.interceptors.add(serviceLocator.get()); + return client; + } + + Future get({ + required String path, + Map? queryParameters, + required T Function(dynamic) expectedResponseModel, + bool showServerMessage = true, + }) async { + try { + final response = await client.get( + path, + queryParameters: queryParameters, + ); + return expectedResponseModel(response.data); + } catch (error) { + rethrow; + } + } + + Future post( + {required String path, + Map? queryParameters, + Options? options, + dynamic body, + bool showServerMessage = true, + required T Function(dynamic) expectedResponseModel}) async { + try { + final response = await client.post( + path, + data: body, + queryParameters: queryParameters, + options: options, + ); + return expectedResponseModel(response.data); + } catch (error) { + rethrow; + } + } + + Future patch( + {required String path, + Map? queryParameters, + dynamic body, + required T Function(dynamic) expectedResponseModel}) async { + try { + final response = await client.patch( + path, + data: body, + queryParameters: queryParameters, + ); + return expectedResponseModel(response.data); + } catch (error) { + rethrow; + } + } + + Future put( + {required String path, + Map? queryParameters, + dynamic body, + required T Function(dynamic) expectedResponseModel}) async { + try { + final response = await client.put( + path, + data: body, + queryParameters: queryParameters, + ); + return expectedResponseModel(response.data); + } catch (error) { + rethrow; + } + } + + Future download( + {required String path, + required String savePath, + Map? queryParameters, + required T Function(dynamic) expectedResponseModel}) async { + try { + final response = await client.download( + path, + savePath, + onReceiveProgress: (current, total) {}, + ); + + return expectedResponseModel(response.data); + // return expectedResponseModel(response.data); + } catch (error) { + rethrow; + } + } + + Future delete({ + required String path, + Map? queryParameters, + required T Function(dynamic) expectedResponseModel, + bool showServerMessage = true, + }) async { + try { + final response = await client.delete( + path, + queryParameters: queryParameters, + ); + return expectedResponseModel(response.data); + } catch (error) { + rethrow; + } + } +} diff --git a/lib/services/api/my_http_overrides.dart b/lib/services/api/my_http_overrides.dart new file mode 100644 index 00000000..3707be37 --- /dev/null +++ b/lib/services/api/my_http_overrides.dart @@ -0,0 +1,11 @@ +import 'dart:io'; + +// We use this class to skip the problem of SSL certification and solve the Image.network(url) issue +class MyHttpOverrides extends HttpOverrides { + @override + HttpClient createHttpClient(SecurityContext? context) { + return super.createHttpClient(context) + ..badCertificateCallback = + (X509Certificate cert, String host, int port) => true; + } +} diff --git a/lib/services/api/network_exception.dart b/lib/services/api/network_exception.dart new file mode 100644 index 00000000..81f12b3e --- /dev/null +++ b/lib/services/api/network_exception.dart @@ -0,0 +1,75 @@ +import 'package:dio/dio.dart'; + +abstract class Failure { + final String errMessage; + + Failure(this.errMessage); +} + +class ServerFailure extends Failure { + ServerFailure(super.errMessage); + + @override + String toString() { + return errMessage; + } + + factory ServerFailure.fromDioError(DioException dioError) { + switch (dioError.type) { + case DioExceptionType.connectionTimeout: + return ServerFailure("Connection timeout with the Server."); + case DioExceptionType.sendTimeout: + return ServerFailure("Send timeout with the Server."); + + case DioExceptionType.receiveTimeout: + return ServerFailure("Receive timeout with the Server."); + + case DioExceptionType.badCertificate: + return ServerFailure("Bad certificate!"); + + case DioExceptionType.badResponse: + { + // var document = parser.parse(dioError.response!.data.toString()); + // var message = document.body!.text; + return ServerFailure.fromResponse( + dioError.response!.statusCode!, + dioError.response?.data['message'] ?? "Error"); + } + case DioExceptionType.cancel: + return ServerFailure("The request to ApiServer was canceled"); + + case DioExceptionType.connectionError: + return ServerFailure("No Internet Connection"); + + case DioExceptionType.unknown: + return ServerFailure("Unexpected Error, Please try again!"); + + default: + return ServerFailure("Unexpected Error, Please try again!"); + } + } + + factory ServerFailure.fromResponse(int? statusCode, dynamic responseMessage) { + switch (statusCode) { + case 401: + case 403: + return ServerFailure(responseMessage); + case 400: + List errors = []; + if (responseMessage is List) { + for (var error in responseMessage) { + errors.add(error); + } + } else { + return ServerFailure(responseMessage); + } + return ServerFailure(errors.join('\n')); + case 404: + return ServerFailure(""); + case 500: + return ServerFailure(responseMessage); + default: + return ServerFailure("Opps there was an Error, Please try again!"); + } + } +} diff --git a/lib/services/auth_api.dart b/lib/services/auth_api.dart index eefc575e..4829f527 100644 --- a/lib/services/auth_api.dart +++ b/lib/services/auth_api.dart @@ -1 +1,29 @@ -class AuthAPI {} +import 'package:syncrow_web/pages/auth/model/token.dart'; +import 'package:syncrow_web/utils/constants/api_const.dart'; +import 'api/http_service.dart'; + +class AuthenticationAPI { + + + static Future loginWithEmail({required var model}) async { + final response = await HTTPService().post( + path: ApiEndpoints.login, + body: model.toJson(), + showServerMessage: false, + expectedResponseModel: (json) => Token.fromJson(json['data'])); + return response; + } + + // static Future signUp({required SignUpModel model}) async { + // final response = await HTTPService().post( + // path: ApiEndpoints.signUp, + // body: model.toJson(), + // showServerMessage: false, + // expectedResponseModel: (json) => json['statusCode'] == 201); + // return response; + // } + + + + +} diff --git a/lib/services/locator.dart b/lib/services/locator.dart new file mode 100644 index 00000000..4a9263a0 --- /dev/null +++ b/lib/services/locator.dart @@ -0,0 +1,12 @@ +import 'package:dio/dio.dart'; +import 'package:get_it/get_it.dart'; +import 'package:syncrow_web/services/api/http_interceptor.dart'; +import 'package:syncrow_web/services/api/http_service.dart'; + +final GetIt serviceLocator = GetIt.instance; + //setupLocator() // to search for dependency injection in flutter +initialSetup() { + serviceLocator.registerSingleton(HTTPInterceptor()); + //Base classes + serviceLocator.registerSingleton(HTTPService.setupDioClient()); +} diff --git a/lib/utils/constants/api_const.dart b/lib/utils/constants/api_const.dart new file mode 100644 index 00000000..e188f4e6 --- /dev/null +++ b/lib/utils/constants/api_const.dart @@ -0,0 +1,8 @@ +abstract class ApiEndpoints { + static const String baseUrl = 'https://syncrow-staging.azurewebsites.net'; + // static const String baseUrl = 'http://100.107.182.63:4001'; //Localhost +//https://syncrow-staging.azurewebsites.net +////////////////////////////////////// Authentication /////////////////////////////// + static const String signUp = 'authentication/user/signup'; + static const String login = 'authentication/user/login'; +} diff --git a/lib/utils/assets.dart b/lib/utils/constants/assets.dart similarity index 100% rename from lib/utils/assets.dart rename to lib/utils/constants/assets.dart diff --git a/lib/utils/constants/string_const.dart b/lib/utils/constants/string_const.dart new file mode 100644 index 00000000..3f2ff2d6 --- /dev/null +++ b/lib/utils/constants/string_const.dart @@ -0,0 +1,5 @@ + + + + + diff --git a/lib/utils/helpers/decodeBase64.dart b/lib/utils/helpers/decodeBase64.dart new file mode 100644 index 00000000..9931a8e3 --- /dev/null +++ b/lib/utils/helpers/decodeBase64.dart @@ -0,0 +1,21 @@ +import 'dart:convert'; + +String decodeBase64(String str) { + //'-', '+' 62nd char of encoding, '_', '/' 63rd char of encoding + String output = str.replaceAll('-', '+').replaceAll('_', '/'); + switch (output.length % 4) { + // Pad with trailing '=' + case 0: // No pad chars in this case + break; + case 2: // Two pad chars + output += '=='; + break; + case 3: // One pad char + output += '='; + break; + default: + throw Exception('Illegal base64url string!"'); + } + + return utf8.decode(base64Url.decode(output)); +} diff --git a/lib/utils/navigation_service.dart b/lib/utils/navigation_service.dart new file mode 100644 index 00000000..c0b12167 --- /dev/null +++ b/lib/utils/navigation_service.dart @@ -0,0 +1,7 @@ +import 'package:flutter/material.dart'; + +class NavigationService { + static GlobalKey navigatorKey = GlobalKey(); + static GlobalKey? snackbarKey = + GlobalKey(); +} diff --git a/lib/utils/responsive_layout.dart b/lib/utils/responsive_layout.dart index 13b584ea..804b5ddb 100644 --- a/lib/utils/responsive_layout.dart +++ b/lib/utils/responsive_layout.dart @@ -1,5 +1,3 @@ - - import 'package:flutter/material.dart'; @@ -15,7 +13,7 @@ class ResponsiveLayout extends StatelessWidget { }else{ return desktopBody; } - }, + }, ); } } diff --git a/lib/utils/snack_bar.dart b/lib/utils/snack_bar.dart new file mode 100644 index 00000000..d50a4250 --- /dev/null +++ b/lib/utils/snack_bar.dart @@ -0,0 +1,40 @@ +import 'package:flutter/material.dart'; +import 'package:syncrow_web/utils/navigation_service.dart'; + +class CustomSnackBar { + static displaySnackBar(String message) { + final key = NavigationService.snackbarKey; + if (key != null) { + final snackBar = SnackBar(content: Text(message)); + key.currentState?.clearSnackBars(); + key.currentState?.showSnackBar(snackBar); + } + } + + static greenSnackBar(String message) { + final key = NavigationService.snackbarKey; + BuildContext? currentContext = key?.currentContext; + if (key != null && currentContext != null) { + final snackBar = SnackBar( + padding: const EdgeInsets.all(16), + backgroundColor: Colors.green, + content: Row(mainAxisAlignment: MainAxisAlignment.center, children: [ + const Icon( + Icons.check_circle, + color: Colors.white, + size: 32, + ), + const SizedBox( + width: 8, + ), + Text( + message, + style: Theme.of(currentContext).textTheme.bodySmall!.copyWith( + fontSize: 14, fontWeight: FontWeight.w500, color: Colors.green), + ) + ]), + ); + key.currentState?.showSnackBar(snackBar); + } + } +} diff --git a/lib/utils/style.dart b/lib/utils/style.dart index 677aac50..8e8eb3f6 100644 --- a/lib/utils/style.dart +++ b/lib/utils/style.dart @@ -1,10 +1,9 @@ import 'package:flutter/material.dart'; - import 'color_manager.dart'; -InputDecoration? textBoxDecoration = InputDecoration( +InputDecoration? textBoxDecoration({bool suffixIcon = false}) => InputDecoration( focusColor: ColorsManager.grayColor, - suffixIcon: const Icon(Icons.search), + suffixIcon:suffixIcon? const Icon(Icons.search):null, hintText: 'Search', filled: true, // Enable background filling fillColor: Colors.grey.shade200, // Set the background color diff --git a/lib/web_layout/menu_sidebar.dart b/lib/web_layout/menu_sidebar.dart index f5af64d7..5cc882a8 100644 --- a/lib/web_layout/menu_sidebar.dart +++ b/lib/web_layout/menu_sidebar.dart @@ -1,5 +1,3 @@ - - import 'package:flutter/material.dart'; import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/style.dart'; @@ -44,7 +42,7 @@ class MenuSidebar extends StatelessWidget { const SizedBox(height: 20,), TextFormField( controller: TextEditingController(), - decoration:textBoxDecoration + decoration:textBoxDecoration(suffixIcon: true) ), Container(height: 100,) ], diff --git a/lib/web_layout/web_app_bar.dart b/lib/web_layout/web_app_bar.dart index 94773d73..c7655eef 100644 --- a/lib/web_layout/web_app_bar.dart +++ b/lib/web_layout/web_app_bar.dart @@ -57,7 +57,4 @@ class WebAppBar extends StatelessWidget { ) , ); } - - // @override - // Size get preferredSize => Size.fromHeight(50.0); } \ No newline at end of file diff --git a/lib/web_layout/web_scaffold.dart b/lib/web_layout/web_scaffold.dart index 8ea9f506..1f697739 100644 --- a/lib/web_layout/web_scaffold.dart +++ b/lib/web_layout/web_scaffold.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_svg/svg.dart'; -import 'package:syncrow_web/utils/assets.dart'; +import 'package:syncrow_web/utils/constants/assets.dart'; import 'package:syncrow_web/web_layout/web_app_bar.dart'; import 'menu_sidebar.dart'; diff --git a/linux/flutter/generated_plugin_registrant.cc b/linux/flutter/generated_plugin_registrant.cc index e71a16d2..d0e7f797 100644 --- a/linux/flutter/generated_plugin_registrant.cc +++ b/linux/flutter/generated_plugin_registrant.cc @@ -6,6 +6,10 @@ #include "generated_plugin_registrant.h" +#include void fl_register_plugins(FlPluginRegistry* registry) { + g_autoptr(FlPluginRegistrar) flutter_secure_storage_linux_registrar = + fl_plugin_registry_get_registrar_for_plugin(registry, "FlutterSecureStorageLinuxPlugin"); + flutter_secure_storage_linux_plugin_register_with_registrar(flutter_secure_storage_linux_registrar); } diff --git a/linux/flutter/generated_plugins.cmake b/linux/flutter/generated_plugins.cmake index 2e1de87a..b29e9ba0 100644 --- a/linux/flutter/generated_plugins.cmake +++ b/linux/flutter/generated_plugins.cmake @@ -3,6 +3,7 @@ # list(APPEND FLUTTER_PLUGIN_LIST + flutter_secure_storage_linux ) list(APPEND FLUTTER_FFI_PLUGIN_LIST diff --git a/macos/Flutter/Flutter-Debug.xcconfig b/macos/Flutter/Flutter-Debug.xcconfig index c2efd0b6..4b81f9b2 100644 --- a/macos/Flutter/Flutter-Debug.xcconfig +++ b/macos/Flutter/Flutter-Debug.xcconfig @@ -1 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" #include "ephemeral/Flutter-Generated.xcconfig" diff --git a/macos/Flutter/Flutter-Release.xcconfig b/macos/Flutter/Flutter-Release.xcconfig index c2efd0b6..5caa9d15 100644 --- a/macos/Flutter/Flutter-Release.xcconfig +++ b/macos/Flutter/Flutter-Release.xcconfig @@ -1 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" #include "ephemeral/Flutter-Generated.xcconfig" diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index cccf817a..15a1671b 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -5,6 +5,10 @@ import FlutterMacOS import Foundation +import flutter_secure_storage_macos +import path_provider_foundation func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { + FlutterSecureStoragePlugin.register(with: registry.registrar(forPlugin: "FlutterSecureStoragePlugin")) + PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) } diff --git a/macos/Podfile b/macos/Podfile new file mode 100644 index 00000000..c795730d --- /dev/null +++ b/macos/Podfile @@ -0,0 +1,43 @@ +platform :osx, '10.14' + +# CocoaPods analytics sends network stats synchronously affecting flutter build latency. +ENV['COCOAPODS_DISABLE_STATS'] = 'true' + +project 'Runner', { + 'Debug' => :debug, + 'Profile' => :release, + 'Release' => :release, +} + +def flutter_root + generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'ephemeral', 'Flutter-Generated.xcconfig'), __FILE__) + unless File.exist?(generated_xcode_build_settings_path) + raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure \"flutter pub get\" is executed first" + end + + File.foreach(generated_xcode_build_settings_path) do |line| + matches = line.match(/FLUTTER_ROOT\=(.*)/) + return matches[1].strip if matches + end + raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Flutter-Generated.xcconfig, then run \"flutter pub get\"" +end + +require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) + +flutter_macos_podfile_setup + +target 'Runner' do + use_frameworks! + use_modular_headers! + + flutter_install_all_macos_pods File.dirname(File.realpath(__FILE__)) + target 'RunnerTests' do + inherit! :search_paths + end +end + +post_install do |installer| + installer.pods_project.targets.each do |target| + flutter_additional_macos_build_settings(target) + end +end diff --git a/pubspec.lock b/pubspec.lock index 863a9197..19cd83d0 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -65,6 +65,22 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.8" + dio: + dependency: "direct main" + description: + name: dio + sha256: e17f6b3097b8c51b72c74c9f071a605c47bcc8893839bd66732457a5ebe73714 + url: "https://pub.dev" + source: hosted + version: "5.5.0+1" + dio_web_adapter: + dependency: transitive + description: + name: dio_web_adapter + sha256: "36c5b2d79eb17cdae41e974b7a8284fec631651d2a6f39a8a2ff22327e90aeac" + url: "https://pub.dev" + source: hosted + version: "1.0.1" equatable: dependency: "direct main" description: @@ -81,6 +97,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.3.1" + ffi: + dependency: transitive + description: + name: ffi + sha256: "493f37e7df1804778ff3a53bd691d8692ddf69702cf4c1c1096a2e41b4779e21" + url: "https://pub.dev" + source: hosted + version: "2.1.2" flutter: dependency: "direct main" description: flutter @@ -102,6 +126,54 @@ packages: url: "https://pub.dev" source: hosted version: "3.0.2" + flutter_secure_storage: + dependency: "direct main" + description: + name: flutter_secure_storage + sha256: "165164745e6afb5c0e3e3fcc72a012fb9e58496fb26ffb92cf22e16a821e85d0" + url: "https://pub.dev" + source: hosted + version: "9.2.2" + flutter_secure_storage_linux: + dependency: transitive + description: + name: flutter_secure_storage_linux + sha256: "4d91bfc23047422cbcd73ac684bc169859ee766482517c22172c86596bf1464b" + url: "https://pub.dev" + source: hosted + version: "1.2.1" + flutter_secure_storage_macos: + dependency: transitive + description: + name: flutter_secure_storage_macos + sha256: "1693ab11121a5f925bbea0be725abfcfbbcf36c1e29e571f84a0c0f436147a81" + url: "https://pub.dev" + source: hosted + version: "3.1.2" + flutter_secure_storage_platform_interface: + dependency: transitive + description: + name: flutter_secure_storage_platform_interface + sha256: cf91ad32ce5adef6fba4d736a542baca9daf3beac4db2d04be350b87f69ac4a8 + url: "https://pub.dev" + source: hosted + version: "1.1.2" + flutter_secure_storage_web: + dependency: transitive + description: + name: flutter_secure_storage_web + sha256: f4ebff989b4f07b2656fb16b47852c0aab9fed9b4ec1c70103368337bc1886a9 + url: "https://pub.dev" + source: hosted + version: "1.2.1" + flutter_secure_storage_windows: + dependency: transitive + description: + name: flutter_secure_storage_windows + sha256: b20b07cb5ed4ed74fc567b78a72936203f587eba460af1df11281c9326cd3709 + url: "https://pub.dev" + source: hosted + version: "3.1.2" flutter_svg: dependency: "direct main" description: @@ -115,6 +187,19 @@ packages: description: flutter source: sdk version: "0.0.0" + flutter_web_plugins: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + get_it: + dependency: "direct main" + description: + name: get_it + sha256: d85128a5dae4ea777324730dc65edd9c9f43155c109d5cc0a69cab74139fbac1 + url: "https://pub.dev" + source: hosted + version: "7.7.0" graphview: dependency: "direct main" description: @@ -139,6 +224,14 @@ packages: url: "https://pub.dev" source: hosted version: "4.0.2" + js: + dependency: transitive + description: + name: js + sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3 + url: "https://pub.dev" + source: hosted + version: "0.6.7" leak_tracker: dependency: transitive description: @@ -219,6 +312,54 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.1" + path_provider: + dependency: transitive + description: + name: path_provider + sha256: c9e7d3a4cd1410877472158bee69963a4579f78b68c65a2b7d40d1a7a88bb161 + url: "https://pub.dev" + source: hosted + version: "2.1.3" + path_provider_android: + dependency: transitive + description: + name: path_provider_android + sha256: "30c5aa827a6ae95ce2853cdc5fe3971daaac00f6f081c419c013f7f57bff2f5e" + url: "https://pub.dev" + source: hosted + version: "2.2.7" + path_provider_foundation: + dependency: transitive + description: + name: path_provider_foundation + sha256: f234384a3fdd67f989b4d54a5d73ca2a6c422fa55ae694381ae0f4375cd1ea16 + url: "https://pub.dev" + source: hosted + version: "2.4.0" + path_provider_linux: + dependency: transitive + description: + name: path_provider_linux + sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279 + url: "https://pub.dev" + source: hosted + version: "2.2.1" + path_provider_platform_interface: + dependency: transitive + description: + name: path_provider_platform_interface + sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334" + url: "https://pub.dev" + source: hosted + version: "2.1.2" + path_provider_windows: + dependency: transitive + description: + name: path_provider_windows + sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7 + url: "https://pub.dev" + source: hosted + version: "2.3.0" petitparser: dependency: transitive description: @@ -227,6 +368,22 @@ packages: url: "https://pub.dev" source: hosted version: "6.0.2" + platform: + dependency: transitive + description: + name: platform + sha256: "9b71283fc13df574056616011fb138fd3b793ea47cc509c189a6c3fa5f8a1a65" + url: "https://pub.dev" + source: hosted + version: "3.1.5" + plugin_platform_interface: + dependency: transitive + description: + name: plugin_platform_interface + sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02" + url: "https://pub.dev" + source: hosted + version: "2.1.8" provider: dependency: transitive description: @@ -344,6 +501,22 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.0" + win32: + dependency: transitive + description: + name: win32 + sha256: a79dbe579cb51ecd6d30b17e0cae4e0ea15e2c0e66f69ad4198f22a6789e94f4 + url: "https://pub.dev" + source: hosted + version: "5.5.1" + xdg_directories: + dependency: transitive + description: + name: xdg_directories + sha256: faea9dee56b520b55a566385b84f2e8de55e7496104adada9962e0bd11bcff1d + url: "https://pub.dev" + source: hosted + version: "1.0.4" xml: dependency: transitive description: @@ -354,4 +527,4 @@ packages: version: "6.5.0" sdks: dart: ">=3.4.0 <4.0.0" - flutter: ">=3.18.0-18.0.pre.54" + flutter: ">=3.22.0" diff --git a/pubspec.yaml b/pubspec.yaml index 5b17566f..84c96f79 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -39,6 +39,9 @@ dependencies: equatable: ^2.0.5 graphview: ^1.2.0 flutter_svg: ^2.0.10+1 + dio: ^5.5.0+1 + get_it: ^7.6.7 + flutter_secure_storage: ^9.2.2 dev_dependencies: diff --git a/windows/flutter/generated_plugin_registrant.cc b/windows/flutter/generated_plugin_registrant.cc index 8b6d4680..0c507538 100644 --- a/windows/flutter/generated_plugin_registrant.cc +++ b/windows/flutter/generated_plugin_registrant.cc @@ -6,6 +6,9 @@ #include "generated_plugin_registrant.h" +#include void RegisterPlugins(flutter::PluginRegistry* registry) { + FlutterSecureStorageWindowsPluginRegisterWithRegistrar( + registry->GetRegistrarForPlugin("FlutterSecureStorageWindowsPlugin")); } diff --git a/windows/flutter/generated_plugins.cmake b/windows/flutter/generated_plugins.cmake index b93c4c30..4fc759c4 100644 --- a/windows/flutter/generated_plugins.cmake +++ b/windows/flutter/generated_plugins.cmake @@ -3,6 +3,7 @@ # list(APPEND FLUTTER_PLUGIN_LIST + flutter_secure_storage_windows ) list(APPEND FLUTTER_FFI_PLUGIN_LIST