fix: the login request into dio and fix the model
This commit is contained in:
parent
f479acac91
commit
837823f937
|
@ -1,9 +1,11 @@
|
|||
import 'package:get/get.dart';
|
||||
import 'package:quiz_app/data/providers/dio_client.dart';
|
||||
import 'package:quiz_app/data/services/user_storage_service.dart';
|
||||
|
||||
class InitialBindings extends Bindings {
|
||||
@override
|
||||
void dependencies() {
|
||||
Get.put<UserStorageService>(UserStorageService());
|
||||
Get.putAsync(() => ApiClient().init());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,11 +1,15 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get_navigation/src/routes/route_middleware.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:quiz_app/app/routes/app_pages.dart';
|
||||
import 'package:quiz_app/data/services/user_storage_service.dart';
|
||||
|
||||
class AuthMiddleware extends GetMiddleware {
|
||||
@override
|
||||
RouteSettings? redirect(String? route) {
|
||||
if (route != null) return RouteSettings(name: AppRoutes.loginPage);
|
||||
final UserStorageService _storageService = Get.find();
|
||||
if (!_storageService.isLogged) {
|
||||
return const RouteSettings(name: AppRoutes.loginPage);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
class BaseResponseModel<T> {
|
||||
final String message;
|
||||
final T? data;
|
||||
final dynamic meta;
|
||||
|
||||
BaseResponseModel({
|
||||
required this.message,
|
||||
this.data,
|
||||
this.meta,
|
||||
});
|
||||
|
||||
factory BaseResponseModel.fromJson(
|
||||
Map<String, dynamic> json,
|
||||
T Function(Map<String, dynamic>) fromJsonT,
|
||||
) {
|
||||
return BaseResponseModel<T>(
|
||||
message: json['message'],
|
||||
data: json['data'] != null ? fromJsonT(json['data']) : null,
|
||||
meta: json['meta'],
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
class LoginRequestModel {
|
||||
final String email;
|
||||
final String password;
|
||||
|
||||
LoginRequestModel({
|
||||
required this.email,
|
||||
required this.password,
|
||||
});
|
||||
|
||||
factory LoginRequestModel.fromJson(Map<String, dynamic> json) {
|
||||
return LoginRequestModel(
|
||||
email: json['email'] ?? '',
|
||||
password: json['password'] ?? '',
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'email': email,
|
||||
'password': password,
|
||||
};
|
||||
}
|
||||
}
|
|
@ -0,0 +1,55 @@
|
|||
class LoginResponseModel {
|
||||
final String? id;
|
||||
final String? googleId;
|
||||
final String email;
|
||||
final String name;
|
||||
final DateTime? birthDate;
|
||||
final String? picUrl;
|
||||
final String? phone;
|
||||
final String locale;
|
||||
// final DateTime? createdAt;
|
||||
// final DateTime? updatedAt;
|
||||
|
||||
LoginResponseModel({
|
||||
this.id,
|
||||
this.googleId,
|
||||
required this.email,
|
||||
required this.name,
|
||||
this.birthDate,
|
||||
this.picUrl,
|
||||
this.phone,
|
||||
this.locale = "en-US",
|
||||
// this.createdAt,
|
||||
// this.updatedAt,
|
||||
});
|
||||
|
||||
factory LoginResponseModel.fromJson(Map<String, dynamic> json) {
|
||||
return LoginResponseModel(
|
||||
id: json['_id'],
|
||||
googleId: json['google_id'],
|
||||
email: json['email'],
|
||||
name: json['name'],
|
||||
birthDate: json['birth_date'] != null ? DateTime.parse(json['birth_date']) : null,
|
||||
picUrl: json['pic_url'],
|
||||
phone: json['phone'],
|
||||
locale: json['locale'] ?? 'en-US',
|
||||
// createdAt: json['created_at'] != null ? DateTime.parse(json['created_at']) : null,
|
||||
// updatedAt: json['updated_at'] != null ? DateTime.parse(json['updated_at']) : null,
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'_id': id,
|
||||
'google_id': googleId,
|
||||
'email': email,
|
||||
'name': name,
|
||||
'birth_date': birthDate?.toIso8601String(),
|
||||
'pic_url': picUrl,
|
||||
'phone': phone,
|
||||
'locale': locale,
|
||||
// 'created_at': createdAt?.toIso8601String(),
|
||||
// 'updated_at': updatedAt?.toIso8601String(),
|
||||
};
|
||||
}
|
||||
}
|
|
@ -1,6 +1,9 @@
|
|||
import 'package:dio/dio.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:quiz_app/core/endpoint/api_endpoint.dart';
|
||||
import 'package:quiz_app/data/models/base/base_model.dart';
|
||||
import 'package:quiz_app/data/models/login/login_request_model.dart';
|
||||
import 'package:quiz_app/data/models/login/login_response_model.dart';
|
||||
import 'package:quiz_app/data/models/register/register_request.dart';
|
||||
import 'package:quiz_app/data/providers/dio_client.dart';
|
||||
|
||||
|
@ -13,11 +16,47 @@ class AuthService extends GetxService {
|
|||
super.onInit();
|
||||
}
|
||||
|
||||
Future<void> register(RegisterRequestModel request) async {
|
||||
Future<bool> register(RegisterRequestModel request) async {
|
||||
var data = await _dio.post(
|
||||
APIEndpoint.register,
|
||||
data: request.toJson(),
|
||||
);
|
||||
print(data);
|
||||
if (data.statusCode == 200) {
|
||||
return true;
|
||||
} else {
|
||||
throw Exception("Registration failed");
|
||||
}
|
||||
}
|
||||
|
||||
Future<LoginResponseModel> loginWithEmail(LoginRequestModel request) async {
|
||||
final data = request.toJson();
|
||||
final response = await _dio.post(APIEndpoint.login, data: data);
|
||||
|
||||
if (response.statusCode == 200) {
|
||||
final baseResponse = BaseResponseModel<LoginResponseModel>.fromJson(
|
||||
response.data,
|
||||
(json) => LoginResponseModel.fromJson(json),
|
||||
);
|
||||
return baseResponse.data!;
|
||||
} else {
|
||||
throw Exception("Login failed");
|
||||
}
|
||||
}
|
||||
|
||||
Future<LoginResponseModel> loginWithGoogle(String idToken) async {
|
||||
final response = await _dio.post(
|
||||
APIEndpoint.loginGoogle,
|
||||
data: {"token_id": idToken},
|
||||
);
|
||||
|
||||
if (response.statusCode == 200) {
|
||||
final baseResponse = BaseResponseModel<LoginResponseModel>.fromJson(
|
||||
response.data,
|
||||
(json) => LoginResponseModel.fromJson(json),
|
||||
);
|
||||
return baseResponse.data!;
|
||||
} else {
|
||||
throw Exception("Google login failed");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,31 @@
|
|||
import 'dart:convert';
|
||||
import 'package:quiz_app/data/models/login/login_response_model.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
|
||||
class UserStorageService {
|
||||
static const _userKey = 'user_data';
|
||||
bool isLogged = false;
|
||||
|
||||
Future<void> saveUser(LoginResponseModel user) async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
await prefs.setString(_userKey, jsonEncode(user.toJson()));
|
||||
}
|
||||
|
||||
Future<LoginResponseModel?> loadUser() async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
final jsonString = prefs.getString(_userKey);
|
||||
|
||||
if (jsonString == null) return null;
|
||||
return LoginResponseModel.fromJson(jsonDecode(jsonString));
|
||||
}
|
||||
|
||||
Future<void> clearUser() async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
await prefs.remove(_userKey);
|
||||
}
|
||||
|
||||
Future<bool> isLoggedIn() async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
return prefs.containsKey(_userKey);
|
||||
}
|
||||
}
|
|
@ -1,10 +1,13 @@
|
|||
import 'package:get/get_core/get_core.dart';
|
||||
import 'package:get/get_instance/get_instance.dart';
|
||||
import 'package:quiz_app/data/services/auth_service.dart';
|
||||
import 'package:quiz_app/data/services/user_storage_service.dart';
|
||||
import 'package:quiz_app/feature/login/controllers/login_controller.dart';
|
||||
|
||||
class LoginBinding extends Bindings {
|
||||
@override
|
||||
void dependencies() {
|
||||
Get.lazyPut(() => LoginController());
|
||||
Get.lazyPut(() => AuthService());
|
||||
Get.lazyPut(() => LoginController(Get.find<AuthService>(), Get.find<UserStorageService>()));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,21 +1,26 @@
|
|||
import 'dart:convert';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:google_sign_in/google_sign_in.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:quiz_app/app/routes/app_pages.dart';
|
||||
import 'package:quiz_app/core/endpoint/api_endpoint.dart';
|
||||
import 'package:quiz_app/core/utils/logger.dart';
|
||||
import 'package:quiz_app/data/models/login/login_request_model.dart';
|
||||
import 'package:quiz_app/data/models/login/login_response_model.dart';
|
||||
import 'package:quiz_app/data/services/auth_service.dart';
|
||||
import 'package:quiz_app/data/services/user_storage_service.dart';
|
||||
|
||||
class LoginController extends GetxController {
|
||||
final AuthService _authService;
|
||||
final UserStorageService _userStorageService;
|
||||
|
||||
LoginController(this._authService, this._userStorageService);
|
||||
final TextEditingController emailController = TextEditingController();
|
||||
final TextEditingController passwordController = TextEditingController();
|
||||
|
||||
var isPasswordHidden = true.obs;
|
||||
var isLoading = false.obs; // Loading state for UI
|
||||
var isLoading = false.obs;
|
||||
final GoogleSignIn _googleSignIn = GoogleSignIn(
|
||||
scopes: ['email', 'profile', 'openid'],
|
||||
); // Singleton instance
|
||||
);
|
||||
|
||||
void togglePasswordVisibility() {
|
||||
isPasswordHidden.value = !isPasswordHidden.value;
|
||||
|
@ -34,19 +39,18 @@ class LoginController extends GetxController {
|
|||
try {
|
||||
isLoading.value = true;
|
||||
|
||||
var response = await http.post(
|
||||
Uri.parse("${APIEndpoint.baseUrl}${APIEndpoint.login}"),
|
||||
body: jsonEncode({"email": email, "password": password}),
|
||||
headers: {"Content-Type": "application/json"},
|
||||
LoginResponseModel response = await _authService.loginWithEmail(
|
||||
LoginRequestModel(
|
||||
email: email,
|
||||
password: password,
|
||||
),
|
||||
);
|
||||
|
||||
if (response.statusCode == 200) {
|
||||
var data = jsonDecode(response.body);
|
||||
} else {
|
||||
var errorMsg = jsonDecode(response.body)['message'] ?? "Invalid credentials";
|
||||
logC.i(errorMsg);
|
||||
Get.snackbar("Error", errorMsg);
|
||||
}
|
||||
await _userStorageService.saveUser(response);
|
||||
|
||||
_userStorageService.isLogged = true;
|
||||
|
||||
Get.toNamed(AppRoutes.homePage);
|
||||
} catch (e, stackTrace) {
|
||||
logC.e(e, stackTrace: stackTrace);
|
||||
Get.snackbar("Error", "Failed to connect to server");
|
||||
|
@ -63,10 +67,6 @@ class LoginController extends GetxController {
|
|||
return;
|
||||
}
|
||||
|
||||
logC.i("Google User ID: ${googleUser.id}");
|
||||
logC.i("Google User Email: ${googleUser.email}");
|
||||
logC.i("Google User Display Name: ${googleUser.displayName}");
|
||||
|
||||
final GoogleSignInAuthentication googleAuth = await googleUser.authentication;
|
||||
|
||||
if (googleAuth.idToken == null || googleAuth.idToken!.isEmpty) {
|
||||
|
@ -75,25 +75,13 @@ class LoginController extends GetxController {
|
|||
}
|
||||
|
||||
String idToken = googleAuth.idToken!;
|
||||
logC.i("Google ID Token: $idToken");
|
||||
|
||||
// Send ID Token to backend
|
||||
var response = await http.post(
|
||||
Uri.parse("${APIEndpoint.baseUrl}${APIEndpoint.loginGoogle}"),
|
||||
body: jsonEncode({"token_id": idToken}),
|
||||
headers: {"Content-Type": "application/json"},
|
||||
);
|
||||
final response = await _authService.loginWithGoogle(idToken);
|
||||
await _userStorageService.saveUser(response);
|
||||
|
||||
if (response.statusCode == 200) {
|
||||
var data = jsonDecode(response.body);
|
||||
_userStorageService.isLogged = true;
|
||||
|
||||
Get.snackbar("Success", "Google login successful!");
|
||||
// logC.i("Backend Auth Token: $backendToken");
|
||||
} else {
|
||||
var errorMsg = jsonDecode(response.body)['message'] ?? "Google login failed";
|
||||
Get.snackbar("Error", errorMsg);
|
||||
logC.i(errorMsg);
|
||||
}
|
||||
Get.toNamed(AppRoutes.homePage);
|
||||
} catch (e, stackTrace) {
|
||||
logC.e("Google Sign-In Error: $e", stackTrace: stackTrace);
|
||||
Get.snackbar("Error", "Google sign-in error");
|
||||
|
|
|
@ -2,21 +2,36 @@ import 'package:flutter/material.dart';
|
|||
import 'package:get/get.dart';
|
||||
import 'package:quiz_app/app/routes/app_pages.dart';
|
||||
import 'package:quiz_app/component/app_name.dart';
|
||||
import 'package:quiz_app/data/services/user_storage_service.dart';
|
||||
|
||||
class SplashScreenView extends StatelessWidget {
|
||||
const SplashScreenView({super.key});
|
||||
|
||||
void _navigate() async {
|
||||
final storageService = Get.find<UserStorageService>();
|
||||
final isLoggedIn = await storageService.isLoggedIn();
|
||||
storageService.isLogged = isLoggedIn;
|
||||
|
||||
await Future.delayed(const Duration(seconds: 2));
|
||||
|
||||
if (isLoggedIn) {
|
||||
Get.offNamed(AppRoutes.homePage);
|
||||
} else {
|
||||
Get.offNamed(AppRoutes.loginPage);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
// Delay navigation after the first frame is rendered
|
||||
// Jalankan navigasi setelah frame pertama selesai dirender
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
Future.delayed(const Duration(seconds: 2), () {
|
||||
Get.offNamed(AppRoutes.homePage);
|
||||
});
|
||||
_navigate();
|
||||
});
|
||||
|
||||
return Scaffold(
|
||||
body: Center(child: AppName()),
|
||||
return const Scaffold(
|
||||
body: Center(
|
||||
child: AppName(),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
116
pubspec.lock
116
pubspec.lock
|
@ -73,6 +73,22 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.3.1"
|
||||
ffi:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: ffi
|
||||
sha256: "16ed7b077ef01ad6170a3d0c57caa4a112a38d7a2ed5602e0aca9ca6f3d98da6"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.3"
|
||||
file:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: file
|
||||
sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "7.0.1"
|
||||
flutter:
|
||||
dependency: "direct main"
|
||||
description: flutter
|
||||
|
@ -161,7 +177,7 @@ packages:
|
|||
source: hosted
|
||||
version: "0.12.4+3"
|
||||
http:
|
||||
dependency: "direct main"
|
||||
dependency: transitive
|
||||
description:
|
||||
name: http
|
||||
sha256: fe7ab022b76f3034adc518fb6ea04a82387620e19977665ea18d30a1cf43442f
|
||||
|
@ -248,6 +264,38 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.9.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"
|
||||
platform:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: platform
|
||||
sha256: "5d6b1b0036a5f331ebc77c850ebc8506cbc1e9416c27e59b439f917a902a4984"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.1.6"
|
||||
plugin_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -256,6 +304,62 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.8"
|
||||
shared_preferences:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: shared_preferences
|
||||
sha256: "6e8bf70b7fef813df4e9a36f658ac46d107db4b4cfe1048b477d4e453a8159f5"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.5.3"
|
||||
shared_preferences_android:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shared_preferences_android
|
||||
sha256: "20cbd561f743a342c76c151d6ddb93a9ce6005751e7aa458baad3858bfbfb6ac"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.4.10"
|
||||
shared_preferences_foundation:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shared_preferences_foundation
|
||||
sha256: "6a52cfcdaeac77cad8c97b539ff688ccfc458c007b4db12be584fbe5c0e49e03"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.5.4"
|
||||
shared_preferences_linux:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shared_preferences_linux
|
||||
sha256: "580abfd40f415611503cae30adf626e6656dfb2f0cee8f465ece7b6defb40f2f"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.4.1"
|
||||
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: c49bd060261c9a3f0ff445892695d6212ff603ef3115edbb448509d407600019
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.4.3"
|
||||
shared_preferences_windows:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shared_preferences_windows
|
||||
sha256: "94ef0f72b2d71bc3e700e025db3710911bd51a71cefb65cc609dd0d9a982e3c1"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.4.1"
|
||||
sky_engine:
|
||||
dependency: transitive
|
||||
description: flutter
|
||||
|
@ -341,6 +445,14 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.1"
|
||||
xdg_directories:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: xdg_directories
|
||||
sha256: "7a3f37b05d989967cdddcbb571f1ea834867ae2faa29725fd085180e0883aa15"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.0"
|
||||
sdks:
|
||||
dart: ">=3.6.0 <4.0.0"
|
||||
flutter: ">=3.24.0"
|
||||
flutter: ">=3.27.0"
|
||||
|
|
|
@ -39,6 +39,7 @@ dependencies:
|
|||
google_sign_in: ^6.2.2
|
||||
flutter_dotenv: ^5.2.1
|
||||
dio: ^5.8.0+1
|
||||
shared_preferences: ^2.5.3
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
|
|
Loading…
Reference in New Issue