feat: login page
This commit is contained in:
parent
f1bb4abd6d
commit
5787cea803
Binary file not shown.
After Width: | Height: | Size: 16 KiB |
|
@ -1,6 +1,7 @@
|
||||||
import 'package:get/get_navigation/src/routes/get_route.dart';
|
import 'package:get/get_navigation/src/routes/get_route.dart';
|
||||||
import 'package:quiz_app/app/middleware/auth_middleware.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/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/login/presentation/login_page.dart';
|
||||||
import 'package:quiz_app/feature/splash_screen/presentation/splash_screen_page.dart';
|
import 'package:quiz_app/feature/splash_screen/presentation/splash_screen_page.dart';
|
||||||
|
|
||||||
|
@ -15,6 +16,7 @@ class AppPages {
|
||||||
GetPage(
|
GetPage(
|
||||||
name: AppRoutes.loginPage,
|
name: AppRoutes.loginPage,
|
||||||
page: () => LoginView(),
|
page: () => LoginView(),
|
||||||
|
binding: LoginBinding(),
|
||||||
),
|
),
|
||||||
GetPage(
|
GetPage(
|
||||||
name: AppRoutes.homePage,
|
name: AppRoutes.homePage,
|
||||||
|
|
|
@ -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,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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";
|
||||||
|
}
|
|
@ -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());
|
||||||
|
}
|
||||||
|
}
|
|
@ -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<void> 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<void> 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");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,15 +1,72 @@
|
||||||
import 'package:flutter/material.dart';
|
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<LoginController> {
|
||||||
const LoginView({super.key});
|
const LoginView({super.key});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
body: Center(
|
body: SafeArea(
|
||||||
child: GestureDetector(
|
child: Padding(
|
||||||
onTap: () {},
|
padding: const EdgeInsets.all(16.0),
|
||||||
child: Text("test work in background"),
|
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,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
|
@ -137,7 +137,7 @@ packages:
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.12.4+3"
|
version: "0.12.4+3"
|
||||||
http:
|
http:
|
||||||
dependency: transitive
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: http
|
name: http
|
||||||
sha256: fe7ab022b76f3034adc518fb6ea04a82387620e19977665ea18d30a1cf43442f
|
sha256: fe7ab022b76f3034adc518fb6ea04a82387620e19977665ea18d30a1cf43442f
|
||||||
|
|
|
@ -37,6 +37,7 @@ dependencies:
|
||||||
logger: ^2.5.0
|
logger: ^2.5.0
|
||||||
|
|
||||||
google_sign_in: ^6.2.2
|
google_sign_in: ^6.2.2
|
||||||
|
http: ^1.3.0
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
|
@ -60,7 +61,9 @@ flutter:
|
||||||
uses-material-design: true
|
uses-material-design: true
|
||||||
|
|
||||||
# To add assets to your application, add an assets section, like this:
|
# 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_burr.jpeg
|
||||||
# - images/a_dot_ham.jpeg
|
# - images/a_dot_ham.jpeg
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue