fix: the login request into dio and fix the model

This commit is contained in:
akhdanre 2025-04-22 19:34:03 +07:00
parent f479acac91
commit 837823f937
12 changed files with 344 additions and 49 deletions

View File

@ -1,9 +1,11 @@
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:quiz_app/data/providers/dio_client.dart'; import 'package:quiz_app/data/providers/dio_client.dart';
import 'package:quiz_app/data/services/user_storage_service.dart';
class InitialBindings extends Bindings { class InitialBindings extends Bindings {
@override @override
void dependencies() { void dependencies() {
Get.put<UserStorageService>(UserStorageService());
Get.putAsync(() => ApiClient().init()); Get.putAsync(() => ApiClient().init());
} }
} }

View File

@ -1,11 +1,15 @@
import 'package:flutter/material.dart'; 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/app/routes/app_pages.dart';
import 'package:quiz_app/data/services/user_storage_service.dart';
class AuthMiddleware extends GetMiddleware { class AuthMiddleware extends GetMiddleware {
@override @override
RouteSettings? redirect(String? route) { 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; return null;
} }
} }

View File

@ -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'],
);
}
}

View File

@ -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,
};
}
}

View File

@ -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(),
};
}
}

View File

@ -1,6 +1,9 @@
import 'package:dio/dio.dart'; import 'package:dio/dio.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:quiz_app/core/endpoint/api_endpoint.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/models/register/register_request.dart';
import 'package:quiz_app/data/providers/dio_client.dart'; import 'package:quiz_app/data/providers/dio_client.dart';
@ -13,11 +16,47 @@ class AuthService extends GetxService {
super.onInit(); super.onInit();
} }
Future<void> register(RegisterRequestModel request) async { Future<bool> register(RegisterRequestModel request) async {
var data = await _dio.post( var data = await _dio.post(
APIEndpoint.register, APIEndpoint.register,
data: request.toJson(), 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");
}
} }
} }

View File

@ -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);
}
}

View File

@ -1,10 +1,13 @@
import 'package:get/get_core/get_core.dart'; import 'package:get/get_core/get_core.dart';
import 'package:get/get_instance/get_instance.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'; import 'package:quiz_app/feature/login/controllers/login_controller.dart';
class LoginBinding extends Bindings { class LoginBinding extends Bindings {
@override @override
void dependencies() { void dependencies() {
Get.lazyPut(() => LoginController()); Get.lazyPut(() => AuthService());
Get.lazyPut(() => LoginController(Get.find<AuthService>(), Get.find<UserStorageService>()));
} }
} }

View File

