From 5787cea803cb8c629b195500f864dbcb46d7ae7c Mon Sep 17 00:00:00 2001 From: akhdanre Date: Sat, 1 Mar 2025 13:03:55 +0700 Subject: [PATCH] feat: login page --- assets/logo/google_logo.png | Bin 0 -> 16590 bytes lib/app/routes/app_pages.dart | 2 + lib/component/global_text_field.dart | 18 +++++ lib/core/endpoint/api_endpoint.dart | 6 ++ lib/feature/login/bindings/login_binding.dart | 10 +++ .../login/controllers/login_controller.dart | 69 ++++++++++++++++++ .../login/presentation/login_page.dart | 67 +++++++++++++++-- pubspec.lock | 2 +- pubspec.yaml | 5 +- 9 files changed, 172 insertions(+), 7 deletions(-) create mode 100644 assets/logo/google_logo.png create mode 100644 lib/component/global_text_field.dart create mode 100644 lib/core/endpoint/api_endpoint.dart create mode 100644 lib/feature/login/bindings/login_binding.dart create mode 100644 lib/feature/login/controllers/login_controller.dart diff --git a/assets/logo/google_logo.png b/assets/logo/google_logo.png new file mode 100644 index 0000000000000000000000000000000000000000..a4a9918dad10832bc8aaa5574d71fe15f089c8cb GIT binary patch literal 16590 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 literal 0 HcmV?d00001 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