diff --git a/.gitignore b/.gitignore index b6323af..69216e7 100644 --- a/.gitignore +++ b/.gitignore @@ -45,4 +45,6 @@ app.*.map.json /android/app/release # FVM Version Cache -.fvm/ \ No newline at end of file +.fvm/ + +*.env \ No newline at end of file diff --git a/android/app/build.gradle b/android/app/build.gradle index 2648e2a..c913db7 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -30,8 +30,18 @@ android { versionName = flutter.versionName } + signingConfigs { + debug { + keyAlias = "keyDebugQuiz" + keyPassword = "uppercase12" + storeFile = file("debugKeystore.jks") + storePassword = "uppercase12" + + } + } + buildTypes { - release { + debug { // TODO: Add your own signing config for the release build. // Signing with the debug keys for now, so `flutter run --release` works. signingConfig = signingConfigs.debug diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 9e55388..3d04507 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -1,4 +1,5 @@ + + android:name="io.flutter.embedding.android.NormalTheme" + android:resource="@style/NormalTheme" + /> - - + + - - + + - + \ No newline at end of file diff --git a/lib/core/endpoint/api_endpoint.dart b/lib/core/endpoint/api_endpoint.dart index e46fda5..e75ad03 100644 --- a/lib/core/endpoint/api_endpoint.dart +++ b/lib/core/endpoint/api_endpoint.dart @@ -1,5 +1,5 @@ class APIEndpoint { - static const String baseUrl = "http://127.0.0.1:8000/api"; + static const String baseUrl = "http://192.168.1.9:5000/api"; static const String login = "/login"; static const String loginGoogle = "/login/google"; diff --git a/lib/feature/login/controllers/login_controller.dart b/lib/feature/login/controllers/login_controller.dart index ebcd649..9fc790c 100644 --- a/lib/feature/login/controllers/login_controller.dart +++ b/lib/feature/login/controllers/login_controller.dart @@ -4,26 +4,35 @@ 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/core/endpoint/api_endpoint.dart'; +import 'package:quiz_app/core/utils/logger.dart'; class LoginController extends GetxController { final TextEditingController emailController = TextEditingController(); final TextEditingController passwordController = TextEditingController(); var isPasswordHidden = true.obs; + var isLoading = false.obs; // Loading state for UI + final GoogleSignIn _googleSignIn = GoogleSignIn(); // Singleton instance + void togglePasswordVisibility() { isPasswordHidden.value = !isPasswordHidden.value; } - final GoogleSignIn _googleSignIn = GoogleSignIn(); - - // Login menggunakan Flask Backend (Email & Password) + /// **🔹 Login via Email & Password** Future loginWithEmail() async { String email = emailController.text.trim(); String password = passwordController.text.trim(); + if (email.isEmpty || password.isEmpty) { + Get.snackbar("Error", "Email and password are required"); + return; + } + try { + isLoading.value = true; + var response = await http.post( - Uri.parse(APIEndpoint.baseUrl + APIEndpoint.login), + Uri.parse("${APIEndpoint.baseUrl}${APIEndpoint.login}"), body: jsonEncode({"email": email, "password": password}), headers: {"Content-Type": "application/json"}, ); @@ -31,39 +40,80 @@ class LoginController extends GetxController { if (response.statusCode == 200) { var data = jsonDecode(response.body); String token = data['token']; + + // await _secureStorage.write(key: "auth_token", value: token); + Get.snackbar("Success", "Login successful!"); + logC.i("Login Token: $token"); } else { - Get.snackbar("Error", "Invalid email or password"); + var errorMsg = jsonDecode(response.body)['message'] ?? "Invalid credentials"; + Get.snackbar("Error", errorMsg); } - } catch (e) { + } catch (e, stackTrace) { + logC.e(e, stackTrace: stackTrace); Get.snackbar("Error", "Failed to connect to server"); + } finally { + isLoading.value = false; } } - // Login menggunakan Google (Tanpa Firebase) Future loginWithGoogle() async { try { final GoogleSignInAccount? googleUser = await _googleSignIn.signIn(); - if (googleUser == null) return; + if (googleUser == null) { + Get.snackbar("Error", "Google Sign-In canceled"); + 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; - String idToken = googleAuth.idToken ?? ""; + + logC.i("Google Access Token: ${googleAuth.accessToken}"); + logC.i("Google ID Token: ${googleAuth.idToken}"); + + if (googleAuth.idToken == null || googleAuth.idToken!.isEmpty) { + Get.snackbar("Error", "Google sign-in failed. No token received."); + return; + } var response = await http.post( - Uri.parse(APIEndpoint.baseUrl + APIEndpoint.loginGoogle), - body: jsonEncode({"token": idToken}), + Uri.parse("${APIEndpoint.baseUrl}${APIEndpoint.loginGoogle}"), + body: jsonEncode({"token": googleAuth.idToken}), headers: {"Content-Type": "application/json"}, ); if (response.statusCode == 200) { var data = jsonDecode(response.body); - String token = data['token']; // Simpan token untuk sesi login + String token = data['token']; + Get.snackbar("Success", "Google login successful!"); + logC.i("Google Login Token: $token"); } else { - Get.snackbar("Error", "Google login failed"); + var errorMsg = jsonDecode(response.body)['message'] ?? "Google login failed"; + Get.snackbar("Error", errorMsg); } - } catch (e) { + } catch (e, stackTrace) { + logC.e("Google Sign-In Error: $e", stackTrace: stackTrace); Get.snackbar("Error", "Google sign-in error"); } } + + /// **🔹 Logout Function** + Future logout() async { + try { + await _googleSignIn.signOut(); + // await _secureStorage.delete(key: "auth_token"); + + emailController.clear(); + passwordController.clear(); + + Get.snackbar("Success", "Logged out successfully"); + } catch (e) { + logC.e("Logout error: $e"); + Get.snackbar("Error", "Logout failed"); + } + } } diff --git a/lib/feature/login/presentation/login_page.dart b/lib/feature/login/presentation/login_page.dart index cdcd2d0..3dc263c 100644 --- a/lib/feature/login/presentation/login_page.dart +++ b/lib/feature/login/presentation/login_page.dart @@ -48,7 +48,6 @@ class LoginView extends GetView { onPressed: () => controller.loginWithEmail(), child: const Text("Login with Email"), ), - const SizedBox(height: 10), // Google Sign-In Button @@ -65,6 +64,11 @@ class LoginView extends GetView { elevation: 2, ), ), + + ElevatedButton( + onPressed: () => controller.logout(), + child: const Text("log out"), + ), ], ), ), diff --git a/pubspec.lock b/pubspec.lock index e3bb302..75761a7 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -62,6 +62,14 @@ packages: description: flutter source: sdk version: "0.0.0" + flutter_dotenv: + dependency: "direct main" + description: + name: flutter_dotenv + sha256: b7c7be5cd9f6ef7a78429cabd2774d3c4af50e79cb2b7593e3d5d763ef95c61b + url: "https://pub.dev" + source: hosted + version: "5.2.1" flutter_lints: dependency: "direct dev" description: diff --git a/pubspec.yaml b/pubspec.yaml index 45f96f4..a723236 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -38,6 +38,7 @@ dependencies: google_sign_in: ^6.2.2 http: ^1.3.0 + flutter_dotenv: ^5.2.1 dev_dependencies: flutter_test: