From bfa253ceec69113deb764ee00a5af6696f1dc6fd Mon Sep 17 00:00:00 2001 From: akhdanre Date: Fri, 23 May 2025 15:04:04 +0700 Subject: [PATCH] fix: register page --- lib/app/bindings/initial_bindings.dart | 2 + lib/core/helper/connection_check.dart | 17 +++ lib/data/services/connection_service.dart | 56 +++++++++ lib/feature/login/bindings/login_binding.dart | 11 +- .../login/controllers/login_controller.dart | 32 ++++- lib/feature/login/view/login_page.dart | 2 +- .../register/binding/register_binding.dart | 8 +- .../controller/register_controller.dart | 21 +++- lib/feature/register/view/register_page.dart | 117 +++++++++++------- pubspec.lock | 48 +++++++ pubspec.yaml | 1 + 11 files changed, 264 insertions(+), 51 deletions(-) create mode 100644 lib/core/helper/connection_check.dart create mode 100644 lib/data/services/connection_service.dart diff --git a/lib/app/bindings/initial_bindings.dart b/lib/app/bindings/initial_bindings.dart index 189bb2e..cc212a4 100644 --- a/lib/app/bindings/initial_bindings.dart +++ b/lib/app/bindings/initial_bindings.dart @@ -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()); + Get.put(ConnectionService()); Get.putAsync(() => ApiClient().init()); Get.put(UserController(Get.find())); } diff --git a/lib/core/helper/connection_check.dart b/lib/core/helper/connection_check.dart new file mode 100644 index 0000000..a35d433 --- /dev/null +++ b/lib/core/helper/connection_check.dart @@ -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", + ); + } +} diff --git a/lib/data/services/connection_service.dart b/lib/data/services/connection_service.dart new file mode 100644 index 0000000..de5609f --- /dev/null +++ b/lib/data/services/connection_service.dart @@ -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> _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 _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 results) { + // If all results are `none`, the device is considered offline. + isConnected.value = results.any((result) => result != ConnectivityResult.none); + } + + Future 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(); + } +} diff --git a/lib/feature/login/bindings/login_binding.dart b/lib/feature/login/bindings/login_binding.dart index 9686a88..3e05bcc 100644 --- a/lib/feature/login/bindings/login_binding.dart +++ b/lib/feature/login/bindings/login_binding.dart @@ -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()); Get.lazyPut(() => AuthService()); - Get.lazyPut(() => LoginController(Get.find(), Get.find(), Get.find(), Get.find())); + Get.lazyPut( + () => LoginController( + Get.find(), + Get.find(), + Get.find(), + Get.find(), + Get.find(), + ), + ); } } diff --git a/lib/feature/login/controllers/login_controller.dart b/lib/feature/login/controllers/login_controller.dart index 682200b..2e4c0a0 100644 --- a/lib/feature/login/controllers/login_controller.dart +++ b/lib/feature/login/controllers/login_controller.dart @@ -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 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( diff --git a/lib/feature/login/view/login_page.dart b/lib/feature/login/view/login_page.dart index 74f8f0f..2011f91 100644 --- a/lib/feature/login/view/login_page.dart +++ b/lib/feature/login/view/login_page.dart @@ -16,7 +16,7 @@ class LoginView extends GetView { @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), diff --git a/lib/feature/register/binding/register_binding.dart b/lib/feature/register/binding/register_binding.dart index f00bd23..ab49d20 100644 --- a/lib/feature/register/binding/register_binding.dart +++ b/lib/feature/register/binding/register_binding.dart @@ -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())); + Get.lazyPut( + () => RegisterController( + Get.find(), + Get.find(), + ), + ); } } diff --git a/lib/feature/register/controller/register_controller.dart b/lib/feature/register/controller/register_controller.dart index 79b2db1..506e211 100644 --- a/lib/feature/register/controller/register_controller.dart +++ b/lib/feature/register/controller/register_controller.dart @@ -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 onRegister() async { + if (!_connectionService.isCurrentlyConnected) { + ConnectionNotification.noInternedConnection(); + return; + } + String email = emailController.text.trim(); String name = nameController.text.trim(); String birthDate = bDateController.text.trim(); diff --git a/lib/feature/register/view/register_page.dart b/lib/feature/register/view/register_page.dart index 6a72f87..a749110 100644 --- a/lib/feature/register/view/register_page.dart +++ b/lib/feature/register/view/register_page.dart @@ -18,54 +18,62 @@ class RegisterView extends GetView { 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 { ), ); } + + 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, + ), + ), + ], + ); + } } diff --git a/pubspec.lock b/pubspec.lock index 11e8610..2ec5508 100644 --- a/pubspec.lock +++ b/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" diff --git a/pubspec.yaml b/pubspec.yaml index 56cd005..c190f99 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -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: