diff --git a/assets/logo/google_logo.png b/assets/logo/google_logo.png new file mode 100644 index 0000000..a4a9918 Binary files /dev/null and b/assets/logo/google_logo.png differ diff --git a/lib/app/routes/app_pages.dart b/lib/app/routes/app_pages.dart index 88c40c8..aaa8fd8 100644 --- a/lib/app/routes/app_pages.dart +++ b/lib/app/routes/app_pages.dart @@ -1,6 +1,7 @@ import 'package:get/get_navigation/src/routes/get_route.dart'; import 'package:quiz_app/app/middleware/auth_middleware.dart'; import 'package:quiz_app/feature/home/presentation/home_page.dart'; +import 'package:quiz_app/feature/login/bindings/login_binding.dart'; import 'package:quiz_app/feature/login/presentation/login_page.dart'; import 'package:quiz_app/feature/splash_screen/presentation/splash_screen_page.dart'; @@ -15,6 +16,7 @@ class AppPages { GetPage( name: AppRoutes.loginPage, page: () => LoginView(), + binding: LoginBinding(), ), GetPage( name: AppRoutes.homePage, diff --git a/lib/component/global_text_field.dart b/lib/component/global_text_field.dart new file mode 100644 index 0000000..5529475 --- /dev/null +++ b/lib/component/global_text_field.dart @@ -0,0 +1,18 @@ +import 'package:flutter/material.dart'; + +class GlobalTextField extends StatelessWidget { + final TextEditingController controller; + final String? hintText; + + const GlobalTextField({super.key, required this.controller, this.hintText}); + + @override + Widget build(BuildContext context) { + return TextField( + decoration: InputDecoration( + border: OutlineInputBorder(), + labelText: hintText, + ), + ); + } +} diff --git a/lib/core/endpoint/api_endpoint.dart b/lib/core/endpoint/api_endpoint.dart new file mode 100644 index 0000000..e46fda5 --- /dev/null +++ b/lib/core/endpoint/api_endpoint.dart @@ -0,0 +1,6 @@ +class APIEndpoint { + static const String baseUrl = "http://127.0.0.1:8000/api"; + + static const String login = "/login"; + static const String loginGoogle = "/login/google"; +} diff --git a/lib/feature/login/bindings/login_binding.dart b/lib/feature/login/bindings/login_binding.dart new file mode 100644 index 0000000..8ca14ce --- /dev/null +++ b/lib/feature/login/bindings/login_binding.dart @@ -0,0 +1,10 @@ +import 'package:get/get_core/get_core.dart'; +import 'package:get/get_instance/get_instance.dart'; +import 'package:quiz_app/feature/login/controllers/login_controller.dart'; + +class LoginBinding extends Bindings { + @override + void dependencies() { + Get.lazyPut(() => LoginController()); + } +} diff --git a/lib/feature/login/controllers/login_controller.dart b/lib/feature/login/controllers/login_controller.dart new file mode 100644 index 0000000..ebcd649 --- /dev/null +++ b/lib/feature/login/controllers/login_controller.dart @@ -0,0 +1,69 @@ +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/core/endpoint/api_endpoint.dart'; + +class LoginController extends GetxController { + final TextEditingController emailController = TextEditingController(); + final TextEditingController passwordController = TextEditingController(); + + var isPasswordHidden = true.obs; + void togglePasswordVisibility() { + isPasswordHidden.value = !isPasswordHidden.value; + } + + final GoogleSignIn _googleSignIn = GoogleSignIn(); + + // Login menggunakan Flask Backend (Email & Password) + Future loginWithEmail() async { + String email = emailController.text.trim(); + String password = passwordController.text.trim(); + + try { + var response = await http.post( + Uri.parse(APIEndpoint.baseUrl + APIEndpoint.login), + body: jsonEncode({"email": email, "password": password}), + headers: {"Content-Type": "application/json"}, + ); + + if (response.statusCode == 200) { + var data = jsonDecode(response.body); + String token = data['token']; + Get.snackbar("Success", "Login successful!"); + } else { + Get.snackbar("Error", "Invalid email or password"); + } + } catch (e) { + Get.snackbar("Error", "Failed to connect to server"); + } + } + + // Login menggunakan Google (Tanpa Firebase) + Future loginWithGoogle() async { + try { + final GoogleSignInAccount? googleUser = await _googleSignIn.signIn(); + if (googleUser == null) return; + + final GoogleSignInAuthentication googleAuth = await googleUser.authentication; + String idToken = googleAuth.idToken ?? ""; + + var response = await http.post( + Uri.parse(APIEndpoint.baseUrl + APIEndpoint.loginGoogle), + body: jsonEncode({"token": idToken}), + headers: {"Content-Type": "application/json"}, + ); + + if (response.statusCode == 200) { + var data = jsonDecode(response.body); + String token = data['token']; // Simpan token untuk sesi login + Get.snackbar("Success", "Google login successful!"); + } else { + Get.snackbar("Error", "Google login failed"); + } + } catch (e) { + Get.snackbar("Error", "Google sign-in error"); + } + } +} diff --git a/lib/feature/login/presentation/login_page.dart b/lib/feature/login/presentation/login_page.dart index 4a6bff7..cdcd2d0 100644 --- a/lib/feature/login/presentation/login_page.dart +++ b/lib/feature/login/presentation/login_page.dart @@ -1,15 +1,72 @@ import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:quiz_app/feature/login/controllers/login_controller.dart'; -class LoginView extends StatelessWidget { +class LoginView extends GetView { const LoginView({super.key}); @override Widget build(BuildContext context) { return Scaffold( - body: Center( - child: GestureDetector( - onTap: () {}, - child: Text("test work in background"), + body: SafeArea( + child: Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + const Text("Login Page", style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold)), + const SizedBox(height: 20), + + // Email Input + TextField( + controller: controller.emailController, + decoration: InputDecoration( + labelText: "Email", + border: OutlineInputBorder(), + ), + ), + const SizedBox(height: 10), + + // Password Input dengan fitur show/hide + Obx(() => TextField( + controller: controller.passwordController, + obscureText: controller.isPasswordHidden.value, + decoration: InputDecoration( + labelText: "Password", + border: OutlineInputBorder(), + suffixIcon: IconButton( + icon: Icon(controller.isPasswordHidden.value ? Icons.visibility_off : Icons.visibility), + onPressed: controller.togglePasswordVisibility, + ), + ), + )), + const SizedBox(height: 20), + + // Login Button + ElevatedButton( + onPressed: () => controller.loginWithEmail(), + child: const Text("Login with Email"), + ), + + const SizedBox(height: 10), + + // Google Sign-In Button + ElevatedButton.icon( + onPressed: () => controller.loginWithGoogle(), + icon: Image.asset( + 'assets/logo/google_logo.png', // Pastikan logo Google ada di folder assets + height: 24, + ), + label: const Text("Login with Google"), + style: ElevatedButton.styleFrom( + backgroundColor: Colors.white, + foregroundColor: Colors.black, + elevation: 2, + ), + ), + ], + ), ), ), ); diff --git a/pubspec.lock b/pubspec.lock index ee6dd09..e3bb302 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -137,7 +137,7 @@ packages: source: hosted version: "0.12.4+3" http: - dependency: transitive + dependency: "direct main" description: name: http sha256: fe7ab022b76f3034adc518fb6ea04a82387620e19977665ea18d30a1cf43442f diff --git a/pubspec.yaml b/pubspec.yaml index ff99772..45f96f4 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -37,6 +37,7 @@ dependencies: logger: ^2.5.0 google_sign_in: ^6.2.2 + http: ^1.3.0 dev_dependencies: flutter_test: @@ -60,7 +61,9 @@ flutter: uses-material-design: true # To add assets to your application, add an assets section, like this: - # assets: + assets: + - assets/ + - assets/logo/ # - images/a_dot_burr.jpeg # - images/a_dot_ham.jpeg