@ -1,21 +1,26 @@
import 'dart:convert';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:http/http.dart' as http;
import 'package:google_sign_in/google_sign_in.dart'; import 'package:google_sign_in/google_sign_in.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:quiz_app/app/routes/app_pages.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/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 { class LoginController extends GetxController {
final AuthService _authService;
final UserStorageService _userStorageService;
LoginController(this._authService, this._userStorageService);
final TextEditingController emailController = TextEditingController(); final TextEditingController emailController = TextEditingController();
final TextEditingController passwordController = TextEditingController(); final TextEditingController passwordController = TextEditingController();
var isPasswordHidden = true.obs; var isPasswordHidden = true.obs;
var isLoading = false.obs; // Loading state for UI var isLoading = false.obs;
final GoogleSignIn _googleSignIn = GoogleSignIn( final GoogleSignIn _googleSignIn = GoogleSignIn(
scopes: ['email', 'profile', 'openid'], scopes: ['email', 'profile', 'openid'],
); // Singleton instance );
void togglePasswordVisibility() { void togglePasswordVisibility() {
isPasswordHidden.value = !isPasswordHidden.value; isPasswordHidden.value = !isPasswordHidden.value;
@ -34,19 +39,18 @@ class LoginController extends GetxController {
try { try {
isLoading.value = true; isLoading.value = true;
var response = await http.post( LoginResponseModel response = await _authService.loginWithEmail(
Uri.parse("${APIEndpoint.baseUrl}${APIEndpoint.login}"), LoginRequestModel(
body: jsonEncode({"email": email, "password": password}), email: email,
headers: {"Content-Type": "application/json"}, password: password,
),
); );
if (response.statusCode == 200) { await _userStorageService.saveUser(response);
var data = jsonDecode(response.body);
} else { _userStorageService.isLogged = true;
var errorMsg = jsonDecode(response.body)['message'] ?? "Invalid credentials";
logC.i(errorMsg); Get.toNamed(AppRoutes.homePage);
Get.snackbar("Error", errorMsg);
}
} catch (e, stackTrace) { } catch (e, stackTrace) {
logC.e(e, stackTrace: stackTrace); logC.e(e, stackTrace: stackTrace);
Get.snackbar("Error", "Failed to connect to server"); Get.snackbar("Error", "Failed to connect to server");
@ -63,10 +67,6 @@ class LoginController extends GetxController {
return; 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; final GoogleSignInAuthentication googleAuth = await googleUser.authentication;
if (googleAuth.idToken == null || googleAuth.idToken!.isEmpty) { if (googleAuth.idToken == null || googleAuth.idToken!.isEmpty) {
@ -75,25 +75,13 @@ class LoginController extends GetxController {
} }
String idToken = googleAuth.idToken!; String idToken = googleAuth.idToken!;
logC.i("Google ID Token: $idToken");
// Send ID Token to backend final response = await _authService.loginWithGoogle(idToken);
var response = await http.post( await _userStorageService.saveUser(response);
Uri.parse("${APIEndpoint.baseUrl}${APIEndpoint.loginGoogle}"),
body: jsonEncode({"token_id": idToken}),
headers: {"Content-Type": "application/json"},
);
if (response.statusCode == 200) { _userStorageService.isLogged = true;
var data = jsonDecode(response.body);
Get.snackbar("Success", "Google login successful!"); Get.toNamed(AppRoutes.homePage);
// logC.i("Backend Auth Token: $backendToken");
} else {
var errorMsg = jsonDecode(response.body)['message'] ?? "Google login failed";
Get.snackbar("Error", errorMsg);
logC.i(errorMsg);
}
} catch (e, stackTrace) { } catch (e, stackTrace) {
logC.e("Google Sign-In Error: $e", stackTrace: stackTrace); logC.e("Google Sign-In Error: $e", stackTrace: stackTrace);
Get.snackbar("Error", "Google sign-in error"); Get.snackbar("Error", "Google sign-in error");

View File

@ -2,21 +2,36 @@ import 'package:flutter/material.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:quiz_app/app/routes/app_pages.dart'; import 'package:quiz_app/app/routes/app_pages.dart';
import 'package:quiz_app/component/app_name.dart'; import 'package:quiz_app/component/app_name.dart';
import 'package:quiz_app/data/services/user_storage_service.dart';
class SplashScreenView extends StatelessWidget { class SplashScreenView extends StatelessWidget {
const SplashScreenView({super.key}); 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 @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
// Delay navigation after the first frame is rendered // Jalankan navigasi setelah frame pertama selesai dirender
WidgetsBinding.instance.addPostFrameCallback((_) { WidgetsBinding.instance.addPostFrameCallback((_) {
Future.delayed(const Duration(seconds: 2), () { _navigate();
Get.offNamed(AppRoutes.homePage);
});
}); });
return Scaffold( return const Scaffold(
body: Center(child: AppName()), body: Center(
child: AppName(),
),
); );
} }
} }

View File

@ -73,6 +73,22 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.3.1" 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: flutter:
dependency: "direct main" dependency: "direct main"
description: flutter description: flutter
@ -161,7 +177,7 @@ packages:
source: hosted source: hosted
version: "0.12.4+3" version: "0.12.4+3"
http: http:
dependency: "direct main" dependency: transitive
description: description:
name: http name: http
sha256: fe7ab022b76f3034adc518fb6ea04a82387620e19977665ea18d30a1cf43442f sha256: fe7ab022b76f3034adc518fb6ea04a82387620e19977665ea18d30a1cf43442f
@ -248,6 +264,38 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.9.0" 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: plugin_platform_interface:
dependency: transitive dependency: transitive
description: description:
@ -256,6 +304,62 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.1.8" 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: sky_engine:
dependency: transitive dependency: transitive
description: flutter description: flutter
@ -341,6 +445,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.1.1" 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: sdks:
dart: ">=3.6.0 <4.0.0" dart: ">=3.6.0 <4.0.0"
flutter: ">=3.24.0" flutter: ">=3.27.0"

View File

@ -39,6 +39,7 @@ dependencies:
google_sign_in: ^6.2.2 google_sign_in: ^6.2.2
flutter_dotenv: ^5.2.1 flutter_dotenv: ^5.2.1
dio: ^5.8.0+1 dio: ^5.8.0+1
shared_preferences: ^2.5.3
dev_dependencies: dev_dependencies:
flutter_test: flutter_test: