develop #1
|
@ -1,12 +1,14 @@
|
|||
import 'package:get/get.dart';
|
||||
import 'package:quiz_app/data/controllers/user_controller.dart';
|
||||
import 'package:quiz_app/data/providers/dio_client.dart';
|
||||
import 'package:quiz_app/data/services/connection_service.dart';
|
||||
import 'package:quiz_app/data/services/user_storage_service.dart';
|
||||
|
||||
class InitialBindings extends Bindings {
|
||||
@override
|
||||
void dependencies() {
|
||||
Get.put<UserStorageService>(UserStorageService());
|
||||
Get.put(ConnectionService());
|
||||
Get.putAsync(() => ApiClient().init());
|
||||
Get.put<UserController>(UserController(Get.find<UserStorageService>()));
|
||||
}
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
import 'package:quiz_app/core/utils/custom_notification.dart';
|
||||
|
||||
class ConnectionNotification {
|
||||
static void internetConnected() {
|
||||
CustomNotification.success(
|
||||
title: "Terkoneksi kembali",
|
||||
message: "Terhubugn dengan koneksi",
|
||||
);
|
||||
}
|
||||
|
||||
static void noInternedConnection() {
|
||||
CustomNotification.error(
|
||||
title: "Tidak ada internet",
|
||||
message: "cek kembali koneksi internet kamu",
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,56 @@
|
|||
import 'dart:async';
|
||||
import 'package:connectivity_plus/connectivity_plus.dart';
|
||||
import 'package:get/get.dart';
|
||||
|
||||
/// [ConnectionService] is a GetX Service that monitors internet connectivity status.
|
||||
///
|
||||
/// It utilizes the [Connectivity] class from the `connectivity_plus` package.
|
||||
class ConnectionService extends GetxService {
|
||||
final Connectivity _connectivity = Connectivity();
|
||||
|
||||
/// Subscription to the connectivity change stream.
|
||||
late StreamSubscription<List<ConnectivityResult>> _subscription;
|
||||
|
||||
/// Reactive boolean to indicate the current internet connection status.
|
||||
/// `true` means the device is connected to the internet via Wi-Fi, mobile data, or other means.
|
||||
final RxBool isConnected = true.obs;
|
||||
|
||||
bool get isCurrentlyConnected => isConnected.value;
|
||||
|
||||
/// Called when the service is first initialized.
|
||||
@override
|
||||
void onInit() {
|
||||
super.onInit();
|
||||
_initConnectivity();
|
||||
_subscription = _connectivity.onConnectivityChanged.listen(_updateConnectionStatus);
|
||||
}
|
||||
|
||||
/// Checks the initial connectivity status when the service is initialized.
|
||||
Future<void> _initConnectivity() async {
|
||||
try {
|
||||
final result = await _connectivity.checkConnectivity();
|
||||
_updateConnectionStatus(result); // Wrap in a list for consistency
|
||||
} catch (e) {
|
||||
isConnected.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
/// Callback function to handle changes in connectivity status.
|
||||
/// @param results A list of [ConnectivityResult] representing all active network connections.
|
||||
void _updateConnectionStatus(List<ConnectivityResult> results) {
|
||||
// If all results are `none`, the device is considered offline.
|
||||
isConnected.value = results.any((result) => result != ConnectivityResult.none);
|
||||
}
|
||||
|
||||
Future<bool> checkConnection() async {
|
||||
final result = await _connectivity.checkConnectivity();
|
||||
return !result.contains(ConnectivityResult.none);
|
||||
}
|
||||
|
||||
/// Cancels the connectivity subscription when the service is closed.
|
||||
@override
|
||||
void onClose() {
|
||||
_subscription.cancel();
|
||||
super.onClose();
|
||||
}
|
||||
}
|
|
@ -2,6 +2,7 @@ import 'package:get/get_core/get_core.dart';
|
|||
import 'package:get/get_instance/get_instance.dart';
|
||||
import 'package:quiz_app/data/controllers/user_controller.dart';
|
||||
import 'package:quiz_app/data/services/auth_service.dart';
|
||||
import 'package:quiz_app/data/services/connection_service.dart';
|
||||
import 'package:quiz_app/data/services/google_auth_service.dart';
|
||||
import 'package:quiz_app/data/services/user_storage_service.dart';
|
||||
import 'package:quiz_app/feature/login/controllers/login_controller.dart';
|
||||
|
@ -11,6 +12,14 @@ class LoginBinding extends Bindings {
|
|||
void dependencies() {
|
||||
Get.lazyPut<GoogleAuthService>(() => GoogleAuthService());
|
||||
Get.lazyPut(() => AuthService());
|
||||
Get.lazyPut(() => LoginController(Get.find<AuthService>(), Get.find<UserStorageService>(), Get.find<UserController>(), Get.find<GoogleAuthService>()));
|
||||
Get.lazyPut(
|
||||
() => LoginController(
|
||||
Get.find<AuthService>(),
|
||||
Get.find<UserStorageService>(),
|
||||
Get.find<UserController>(),
|
||||
Get.find<GoogleAuthService>(),
|
||||
Get.find<ConnectionService>(),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
|
|||
import 'package:get/get.dart';
|
||||
import 'package:quiz_app/app/routes/app_pages.dart';
|
||||
import 'package:quiz_app/component/global_button.dart';
|
||||
import 'package:quiz_app/core/helper/connection_check.dart';
|
||||
import 'package:quiz_app/core/utils/custom_notification.dart';
|
||||
import 'package:quiz_app/core/utils/logger.dart';
|
||||
import 'package:quiz_app/data/controllers/user_controller.dart';
|
||||
|
@ -9,6 +10,7 @@ import 'package:quiz_app/data/entity/user/user_entity.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/connection_service.dart';
|
||||
import 'package:quiz_app/data/services/google_auth_service.dart';
|
||||
import 'package:quiz_app/data/services/user_storage_service.dart';
|
||||
|
||||
|
@ -17,12 +19,14 @@ class LoginController extends GetxController {
|
|||
final UserStorageService _userStorageService;
|
||||
final UserController _userController;
|
||||
final GoogleAuthService _googleAuthService;
|
||||
final ConnectionService _connectionService;
|
||||
|
||||
LoginController(
|
||||
this._authService,
|
||||
this._userStorageService,
|
||||
this._userController,
|
||||
this._googleAuthService,
|
||||
this._connectionService,
|
||||
);
|
||||
|
||||
final TextEditingController emailController = TextEditingController();
|
||||
|
@ -37,8 +41,26 @@ class LoginController extends GetxController {
|
|||
super.onInit();
|
||||
emailController.addListener(validateFields);
|
||||
passwordController.addListener(validateFields);
|
||||
|
||||
ever(_connectionService.isConnected, (value) {
|
||||
if (!value) {
|
||||
ConnectionNotification.noInternedConnection();
|
||||
} else {
|
||||
ConnectionNotification.internetConnected();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void onReady() {
|
||||
if (!_connectionService.isCurrentlyConnected) {
|
||||
ConnectionNotification.noInternedConnection();
|
||||
}
|
||||
super.onReady();
|
||||
}
|
||||
|
||||
void checkConnection() async {}
|
||||
|
||||
void validateFields() {
|
||||
final isEmailNotEmpty = emailController.text.trim().isNotEmpty;
|
||||
final isPasswordNotEmpty = passwordController.text.trim().isNotEmpty;
|
||||
|
@ -59,6 +81,10 @@ class LoginController extends GetxController {
|
|||
return;
|
||||
}
|
||||
|
||||
if (!_connectionService.isCurrentlyConnected) {
|
||||
ConnectionNotification.noInternedConnection();
|
||||
return;
|
||||
}
|
||||
try {
|
||||
isLoading.value = true;
|
||||
|
||||
|
@ -81,8 +107,11 @@ class LoginController extends GetxController {
|
|||
}
|
||||
}
|
||||
|
||||
/// **🔹 Login via Google**
|
||||
Future<void> loginWithGoogle() async {
|
||||
if (!_connectionService.isCurrentlyConnected) {
|
||||
ConnectionNotification.noInternedConnection();
|
||||
return;
|
||||
}
|
||||
try {
|
||||
final user = await _googleAuthService.signIn();
|
||||
if (user == null) {
|
||||
|
@ -112,7 +141,6 @@ class LoginController extends GetxController {
|
|||
|
||||
void goToRegsPage() => Get.toNamed(AppRoutes.registerPage);
|
||||
|
||||
/// Helper untuk convert LoginResponseModel ke UserEntity
|
||||
UserEntity _convertLoginResponseToUserEntity(LoginResponseModel response) {
|
||||
logC.i("user id : ${response.id}");
|
||||
return UserEntity(
|
||||
|
|
|
@ -16,7 +16,7 @@ class LoginView extends GetView<LoginController> {
|
|||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
backgroundColor: AppColors.background, // background soft clean
|
||||
backgroundColor: AppColors.background,
|
||||
body: SafeArea(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 16),
|
||||
|
|
|
@ -1,11 +1,17 @@
|
|||
import 'package:get/get.dart';
|
||||
import 'package:quiz_app/data/services/auth_service.dart';
|
||||
import 'package:quiz_app/data/services/connection_service.dart';
|
||||
import 'package:quiz_app/feature/register/controller/register_controller.dart';
|
||||
|
||||
class RegisterBinding extends Bindings {
|
||||
@override
|
||||
void dependencies() {
|
||||
Get.lazyPut(() => AuthService());
|
||||
Get.lazyPut(() => RegisterController(Get.find<AuthService>()));
|
||||
Get.lazyPut(
|
||||
() => RegisterController(
|
||||
Get.find<AuthService>(),
|
||||
Get.find<ConnectionService>(),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,14 +1,20 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:quiz_app/core/helper/connection_check.dart';
|
||||
import 'package:quiz_app/core/utils/custom_floating_loading.dart';
|
||||
import 'package:quiz_app/core/utils/custom_notification.dart';
|
||||
import 'package:quiz_app/data/models/register/register_request.dart';
|
||||
import 'package:quiz_app/data/services/auth_service.dart';
|
||||
import 'package:quiz_app/data/services/connection_service.dart';
|
||||
|
||||
class RegisterController extends GetxController {
|
||||
final AuthService _authService;
|
||||
final ConnectionService _connectionService;
|
||||
|
||||
RegisterController(this._authService);
|
||||
RegisterController(
|
||||
this._authService,
|
||||
this._connectionService,
|
||||
);
|
||||
|
||||
final TextEditingController nameController = TextEditingController();
|
||||
final TextEditingController bDateController = TextEditingController();
|
||||
|
@ -20,6 +26,14 @@ class RegisterController extends GetxController {
|
|||
var isPasswordHidden = true.obs;
|
||||
var isConfirmPasswordHidden = true.obs;
|
||||
|
||||
@override
|
||||
void onReady() {
|
||||
if (!_connectionService.isCurrentlyConnected) {
|
||||
ConnectionNotification.noInternedConnection();
|
||||
}
|
||||
super.onReady();
|
||||
}
|
||||
|
||||
void togglePasswordVisibility() {
|
||||
isPasswordHidden.value = !isPasswordHidden.value;
|
||||
}
|
||||
|
@ -29,6 +43,11 @@ class RegisterController extends GetxController {
|
|||
}
|
||||
|
||||
Future<void> onRegister() async {
|
||||
if (!_connectionService.isCurrentlyConnected) {
|
||||
ConnectionNotification.noInternedConnection();
|
||||
return;
|
||||
}
|
||||
|
||||
String email = emailController.text.trim();
|
||||
String name = nameController.text.trim();
|
||||
String birthDate = bDateController.text.trim();
|
||||
|
|
|
@ -18,54 +18,62 @@ class RegisterView extends GetView<RegisterController> {
|
|||
body: SafeArea(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: ListView(
|
||||
child: Column(
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 40),
|
||||
child: AppName(),
|
||||
),
|
||||
LabelTextField(
|
||||
label: context.tr('register_title'),
|
||||
fontSize: 24,
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
LabelTextField(label: context.tr('full_name')),
|
||||
GlobalTextField(controller: controller.nameController),
|
||||
const SizedBox(height: 10),
|
||||
LabelTextField(label: context.tr('email')),
|
||||
GlobalTextField(controller: controller.emailController),
|
||||
const SizedBox(height: 10),
|
||||
LabelTextField(label: context.tr('birth_date')),
|
||||
GlobalTextField(
|
||||
controller: controller.bDateController,
|
||||
hintText: "12-08-2001",
|
||||
),
|
||||
LabelTextField(label: context.tr('phone_optional')),
|
||||
GlobalTextField(
|
||||
controller: controller.phoneController,
|
||||
hintText: "085708570857",
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
LabelTextField(label: context.tr('password')),
|
||||
Obx(
|
||||
() => GlobalTextField(
|
||||
controller: controller.passwordController,
|
||||
isPassword: true,
|
||||
obscureText: controller.isPasswordHidden.value,
|
||||
onToggleVisibility: controller.togglePasswordVisibility,
|
||||
Expanded(
|
||||
child: SingleChildScrollView(
|
||||
child: Column(
|
||||
children: [
|
||||
buildBackHeader(),
|
||||
const SizedBox(height: 30),
|
||||
AppName(),
|
||||
const SizedBox(height: 40),
|
||||
LabelTextField(
|
||||
label: context.tr('register_title'),
|
||||
fontSize: 24,
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
LabelTextField(label: context.tr('full_name')),
|
||||
GlobalTextField(controller: controller.nameController),
|
||||
const SizedBox(height: 10),
|
||||
LabelTextField(label: context.tr('email')),
|
||||
GlobalTextField(controller: controller.emailController),
|
||||
const SizedBox(height: 10),
|
||||
LabelTextField(label: context.tr('birth_date')),
|
||||
GlobalTextField(
|
||||
controller: controller.bDateController,
|
||||
hintText: "12-08-2001",
|
||||
),
|
||||
LabelTextField(label: context.tr('phone_optional')),
|
||||
GlobalTextField(
|
||||
controller: controller.phoneController,
|
||||
hintText: "085708570857",
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
LabelTextField(label: context.tr('password')),
|
||||
Obx(
|
||||
() => GlobalTextField(
|
||||
controller: controller.passwordController,
|
||||
isPassword: true,
|
||||
obscureText: controller.isPasswordHidden.value,
|
||||
onToggleVisibility: controller.togglePasswordVisibility,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
LabelTextField(label: context.tr('verify_password')),
|
||||
Obx(
|
||||
() => GlobalTextField(
|
||||
controller: controller.confirmPasswordController,
|
||||
isPassword: true,
|
||||
obscureText: controller.isConfirmPasswordHidden.value,
|
||||
onToggleVisibility: controller.toggleConfirmPasswordVisibility,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
LabelTextField(label: context.tr('verify_password')),
|
||||
Obx(
|
||||
() => GlobalTextField(
|
||||
controller: controller.confirmPasswordController,
|
||||
isPassword: true,
|
||||
obscureText: controller.isConfirmPasswordHidden.value,
|
||||
onToggleVisibility: controller.toggleConfirmPasswordVisibility,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 40),
|
||||
const SizedBox(height: 20),
|
||||
GlobalButton(
|
||||
onPressed: controller.onRegister,
|
||||
text: context.tr('register_button'),
|
||||
|
@ -76,4 +84,23 @@ class RegisterView extends GetView<RegisterController> {
|
|||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget buildBackHeader({String title = ""}) {
|
||||
return Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
IconButton(
|
||||
icon: const Icon(Icons.arrow_back),
|
||||
onPressed: () => Get.back(),
|
||||
),
|
||||
Text(
|
||||
title,
|
||||
style: const TextStyle(
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
48
pubspec.lock
48
pubspec.lock
|
@ -49,6 +49,22 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.19.0"
|
||||
connectivity_plus:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: connectivity_plus
|
||||
sha256: "051849e2bd7c7b3bc5844ea0d096609ddc3a859890ec3a9ac4a65a2620cc1f99"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.1.4"
|
||||
connectivity_plus_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: connectivity_plus_platform_interface
|
||||
sha256: "42657c1715d48b167930d5f34d00222ac100475f73d10162ddf43e714932f204"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.1"
|
||||
crypto:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -65,6 +81,14 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.8"
|
||||
dbus:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: dbus
|
||||
sha256: "79e0c23480ff85dc68de79e2cd6334add97e48f7f4865d17686dd6ea81a47e8c"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.7.11"
|
||||
dio:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
@ -333,6 +357,14 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.4"
|
||||
nm:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: nm
|
||||
sha256: "2c9aae4127bdc8993206464fcc063611e0e36e72018696cd9631023a31b24254"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.5.0"
|
||||
path:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -397,6 +429,14 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.2.5"
|
||||
petitparser:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: petitparser
|
||||
sha256: c15605cd28af66339f8eb6fbe0e541bfe2d1b72d5825efc6598f3e0a31b9ad27
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.0.2"
|
||||
platform:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -578,6 +618,14 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.0"
|
||||
xml:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: xml
|
||||
sha256: b015a8ad1c488f66851d762d3090a21c600e479dc75e68328c52774040cf9226
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.5.0"
|
||||
sdks:
|
||||
dart: ">=3.6.0 <4.0.0"
|
||||
flutter: ">=3.27.0"
|
||||
|
|
|
@ -45,6 +45,7 @@ dependencies:
|
|||
socket_io_client: ^3.1.2
|
||||
easy_localization: ^3.0.7+1
|
||||
percent_indicator: ^4.2.5
|
||||
connectivity_plus: ^6.1.4
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
|
|
Loading…
Reference in New Issue