From f2a647519a99c62d49fc8ef632719f7de2403593 Mon Sep 17 00:00:00 2001 From: CoderJava Date: Sun, 26 Nov 2023 20:24:06 +0700 Subject: [PATCH 01/18] release: Masukkan app versi 1.6.0 kedalam appcast.xml --- dist/appcast.xml | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/dist/appcast.xml b/dist/appcast.xml index 791fab8..45c3ff4 100644 --- a/dist/appcast.xml +++ b/dist/appcast.xml @@ -5,29 +5,28 @@ en Dipantau - Version 1.5.0 + Version 1.6.0 Fitur
    -
  • Update form add manual track agar lebih gampang dipakai.
  • -
  • Update form add manual track agar wajib mengisi catatan.
  • +
  • Khusus super admin, buatkan fitur untuk melihat original dari screenshot yang diblur.
  • +
  • Buat fitur download screenshot.

Perbaikan

    -
  • Perbaiki reset timer di system tray setelah user logout.
  • -
  • Perbaiki agar user tidak bisa logout jika timer-nya dalam keadaan hidup.
  • +
  • Perbaiki nilai task yang tidak tersimpan ke database lokal.
]]>
- 10 - 1.5.0 + 11 + 1.6.0 - Mon, 07 Oct 2023 09:00:00 +0700 + Sun, 26 Nov 2023 22:00:00 +0700
From 6ff64b0d85f0921effcd59ac42e82bdf2e7931f2 Mon Sep 17 00:00:00 2001 From: CoderJava Date: Thu, 6 Jun 2024 07:30:19 +0700 Subject: [PATCH 02/18] feat: Update beberapa plugin ke versi terbaru yang compatible --- macos/Flutter/GeneratedPluginRegistrant.swift | 8 ++-- macos/Podfile.lock | 46 ++++++++----------- macos/Runner.xcodeproj/project.pbxproj | 2 +- .../xcshareddata/xcschemes/Runner.xcscheme | 2 +- pubspec.yaml | 26 +++++------ .../flutter/generated_plugin_registrant.cc | 6 +-- windows/flutter/generated_plugins.cmake | 2 +- 7 files changed, 42 insertions(+), 50 deletions(-) diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index d0bc274..44a932a 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -5,7 +5,7 @@ import FlutterMacOS import Foundation -import auto_updater +import auto_updater_macos import connectivity_plus import flutter_local_notifications import package_info_plus @@ -17,10 +17,10 @@ import tray_manager import window_manager func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { - AutoUpdaterPlugin.register(with: registry.registrar(forPlugin: "AutoUpdaterPlugin")) - ConnectivityPlugin.register(with: registry.registrar(forPlugin: "ConnectivityPlugin")) + AutoUpdaterMacosPlugin.register(with: registry.registrar(forPlugin: "AutoUpdaterMacosPlugin")) + ConnectivityPlusPlugin.register(with: registry.registrar(forPlugin: "ConnectivityPlusPlugin")) FlutterLocalNotificationsPlugin.register(with: registry.registrar(forPlugin: "FlutterLocalNotificationsPlugin")) - FLTPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FLTPackageInfoPlusPlugin")) + FPPPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FPPPackageInfoPlusPlugin")) PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) ScreenRetrieverPlugin.register(with: registry.registrar(forPlugin: "ScreenRetrieverPlugin")) SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) diff --git a/macos/Podfile.lock b/macos/Podfile.lock index 484b54c..2aca182 100644 --- a/macos/Podfile.lock +++ b/macos/Podfile.lock @@ -1,60 +1,54 @@ PODS: - - auto_updater (0.0.1): + - auto_updater_macos (0.0.1): - FlutterMacOS - Sparkle - connectivity_plus (0.0.1): + - Flutter - FlutterMacOS - - ReachabilitySwift - flutter_local_notifications (0.0.1): - FlutterMacOS - FlutterMacOS (1.0.0) - - FMDB (2.7.5): - - FMDB/standard (= 2.7.5) - - FMDB/standard (2.7.5) - package_info_plus (0.0.1): - FlutterMacOS - path_provider_foundation (0.0.1): - Flutter - FlutterMacOS - - ReachabilitySwift (5.0.0) - screen_retriever (0.0.1): - FlutterMacOS - shared_preferences_foundation (0.0.1): - Flutter - FlutterMacOS - - Sparkle (2.4.2) - - sqflite (0.0.2): + - Sparkle (2.6.2) + - sqflite (0.0.3): + - Flutter - FlutterMacOS - - FMDB (>= 2.7.5) - tray_manager (0.0.1): - FlutterMacOS - window_manager (0.2.0): - FlutterMacOS DEPENDENCIES: - - auto_updater (from `Flutter/ephemeral/.symlinks/plugins/auto_updater/macos`) - - connectivity_plus (from `Flutter/ephemeral/.symlinks/plugins/connectivity_plus/macos`) + - auto_updater_macos (from `Flutter/ephemeral/.symlinks/plugins/auto_updater_macos/macos`) + - connectivity_plus (from `Flutter/ephemeral/.symlinks/plugins/connectivity_plus/darwin`) - flutter_local_notifications (from `Flutter/ephemeral/.symlinks/plugins/flutter_local_notifications/macos`) - FlutterMacOS (from `Flutter/ephemeral`) - package_info_plus (from `Flutter/ephemeral/.symlinks/plugins/package_info_plus/macos`) - path_provider_foundation (from `Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin`) - screen_retriever (from `Flutter/ephemeral/.symlinks/plugins/screen_retriever/macos`) - shared_preferences_foundation (from `Flutter/ephemeral/.symlinks/plugins/shared_preferences_foundation/darwin`) - - sqflite (from `Flutter/ephemeral/.symlinks/plugins/sqflite/macos`) + - sqflite (from `Flutter/ephemeral/.symlinks/plugins/sqflite/darwin`) - tray_manager (from `Flutter/ephemeral/.symlinks/plugins/tray_manager/macos`) - window_manager (from `Flutter/ephemeral/.symlinks/plugins/window_manager/macos`) SPEC REPOS: trunk: - - FMDB - - ReachabilitySwift - Sparkle EXTERNAL SOURCES: - auto_updater: - :path: Flutter/ephemeral/.symlinks/plugins/auto_updater/macos + auto_updater_macos: + :path: Flutter/ephemeral/.symlinks/plugins/auto_updater_macos/macos connectivity_plus: - :path: Flutter/ephemeral/.symlinks/plugins/connectivity_plus/macos + :path: Flutter/ephemeral/.symlinks/plugins/connectivity_plus/darwin flutter_local_notifications: :path: Flutter/ephemeral/.symlinks/plugins/flutter_local_notifications/macos FlutterMacOS: @@ -68,25 +62,23 @@ EXTERNAL SOURCES: shared_preferences_foundation: :path: Flutter/ephemeral/.symlinks/plugins/shared_preferences_foundation/darwin sqflite: - :path: Flutter/ephemeral/.symlinks/plugins/sqflite/macos + :path: Flutter/ephemeral/.symlinks/plugins/sqflite/darwin tray_manager: :path: Flutter/ephemeral/.symlinks/plugins/tray_manager/macos window_manager: :path: Flutter/ephemeral/.symlinks/plugins/window_manager/macos SPEC CHECKSUMS: - auto_updater: d3c03e9e5f2a00ec78572d9f7473cb8c9a6c0273 - connectivity_plus: 18d3c32514c886e046de60e9c13895109866c747 + auto_updater_macos: 3e3462c418fe4e731917eacd8d28eef7af84086d + connectivity_plus: ddd7f30999e1faaef5967c23d5b6d503d10434db flutter_local_notifications: 3805ca215b2fb7f397d78b66db91f6a747af52e4 FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24 - FMDB: 2ce00b547f966261cd18927a3ddb07cb6f3db82a - package_info_plus: 02d7a575e80f194102bef286361c6c326e4c29ce - path_provider_foundation: 29f094ae23ebbca9d3d0cec13889cd9060c0e943 - ReachabilitySwift: 985039c6f7b23a1da463388634119492ff86c825 + package_info_plus: fa739dd842b393193c5ca93c26798dff6e3d0e0c + path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46 screen_retriever: 59634572a57080243dd1bf715e55b6c54f241a38 - shared_preferences_foundation: 5b919d13b803cadd15ed2dc053125c68730e5126 - Sparkle: 5ef7097e655c60f4aeb23fd1658fc3e8dd50f4ec - sqflite: a5789cceda41d54d23f31d6de539d65bb14100ea + shared_preferences_foundation: fcdcbc04712aee1108ac7fda236f363274528f78 + Sparkle: a62c7dc4f410ced73beb2169cf1d3cc3f028a295 + sqflite: 673a0e54cc04b7d6dba8d24fb8095b31c3a99eec tray_manager: 9064e219c56d75c476e46b9a21182087930baf90 window_manager: 3a1844359a6295ab1e47659b1a777e36773cd6e8 diff --git a/macos/Runner.xcodeproj/project.pbxproj b/macos/Runner.xcodeproj/project.pbxproj index 364c46d..7d17195 100644 --- a/macos/Runner.xcodeproj/project.pbxproj +++ b/macos/Runner.xcodeproj/project.pbxproj @@ -208,7 +208,7 @@ isa = PBXProject; attributes = { LastSwiftUpdateCheck = 0920; - LastUpgradeCheck = 1300; + LastUpgradeCheck = 1510; ORGANIZATIONNAME = ""; TargetAttributes = { 33CC10EC2044A3C60003C045 = { diff --git a/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index 49ffbfe..47348a2 100644 --- a/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -1,6 +1,6 @@ +#include #include #include #include #include void RegisterPlugins(flutter::PluginRegistry* registry) { - AutoUpdaterPluginRegisterWithRegistrar( - registry->GetRegistrarForPlugin("AutoUpdaterPlugin")); + AutoUpdaterWindowsPluginCApiRegisterWithRegistrar( + registry->GetRegistrarForPlugin("AutoUpdaterWindowsPluginCApi")); ConnectivityPlusWindowsPluginRegisterWithRegistrar( registry->GetRegistrarForPlugin("ConnectivityPlusWindowsPlugin")); ScreenRetrieverPluginRegisterWithRegistrar( diff --git a/windows/flutter/generated_plugins.cmake b/windows/flutter/generated_plugins.cmake index eb2f99c..2d258cd 100644 --- a/windows/flutter/generated_plugins.cmake +++ b/windows/flutter/generated_plugins.cmake @@ -3,7 +3,7 @@ # list(APPEND FLUTTER_PLUGIN_LIST - auto_updater + auto_updater_windows connectivity_plus screen_retriever tray_manager From 69732dc36d8ad3e24f70c6b04e652cdf2ad8ed15 Mon Sep 17 00:00:00 2001 From: CoderJava Date: Thu, 6 Jun 2024 07:33:53 +0700 Subject: [PATCH 03/18] refactor: Update beberapa kode agar tidak masuk ke dart analysis setelah upgrade Flutter SDK ke versi 3.19.6 --- lib/core/network/network_info.dart | 8 +++++++- .../page/add_member/add_edit_member_page.dart | 4 ++-- .../page/edit_profile/edit_profile_page.dart | 2 +- .../presentation/page/error/error_page.dart | 2 +- .../page/forgot_password/forgot_password_page.dart | 4 ++-- lib/feature/presentation/page/home/home_page.dart | 10 ++++++---- .../presentation/page/login/login_page.dart | 4 ++-- .../page/manual_tracking/manual_tracking_page.dart | 2 +- .../page/member_setting/member_setting_page.dart | 2 +- .../page/photo_view/photo_view_page.dart | 4 ++-- .../presentation/page/register/register_page.dart | 4 ++-- .../register_success/register_success_page.dart | 4 ++-- .../report_screenshot/report_screenshot_page.dart | 11 +++++++---- .../page/reset_password/reset_password_page.dart | 4 ++-- .../reset_password_success_page.dart | 4 ++-- .../presentation/page/setting/setting_page.dart | 2 +- .../page/setting_discord/setting_discord_page.dart | 2 +- .../setting_member_blur_screenshot_page.dart | 2 +- .../setup_credential/setup_credential_page.dart | 4 ++-- .../presentation/page/splash/splash_page.dart | 2 +- lib/feature/presentation/page/sync/sync_page.dart | 2 +- .../user_registration_setting_page.dart | 2 +- .../verify_forgot_password_page.dart | 4 ++-- .../presentation/widget/widget_choose_project.dart | 4 ++-- .../widget_custom_circular_progress_indicator.dart | 4 ++-- lib/feature/presentation/widget/widget_error.dart | 4 ++-- .../presentation/widget/widget_icon_circle.dart | 4 ++-- .../widget/widget_loading_center_full_screen.dart | 2 +- .../presentation/widget/widget_primary_button.dart | 4 ++-- .../widget/widget_theme_container.dart | 4 ++-- lib/main.dart | 14 ++++++++++---- test/core/network/network_info_test.dart | 12 ++++++------ 32 files changed, 79 insertions(+), 62 deletions(-) diff --git a/lib/core/network/network_info.dart b/lib/core/network/network_info.dart index 2223aa1..e004b87 100644 --- a/lib/core/network/network_info.dart +++ b/lib/core/network/network_info.dart @@ -12,6 +12,12 @@ class NetworkInfoImpl implements NetworkInfo { @override Future get isConnected async { final connectivityResult = await connectivity.checkConnectivity(); - return connectivityResult != ConnectivityResult.none; + if (connectivityResult.contains(ConnectivityResult.none)) { + return false; + } else if (connectivityResult.isEmpty) { + return false; + } else { + return true; + } } } diff --git a/lib/feature/presentation/page/add_member/add_edit_member_page.dart b/lib/feature/presentation/page/add_member/add_edit_member_page.dart index d56c50e..f9383df 100644 --- a/lib/feature/presentation/page/add_member/add_edit_member_page.dart +++ b/lib/feature/presentation/page/add_member/add_edit_member_page.dart @@ -24,9 +24,9 @@ class AddEditMemberPage extends StatefulWidget { final UserProfileResponse? defaultValue; const AddEditMemberPage({ - Key? key, + super.key, this.defaultValue, - }) : super(key: key); + }); @override State createState() => _AddEditMemberPageState(); diff --git a/lib/feature/presentation/page/edit_profile/edit_profile_page.dart b/lib/feature/presentation/page/edit_profile/edit_profile_page.dart index 4178a14..83a26d9 100644 --- a/lib/feature/presentation/page/edit_profile/edit_profile_page.dart +++ b/lib/feature/presentation/page/edit_profile/edit_profile_page.dart @@ -22,7 +22,7 @@ class EditProfilePage extends StatefulWidget { static const routePath = '/edit-profile'; static const routeName = 'edit-profile'; - const EditProfilePage({Key? key}) : super(key: key); + const EditProfilePage({super.key}); @override State createState() => _EditProfilePageState(); diff --git a/lib/feature/presentation/page/error/error_page.dart b/lib/feature/presentation/page/error/error_page.dart index 1e1c4c8..74b5713 100644 --- a/lib/feature/presentation/page/error/error_page.dart +++ b/lib/feature/presentation/page/error/error_page.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; class ErrorPage extends StatelessWidget { - const ErrorPage({Key? key}) : super(key: key); + const ErrorPage({super.key}); @override Widget build(BuildContext context) { diff --git a/lib/feature/presentation/page/forgot_password/forgot_password_page.dart b/lib/feature/presentation/page/forgot_password/forgot_password_page.dart index 731d4ae..f982afe 100644 --- a/lib/feature/presentation/page/forgot_password/forgot_password_page.dart +++ b/lib/feature/presentation/page/forgot_password/forgot_password_page.dart @@ -19,9 +19,9 @@ class ForgotPasswordPage extends StatefulWidget { final String? email; const ForgotPasswordPage({ - Key? key, + super.key, required this.email, - }) : super(key: key); + }); @override State createState() => _ForgotPasswordPageState(); diff --git a/lib/feature/presentation/page/home/home_page.dart b/lib/feature/presentation/page/home/home_page.dart index 8fb78c9..ce38b18 100644 --- a/lib/feature/presentation/page/home/home_page.dart +++ b/lib/feature/presentation/page/home/home_page.dart @@ -51,7 +51,7 @@ class HomePage extends StatefulWidget { static const routePath = '/home'; static const routeName = 'home'; - const HomePage({Key? key}) : super(key: key); + const HomePage({super.key}); @override State createState() => _HomePageState(); @@ -129,7 +129,9 @@ class _HomePageState extends State with TrayListener, WindowListener { final appDatabase = await sl.getAsync(); trackDao = appDatabase.trackDao; } catch (error) { - widgetHelper.showSnackBar(context, 'error: $error'); + if (mounted) { + widgetHelper.showSnackBar(context, 'error: $error'); + } } setupCronTimer(); doLoadDataTask(); @@ -644,7 +646,7 @@ class _HomePageState extends State with TrayListener, WindowListener { onTap: () async { final isPermissionScreenRecordingGranted = await platformChannelHelper.checkPermissionScreenRecording(); - if (mounted && isPermissionScreenRecordingGranted != null && !isPermissionScreenRecordingGranted) { + if (context.mounted && isPermissionScreenRecordingGranted != null && !isPermissionScreenRecordingGranted) { widgetHelper.showDialogPermissionScreenRecording(context); return; } @@ -652,7 +654,7 @@ class _HomePageState extends State with TrayListener, WindowListener { if (isPermissionScreenRecordingGranted!) { final isPermissionAccessibilityGranted = await platformChannelHelper.checkPermissionAccessibility(); - if (mounted && isPermissionAccessibilityGranted != null && !isPermissionAccessibilityGranted) { + if (context.mounted && isPermissionAccessibilityGranted != null && !isPermissionAccessibilityGranted) { widgetHelper.showDialogPermissionAccessibility(context); return; } diff --git a/lib/feature/presentation/page/login/login_page.dart b/lib/feature/presentation/page/login/login_page.dart index 52a2693..3518291 100644 --- a/lib/feature/presentation/page/login/login_page.dart +++ b/lib/feature/presentation/page/login/login_page.dart @@ -17,7 +17,7 @@ class LoginPage extends StatefulWidget { static const routePath = '/login'; static const routeName = 'login'; - const LoginPage({Key? key}) : super(key: key); + const LoginPage({super.key}); @override State createState() => _LoginPageState(); @@ -122,7 +122,7 @@ class _LoginPageState extends State { final email = controllerEmail.text.trim(); context.pushNamed( ForgotPasswordPage.routeName, - queryParams: { + queryParameters: { ForgotPasswordPage.parameterEmail: email, }, ); diff --git a/lib/feature/presentation/page/manual_tracking/manual_tracking_page.dart b/lib/feature/presentation/page/manual_tracking/manual_tracking_page.dart index 43b0679..7792e40 100644 --- a/lib/feature/presentation/page/manual_tracking/manual_tracking_page.dart +++ b/lib/feature/presentation/page/manual_tracking/manual_tracking_page.dart @@ -18,7 +18,7 @@ class ManualTrackingPage extends StatefulWidget { static const routePath = '/manual-tracking'; static const routeName = 'manual-tracking'; - const ManualTrackingPage({Key? key}) : super(key: key); + const ManualTrackingPage({super.key}); @override State createState() => _ManualTrackingPageState(); diff --git a/lib/feature/presentation/page/member_setting/member_setting_page.dart b/lib/feature/presentation/page/member_setting/member_setting_page.dart index 973a817..35dbe88 100644 --- a/lib/feature/presentation/page/member_setting/member_setting_page.dart +++ b/lib/feature/presentation/page/member_setting/member_setting_page.dart @@ -18,7 +18,7 @@ class MemberSettingPage extends StatefulWidget { static const routePath = '/member-setting'; static const routeName = 'member-setting'; - const MemberSettingPage({Key? key}) : super(key: key); + const MemberSettingPage({super.key}); @override State createState() => _MemberSettingPageState(); diff --git a/lib/feature/presentation/page/photo_view/photo_view_page.dart b/lib/feature/presentation/page/photo_view/photo_view_page.dart index ad6b667..e2a3486 100644 --- a/lib/feature/presentation/page/photo_view/photo_view_page.dart +++ b/lib/feature/presentation/page/photo_view/photo_view_page.dart @@ -24,10 +24,10 @@ class PhotoViewPage extends StatefulWidget { final bool? isShowIconDownload; PhotoViewPage({ - Key? key, + super.key, required this.listPhotos, required this.isShowIconDownload, - }) : super(key: key); + }); @override State createState() => _PhotoViewPageState(); diff --git a/lib/feature/presentation/page/register/register_page.dart b/lib/feature/presentation/page/register/register_page.dart index fe6fe0c..0048d95 100644 --- a/lib/feature/presentation/page/register/register_page.dart +++ b/lib/feature/presentation/page/register/register_page.dart @@ -17,7 +17,7 @@ class RegisterPage extends StatefulWidget { static const routePath = '/register'; static const routeName = 'register'; - const RegisterPage({Key? key}) : super(key: key); + const RegisterPage({super.key}); @override State createState() => _RegisterPageState(); @@ -51,7 +51,7 @@ class _RegisterPageState extends State { } else if (state is SuccessSubmitSignUpState) { context.goNamed( RegisterSuccessPage.routeName, - queryParams: { + queryParameters: { 'email': state.response.email ?? '-', }, ); diff --git a/lib/feature/presentation/page/register_success/register_success_page.dart b/lib/feature/presentation/page/register_success/register_success_page.dart index 1b1dfe0..b6d2ad4 100644 --- a/lib/feature/presentation/page/register_success/register_success_page.dart +++ b/lib/feature/presentation/page/register_success/register_success_page.dart @@ -14,9 +14,9 @@ class RegisterSuccessPage extends StatelessWidget { final String email; RegisterSuccessPage({ - Key? key, + super.key, required this.email, - }) : super(key: key); + }); final helper = sl(); diff --git a/lib/feature/presentation/page/report_screenshot/report_screenshot_page.dart b/lib/feature/presentation/page/report_screenshot/report_screenshot_page.dart index 4df09c9..e7be2f1 100644 --- a/lib/feature/presentation/page/report_screenshot/report_screenshot_page.dart +++ b/lib/feature/presentation/page/report_screenshot/report_screenshot_page.dart @@ -27,7 +27,7 @@ class ReportScreenshotPage extends StatefulWidget { static const routePath = '/report-screenshot'; static const routeName = 'report-screenshot'; - const ReportScreenshotPage({Key? key}) : super(key: key); + const ReportScreenshotPage({super.key}); @override State createState() => _ReportScreenshotPageState(); @@ -91,10 +91,13 @@ class _ReportScreenshotPageState extends State { @override Widget build(BuildContext context) { - return WillPopScope( - onWillPop: () async { + return PopScope( + canPop: false, + onPopInvoked: (didPop) { + if (didPop) { + return; + } context.pop(isRefreshPreviousPage); - return false; }, child: GestureDetector( onTap: () => widgetHelper.unfocus(context), diff --git a/lib/feature/presentation/page/reset_password/reset_password_page.dart b/lib/feature/presentation/page/reset_password/reset_password_page.dart index 1a04511..f5d48d2 100644 --- a/lib/feature/presentation/page/reset_password/reset_password_page.dart +++ b/lib/feature/presentation/page/reset_password/reset_password_page.dart @@ -22,9 +22,9 @@ class ResetPasswordPage extends StatefulWidget { final String code; const ResetPasswordPage({ - Key? key, + super.key, required this.code, - }) : super(key: key); + }); @override State createState() => _ResetPasswordPageState(); diff --git a/lib/feature/presentation/page/reset_password_success/reset_password_success_page.dart b/lib/feature/presentation/page/reset_password_success/reset_password_success_page.dart index aadb3d5..01fff87 100644 --- a/lib/feature/presentation/page/reset_password_success/reset_password_success_page.dart +++ b/lib/feature/presentation/page/reset_password_success/reset_password_success_page.dart @@ -12,8 +12,8 @@ class ResetPasswordSuccessPage extends StatelessWidget { static const routeName = 'reset-password-success'; ResetPasswordSuccessPage({ - Key? key, - }) : super(key: key); + super.key, + }); final helper = sl(); diff --git a/lib/feature/presentation/page/setting/setting_page.dart b/lib/feature/presentation/page/setting/setting_page.dart index 926ad9c..ba815f3 100644 --- a/lib/feature/presentation/page/setting/setting_page.dart +++ b/lib/feature/presentation/page/setting/setting_page.dart @@ -35,7 +35,7 @@ class SettingPage extends StatefulWidget { static const routePath = '/setting'; static const routeName = 'setting'; - const SettingPage({Key? key}) : super(key: key); + const SettingPage({super.key}); @override State createState() => _SettingPageState(); diff --git a/lib/feature/presentation/page/setting_discord/setting_discord_page.dart b/lib/feature/presentation/page/setting_discord/setting_discord_page.dart index 7788c99..06a7060 100644 --- a/lib/feature/presentation/page/setting_discord/setting_discord_page.dart +++ b/lib/feature/presentation/page/setting_discord/setting_discord_page.dart @@ -16,7 +16,7 @@ class SettingDiscordPage extends StatefulWidget { static const routePath = '/setting-discord'; static const routeName = 'setting-discord'; - const SettingDiscordPage({Key? key}) : super(key: key); + const SettingDiscordPage({super.key}); @override State createState() => _SettingDiscordPageState(); diff --git a/lib/feature/presentation/page/setting_member_blur_screenshot/setting_member_blur_screenshot_page.dart b/lib/feature/presentation/page/setting_member_blur_screenshot/setting_member_blur_screenshot_page.dart index d9341e0..6db57be 100644 --- a/lib/feature/presentation/page/setting_member_blur_screenshot/setting_member_blur_screenshot_page.dart +++ b/lib/feature/presentation/page/setting_member_blur_screenshot/setting_member_blur_screenshot_page.dart @@ -16,7 +16,7 @@ class SettingMemberBlurScreenshotPage extends StatefulWidget { static const routePath = '/member-blur-screenshot'; static const routeName = 'member-blur-screenshot'; - const SettingMemberBlurScreenshotPage({Key? key}) : super(key: key); + const SettingMemberBlurScreenshotPage({super.key}); @override State createState() => _SettingMemberBlurScreenshotPageState(); diff --git a/lib/feature/presentation/page/setup_credential/setup_credential_page.dart b/lib/feature/presentation/page/setup_credential/setup_credential_page.dart index 110904b..6fcdd8d 100644 --- a/lib/feature/presentation/page/setup_credential/setup_credential_page.dart +++ b/lib/feature/presentation/page/setup_credential/setup_credential_page.dart @@ -18,10 +18,10 @@ class SetupCredentialPage extends StatefulWidget { final bool isShowWarning; const SetupCredentialPage({ - Key? key, + super.key, this.isFromSplashScreen = false, this.isShowWarning = false, - }) : super(key: key); + }); @override State createState() => _SetupCredentialPageState(); diff --git a/lib/feature/presentation/page/splash/splash_page.dart b/lib/feature/presentation/page/splash/splash_page.dart index 40519f0..f097eb7 100644 --- a/lib/feature/presentation/page/splash/splash_page.dart +++ b/lib/feature/presentation/page/splash/splash_page.dart @@ -13,7 +13,7 @@ class SplashPage extends StatefulWidget { static const routePath = '/splash'; static const routeName = 'splash'; - const SplashPage({Key? key}) : super(key: key); + const SplashPage({super.key}); @override State createState() => _SplashPageState(); diff --git a/lib/feature/presentation/page/sync/sync_page.dart b/lib/feature/presentation/page/sync/sync_page.dart index 0f1bfa9..7654729 100644 --- a/lib/feature/presentation/page/sync/sync_page.dart +++ b/lib/feature/presentation/page/sync/sync_page.dart @@ -26,7 +26,7 @@ class SyncPage extends StatefulWidget { static const routePath = '/sync'; static const routeName = 'sync'; - const SyncPage({Key? key}) : super(key: key); + const SyncPage({super.key}); @override State createState() => _SyncPageState(); diff --git a/lib/feature/presentation/page/user_registration_setting/user_registration_setting_page.dart b/lib/feature/presentation/page/user_registration_setting/user_registration_setting_page.dart index 3a2a4a8..4fcb0cf 100644 --- a/lib/feature/presentation/page/user_registration_setting/user_registration_setting_page.dart +++ b/lib/feature/presentation/page/user_registration_setting/user_registration_setting_page.dart @@ -17,7 +17,7 @@ class UserRegistrationSettingPage extends StatefulWidget { static const routePath = '/user-registration-setting'; static const routeName = 'user-registration-setting'; - const UserRegistrationSettingPage({Key? key}) : super(key: key); + const UserRegistrationSettingPage({super.key}); @override State createState() => _UserRegistrationSettingPageState(); diff --git a/lib/feature/presentation/page/verify_forgot_password/verify_forgot_password_page.dart b/lib/feature/presentation/page/verify_forgot_password/verify_forgot_password_page.dart index efc62cc..4d27951 100644 --- a/lib/feature/presentation/page/verify_forgot_password/verify_forgot_password_page.dart +++ b/lib/feature/presentation/page/verify_forgot_password/verify_forgot_password_page.dart @@ -20,9 +20,9 @@ class VerifyForgotPasswordPage extends StatefulWidget { final String email; const VerifyForgotPasswordPage({ - Key? key, + super.key, required this.email, - }) : super(key: key); + }); @override State createState() => _VerifyForgotPasswordPageState(); diff --git a/lib/feature/presentation/widget/widget_choose_project.dart b/lib/feature/presentation/widget/widget_choose_project.dart index 7a8c1dc..fb34d83 100644 --- a/lib/feature/presentation/widget/widget_choose_project.dart +++ b/lib/feature/presentation/widget/widget_choose_project.dart @@ -14,8 +14,8 @@ class WidgetChooseProject extends StatefulWidget { const WidgetChooseProject({ required this.defaultSelectedProjectId, - Key? key, - }) : super(key: key); + super.key, + }); @override State createState() => _WidgetChooseProjectState(); diff --git a/lib/feature/presentation/widget/widget_custom_circular_progress_indicator.dart b/lib/feature/presentation/widget/widget_custom_circular_progress_indicator.dart index 94dde8e..9391872 100644 --- a/lib/feature/presentation/widget/widget_custom_circular_progress_indicator.dart +++ b/lib/feature/presentation/widget/widget_custom_circular_progress_indicator.dart @@ -4,9 +4,9 @@ class WidgetCustomCircularProgressIndicator extends StatelessWidget { final Color? color; const WidgetCustomCircularProgressIndicator({ - Key? key, + super.key, this.color, - }) : super(key: key); + }); @override Widget build(BuildContext context) { diff --git a/lib/feature/presentation/widget/widget_error.dart b/lib/feature/presentation/widget/widget_error.dart index 219ab01..194db11 100644 --- a/lib/feature/presentation/widget/widget_error.dart +++ b/lib/feature/presentation/widget/widget_error.dart @@ -9,11 +9,11 @@ class WidgetError extends StatelessWidget { final Function()? onTryAgain; const WidgetError({ - Key? key, + super.key, required this.title, required this.message, this.onTryAgain, - }) : super(key: key); + }); @override Widget build(BuildContext context) { diff --git a/lib/feature/presentation/widget/widget_icon_circle.dart b/lib/feature/presentation/widget/widget_icon_circle.dart index f11585c..eee7421 100644 --- a/lib/feature/presentation/widget/widget_icon_circle.dart +++ b/lib/feature/presentation/widget/widget_icon_circle.dart @@ -9,8 +9,8 @@ class WidgetIconCircle extends StatelessWidget { required this.iconData, this.size = 32.0, this.padding = 16.0, - Key? key, - }) : super(key: key); + super.key, + }); @override Widget build(BuildContext context) { diff --git a/lib/feature/presentation/widget/widget_loading_center_full_screen.dart b/lib/feature/presentation/widget/widget_loading_center_full_screen.dart index 724f4a0..54c68fb 100644 --- a/lib/feature/presentation/widget/widget_loading_center_full_screen.dart +++ b/lib/feature/presentation/widget/widget_loading_center_full_screen.dart @@ -2,7 +2,7 @@ import 'package:dipantau_desktop_client/feature/presentation/widget/widget_custo import 'package:flutter/material.dart'; class WidgetLoadingCenterFullScreen extends StatelessWidget { - const WidgetLoadingCenterFullScreen({Key? key}) : super(key: key); + const WidgetLoadingCenterFullScreen({super.key}); @override Widget build(BuildContext context) { diff --git a/lib/feature/presentation/widget/widget_primary_button.dart b/lib/feature/presentation/widget/widget_primary_button.dart index fe920d9..aeffeb5 100644 --- a/lib/feature/presentation/widget/widget_primary_button.dart +++ b/lib/feature/presentation/widget/widget_primary_button.dart @@ -8,12 +8,12 @@ class WidgetPrimaryButton extends StatelessWidget { final ButtonStyle? buttonStyle; const WidgetPrimaryButton({ - Key? key, + super.key, required this.onPressed, required this.child, this.isLoading, this.buttonStyle, - }) : super(key: key); + }); @override Widget build(BuildContext context) { diff --git a/lib/feature/presentation/widget/widget_theme_container.dart b/lib/feature/presentation/widget/widget_theme_container.dart index cca0062..81e58c4 100644 --- a/lib/feature/presentation/widget/widget_theme_container.dart +++ b/lib/feature/presentation/widget/widget_theme_container.dart @@ -8,12 +8,12 @@ class WidgetThemeContainer extends StatefulWidget { final Color? borderColor; const WidgetThemeContainer({ - Key? key, + super.key, required this.mode, required this.width, required this.height, this.borderColor, - }) : super(key: key); + }); @override State createState() => _WidgetThemeContainerState(); diff --git a/lib/main.dart b/lib/main.dart index 9d2def4..0d31dbe 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -168,14 +168,14 @@ class _MyAppState extends State { path: RegisterSuccessPage.routePath, name: RegisterSuccessPage.routeName, builder: (context, state) => RegisterSuccessPage( - email: state.queryParams['email'] as String, + email: state.uri.queryParameters['email'] as String, ), ), GoRoute( path: ForgotPasswordPage.routePath, name: ForgotPasswordPage.routeName, builder: (context, state) => ForgotPasswordPage( - email: state.queryParams['email'], + email: state.uri.queryParameters['email'], ), ), GoRoute( @@ -411,8 +411,14 @@ class _MyAppState extends State { isDarkMode = state.isDarkMode; } - return WillPopScope( - onWillPop: () async => false, + return PopScope( + canPop: false, + onPopInvoked: (didPop) { + if (didPop) { + return; + } + return; + }, child: MaterialApp.router( title: 'Dipantau', themeMode: isDarkMode ? ThemeMode.dark : ThemeMode.light, diff --git a/test/core/network/network_info_test.dart b/test/core/network/network_info_test.dart index e73d52c..7919e61 100644 --- a/test/core/network/network_info_test.dart +++ b/test/core/network/network_info_test.dart @@ -19,8 +19,8 @@ void main() { 'menggunakan mobile data', () async { // arrange - final tHasConnection = Future.value(ConnectivityResult.mobile); - when(mockConnectivity.checkConnectivity()).thenAnswer((_) => tHasConnection); + final tHasConnection = [ConnectivityResult.mobile]; + when(mockConnectivity.checkConnectivity()).thenAnswer((_) async => tHasConnection); // act final result = await networkInfoImpl.isConnected; @@ -36,8 +36,8 @@ void main() { 'menggunakan wifi', () async { // arrange - final tHasConnection = Future.value(ConnectivityResult.wifi); - when(mockConnectivity.checkConnectivity()).thenAnswer((_) => tHasConnection); + final tHasConnection = [ConnectivityResult.wifi]; + when(mockConnectivity.checkConnectivity()).thenAnswer((_) async => tHasConnection); // act final result = await networkInfoImpl.isConnected; @@ -53,8 +53,8 @@ void main() { 'tidak terhubung sama sekali', () async { // arrange - final tHasConnection = Future.value(ConnectivityResult.none); - when(mockConnectivity.checkConnectivity()).thenAnswer((_) => tHasConnection); + final tHasConnection = [ConnectivityResult.none]; + when(mockConnectivity.checkConnectivity()).thenAnswer((_) async => tHasConnection); // act final result = await networkInfoImpl.isConnected; From 5cb3d2b98b070db0af68a77d39f01db6d2c7895b Mon Sep 17 00:00:00 2001 From: CoderJava Date: Thu, 6 Jun 2024 07:40:35 +0700 Subject: [PATCH 04/18] feat: Update Flutter SDK menjadi 3.19.6 didalam github workflows --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index a78c7d0..b595079 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -15,7 +15,7 @@ jobs: - name: Install & set Flutter version uses: subosito/flutter-action@v2 with: - flutter-version: '3.10.4' + flutter-version: '3.19.6' channel: 'stable' - name: Get package run: flutter pub get From d15dc88a643c4a011438bd72b23234ec40c5ba65 Mon Sep 17 00:00:00 2001 From: CoderJava Date: Tue, 29 Oct 2024 07:22:00 +0700 Subject: [PATCH 05/18] Downgrade plugin `flutter_local_notifications` ke versi 13.0.0 Didowngrade karena versi 17.1.2 tidak berfungsi di macOS. --- pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pubspec.yaml b/pubspec.yaml index 060077e..9dfe644 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -99,7 +99,7 @@ dependencies: # A cross platform plugin for displaying and scheduling local notifications for Flutter applications # with the ability to customize for each platform. - flutter_local_notifications: ^17.1.2 + flutter_local_notifications: 13.0.0 # The Font Awesome Icon pack available as Flutter Icons. Provides 1600 additional icons to use # in your apps. From 0b87d05b36b5bc89bdd20c5c21001142907c8284 Mon Sep 17 00:00:00 2001 From: CoderJava Date: Tue, 29 Oct 2024 07:41:19 +0700 Subject: [PATCH 06/18] Buat function `removeTrailingSlash` didalam helper.dart Function tersebut berfungsi untuk menghapus karakter "/" diakhir dari sebuah String. --- lib/core/util/helper.dart | 4 ++++ test/util/helper_test.dart | 21 +++++++++++++++++++++ 2 files changed, 25 insertions(+) diff --git a/lib/core/util/helper.dart b/lib/core/util/helper.dart index 78f8d38..a9eab91 100644 --- a/lib/core/util/helper.dart +++ b/lib/core/util/helper.dart @@ -101,4 +101,8 @@ class Helper { return ConstantErrorMessage().failureUnknown; } } + + String removeTrailingSlash(String input) { + return input.replaceAll(RegExp(r'/+$'), ''); + } } diff --git a/test/util/helper_test.dart b/test/util/helper_test.dart index ec5779d..06f184c 100644 --- a/test/util/helper_test.dart +++ b/test/util/helper_test.dart @@ -170,4 +170,25 @@ void main() { expect(unknownFailure, constantErrorMessage.failureUnknown); }, ); + + test( + 'pastikan function removeTrailingSlash bisa menghapus karakter "/" diakhir dari sebuah string.', + () async { + // arrange + const input = 'https://example.com/'; + const input2 = 'https://example.com'; + const input3 = 'https://example.com////'; + const output = 'https://example.com'; + + // act + final actual1 = helper.removeTrailingSlash(input); + final actual2 = helper.removeTrailingSlash(input2); + final actual3 = helper.removeTrailingSlash(input3); + + // assert + expect(actual1, output); + expect(actual2, output); + expect(actual3, output); + }, + ); } From 7e10f16144124026d4c3acdfb24c802c82649c9d Mon Sep 17 00:00:00 2001 From: CoderJava Date: Tue, 29 Oct 2024 07:41:44 +0700 Subject: [PATCH 07/18] Hapus karakter "/" diakhir dari hostname --- .../page/setup_credential/setup_credential_page.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/feature/presentation/page/setup_credential/setup_credential_page.dart b/lib/feature/presentation/page/setup_credential/setup_credential_page.dart index 6fcdd8d..dd76164 100644 --- a/lib/feature/presentation/page/setup_credential/setup_credential_page.dart +++ b/lib/feature/presentation/page/setup_credential/setup_credential_page.dart @@ -148,7 +148,7 @@ class _SetupCredentialPageState extends State { ) as bool?; } if (isContinue != null && isContinue) { - final hostname = controllerHostname.text.trim(); + final hostname = helper.removeTrailingSlash(controllerHostname.text.trim()).trim(); await sharedPreferencesManager.putString(SharedPreferencesManager.keyDomainApi, hostname); helper.setDomainApiToFlavor(hostname); di.init(); From ce03a64f963000137fcd62f33d1a4fc6d2d182a0 Mon Sep 17 00:00:00 2001 From: CoderJava Date: Tue, 29 Oct 2024 08:23:17 +0700 Subject: [PATCH 08/18] Tambahkan plugin `shared_preferences_tools` --- devtools_options.yaml | 1 + pubspec.yaml | 3 +++ 2 files changed, 4 insertions(+) create mode 100644 devtools_options.yaml diff --git a/devtools_options.yaml b/devtools_options.yaml new file mode 100644 index 0000000..7e7e7f6 --- /dev/null +++ b/devtools_options.yaml @@ -0,0 +1 @@ +extensions: diff --git a/pubspec.yaml b/pubspec.yaml index 9dfe644..95e0e96 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -168,6 +168,9 @@ dev_dependencies: # This package provides a library that performs static analysis of Dart code. analyzer: ^6.4.1 + # DevTools extension for Flutter: Manage SharedPreferences efficiently. Edit, search, and view keys. + shared_preferences_tools: 1.0.3 + # For information on the generic Dart part of this file, see the # following page: https://dart.dev/tools/pub/pubspec From 8a6369fca3a26907745ff25e42a617c5eae2dc51 Mon Sep 17 00:00:00 2001 From: CoderJava Date: Tue, 29 Oct 2024 08:23:31 +0700 Subject: [PATCH 09/18] Buat endpoint `ping` Sekalian buat unit testnya. --- .../general/general_remote_data_source.dart | 36 ++++++ .../general_remote_data_source_test.dart | 108 ++++++++++++++++++ 2 files changed, 144 insertions(+) create mode 100644 lib/feature/data/datasource/general/general_remote_data_source.dart create mode 100644 test/feature/data/datasource/general/general_remote_data_source_test.dart diff --git a/lib/feature/data/datasource/general/general_remote_data_source.dart b/lib/feature/data/datasource/general/general_remote_data_source.dart new file mode 100644 index 0000000..6b79539 --- /dev/null +++ b/lib/feature/data/datasource/general/general_remote_data_source.dart @@ -0,0 +1,36 @@ +import 'package:dio/dio.dart'; +import 'package:dipantau_desktop_client/config/flavor_config.dart'; +import 'package:dipantau_desktop_client/feature/data/model/general/general_response.dart'; + +abstract class GeneralRemoteDataSource { + /// Panggil endpoint [host]/api/ping + /// + /// Throws [DioException] untuk semua error kode + late String pathPing; + + Future ping(); +} + +class GeneralRemoteDataSourceImpl implements GeneralRemoteDataSource { + final Dio dio; + + GeneralRemoteDataSourceImpl({ + required this.dio, + }); + + final baseUrl = FlavorConfig.instance.values.baseUrl; + + @override + String pathPing = ''; + + @override + Future ping() async { + pathPing = '$baseUrl/api/ping'; + final response = await dio.get(pathPing); + if (response.statusCode.toString().startsWith('2')) { + return GeneralResponse.fromJson(response.data); + } else { + throw DioException(requestOptions: RequestOptions(path: pathPing)); + } + } +} diff --git a/test/feature/data/datasource/general/general_remote_data_source_test.dart b/test/feature/data/datasource/general/general_remote_data_source_test.dart new file mode 100644 index 0000000..02ad540 --- /dev/null +++ b/test/feature/data/datasource/general/general_remote_data_source_test.dart @@ -0,0 +1,108 @@ +import 'dart:convert'; + +import 'package:dio/dio.dart'; +import 'package:dipantau_desktop_client/config/flavor_config.dart'; +import 'package:dipantau_desktop_client/feature/data/datasource/general/general_remote_data_source.dart'; +import 'package:dipantau_desktop_client/feature/data/model/general/general_response.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mockito/mockito.dart'; + +import '../../../../fixture/fixture_reader.dart'; +import '../../../../helper/mock_helper.mocks.dart'; + +void main() { + late GeneralRemoteDataSource remoteDataSource; + late MockDio mockDio; + late MockHttpClientAdapter mockDioAdapter; + + const baseUrl = 'https://example.com'; + + setUp(() { + FlavorConfig( + values: FlavorValues( + baseUrl: baseUrl, + baseUrlAuth: '', + baseUrlUser: '', + baseUrlTrack: '', + baseUrlProject: '', + baseUrlSetting: '', + ), + ); + mockDio = MockDio(); + mockDioAdapter = MockHttpClientAdapter(); + mockDio.httpClientAdapter = mockDioAdapter; + remoteDataSource = GeneralRemoteDataSourceImpl(dio: mockDio); + }); + + final tRequestOptions = RequestOptions(path: ''); + + group('ping', () { + const tPathResponse = 'general_response.json'; + final tResponse = GeneralResponse.fromJson( + json.decode( + fixture(tPathResponse), + ), + ); + + void setUpMockDioSuccess() { + final responsePayload = json.decode(fixture(tPathResponse)); + final response = Response( + requestOptions: tRequestOptions, + data: responsePayload, + statusCode: 200, + headers: Headers.fromMap({ + Headers.contentTypeHeader: [Headers.jsonContentType], + }), + ); + when(mockDio.get(any)).thenAnswer((_) async => response); + } + + test( + 'pastikan endpoint ping benar-benar terpanggil dengan method GET', + () async { + // arrange + setUpMockDioSuccess(); + + // act + await remoteDataSource.ping(); + + // assert + verify(mockDio.get('$baseUrl/api/ping')); + }, + ); + + test( + 'pastikan mengembalikan objek class model GeneralResponse ketika menerima respon sukses ' + 'dari endpoint', + () async { + // arrange + setUpMockDioSuccess(); + + // act + final result = await remoteDataSource.ping(); + + // assert + expect(result, tResponse); + }, + ); + + test( + 'pastikan akan menerima exception DioError ketika menerima respon kegagalan dari endpoint', + () async { + // arrange + final response = Response( + requestOptions: tRequestOptions, + data: 'Bad Request', + statusCode: 400, + ); + when(mockDio.get(any)).thenAnswer((_) async => response); + + // act + final call = remoteDataSource.ping(); + + // assert + expect(() => call, throwsA(const TypeMatcher())); + }, + ); + }); +} From 7524e0beef51c5428cbc06fb893148fbeb1907c1 Mon Sep 17 00:00:00 2001 From: CoderJava Date: Sat, 16 Nov 2024 20:57:54 +0700 Subject: [PATCH 10/18] Daftarkan `GeneralRemoteDataSource` kedalam mock_helper.dart --- test/helper/mock_helper.dart | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/helper/mock_helper.dart b/test/helper/mock_helper.dart index ab3f6c1..7cc2010 100644 --- a/test/helper/mock_helper.dart +++ b/test/helper/mock_helper.dart @@ -4,6 +4,7 @@ import 'package:dipantau_desktop_client/core/network/network_info.dart'; import 'package:dipantau_desktop_client/core/util/helper.dart'; import 'package:dipantau_desktop_client/core/util/shared_preferences_manager.dart'; import 'package:dipantau_desktop_client/feature/data/datasource/auth/auth_remote_data_source.dart'; +import 'package:dipantau_desktop_client/feature/data/datasource/general/general_remote_data_source.dart'; import 'package:dipantau_desktop_client/feature/data/datasource/project/project_remote_data_source.dart'; import 'package:dipantau_desktop_client/feature/data/datasource/setting/setting_remote_data_source.dart'; import 'package:dipantau_desktop_client/feature/data/datasource/track/track_remote_data_source.dart'; @@ -53,6 +54,7 @@ import 'package:shared_preferences/shared_preferences.dart'; MockSpec(), MockSpec(), MockSpec(), + MockSpec(), MockSpec(), MockSpec(), MockSpec(), From 5ee2f1d82d6b942eb5bb99eef507b38659127afa Mon Sep 17 00:00:00 2001 From: CoderJava Date: Sat, 16 Nov 2024 20:58:17 +0700 Subject: [PATCH 11/18] Buat implement function endpoint ping Sekalian buat unit testnya. --- .../general/general_repository_impl.dart | 56 ++++++ .../general/general_repository.dart | 6 + .../general/general_repository_impl_test.dart | 188 ++++++++++++++++++ 3 files changed, 250 insertions(+) create mode 100644 lib/feature/data/repository/general/general_repository_impl.dart create mode 100644 lib/feature/domain/repository/general/general_repository.dart create mode 100644 test/feature/data/repository/general/general_repository_impl_test.dart diff --git a/lib/feature/data/repository/general/general_repository_impl.dart b/lib/feature/data/repository/general/general_repository_impl.dart new file mode 100644 index 0000000..402903c --- /dev/null +++ b/lib/feature/data/repository/general/general_repository_impl.dart @@ -0,0 +1,56 @@ +import 'package:dio/dio.dart'; +import 'package:dipantau_desktop_client/core/error/failure.dart'; +import 'package:dipantau_desktop_client/core/network/network_info.dart'; +import 'package:dipantau_desktop_client/feature/data/datasource/general/general_remote_data_source.dart'; +import 'package:dipantau_desktop_client/feature/data/model/general/general_response.dart'; +import 'package:dipantau_desktop_client/feature/domain/repository/general/general_repository.dart'; + +class GeneralRepositoryImpl implements GeneralRepository { + final GeneralRemoteDataSource remoteDataSource; + final NetworkInfo networkInfo; + + GeneralRepositoryImpl({ + required this.remoteDataSource, + required this.networkInfo, + }); + + String getErrorMessageFromEndpoint(dynamic dynamicErrorMessage, String httpErrorMessage, int? statusCode) { + if (dynamicErrorMessage is Map && dynamicErrorMessage.containsKey('message')) { + return '$statusCode ${dynamicErrorMessage['message']}'; + } else if (dynamicErrorMessage is String) { + return httpErrorMessage; + } else { + return httpErrorMessage; + } + } + + @override + Future<({Failure? failure, GeneralResponse? response})> ping() async { + Failure? failure; + GeneralResponse? response; + final isConnected = await networkInfo.isConnected; + if (isConnected) { + try { + response = await remoteDataSource.ping(); + } on DioException catch (error) { + final message = error.message ?? error.toString(); + if (error.response == null) { + failure = ServerFailure(message); + } else { + final errorMessage = getErrorMessageFromEndpoint( + error.response?.data, + message, + error.response?.statusCode, + ); + failure = ServerFailure(errorMessage); + } + } on TypeError catch (error) { + final errorMessage = error.toString(); + failure = ParsingFailure(errorMessage); + } + } else { + failure = ConnectionFailure(); + } + return (failure: failure, response: response); + } +} diff --git a/lib/feature/domain/repository/general/general_repository.dart b/lib/feature/domain/repository/general/general_repository.dart new file mode 100644 index 0000000..a740d15 --- /dev/null +++ b/lib/feature/domain/repository/general/general_repository.dart @@ -0,0 +1,6 @@ +import 'package:dipantau_desktop_client/core/error/failure.dart'; +import 'package:dipantau_desktop_client/feature/data/model/general/general_response.dart'; + +abstract class GeneralRepository { + Future<({Failure? failure, GeneralResponse? response})> ping(); +} \ No newline at end of file diff --git a/test/feature/data/repository/general/general_repository_impl_test.dart b/test/feature/data/repository/general/general_repository_impl_test.dart new file mode 100644 index 0000000..b3595b8 --- /dev/null +++ b/test/feature/data/repository/general/general_repository_impl_test.dart @@ -0,0 +1,188 @@ +import 'dart:convert'; + +import 'package:dio/dio.dart'; +import 'package:dipantau_desktop_client/core/error/failure.dart'; +import 'package:dipantau_desktop_client/feature/data/model/general/general_response.dart'; +import 'package:dipantau_desktop_client/feature/data/repository/general/general_repository_impl.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mockito/mockito.dart'; + +import '../../../../fixture/fixture_reader.dart'; +import '../../../../helper/mock_helper.mocks.dart'; + +void main() { + late GeneralRepositoryImpl repository; + late MockGeneralRemoteDataSource mockRemoteDataSource; + late MockNetworkInfo mockNetworkInfo; + + setUp(() { + mockRemoteDataSource = MockGeneralRemoteDataSource(); + mockNetworkInfo = MockNetworkInfo(); + repository = GeneralRepositoryImpl( + remoteDataSource: mockRemoteDataSource, + networkInfo: mockNetworkInfo, + ); + }); + + final tRequestOptions = RequestOptions(path: ''); + + void setUpMockNetworkConnected() { + when(mockNetworkInfo.isConnected).thenAnswer((_) async => true); + } + + void setUpMockNetworkDisconnected() { + when(mockNetworkInfo.isConnected).thenAnswer((_) async => false); + } + + void testDisconnected(Function endpointInvoke) { + test( + 'pastikan mengembalikan objek ConnectionFailure ketika device tidak terhubung ke internet', + () async { + // arrange + setUpMockNetworkDisconnected(); + + // act + final result = await endpointInvoke.call(); + + // assert + verify(mockNetworkInfo.isConnected); + expect(result.failure, ConnectionFailure()); + }, + ); + } + + void testServerFailureString(Function whenInvoke, Function actInvoke, Function verifyInvoke) { + test( + 'pastikan mengembalikan objek ServerFailure ketika repository menerima respon kegagalan ' + 'dari endpoint dengan respon data html atau string', + () async { + // arrange + setUpMockNetworkConnected(); + when(whenInvoke.call()).thenThrow( + DioException( + requestOptions: tRequestOptions, + message: 'testError', + response: Response( + requestOptions: tRequestOptions, + data: 'testDataError', + statusCode: 400, + ), + ), + ); + + // act + final result = await actInvoke.call(); + + // assert + verify(verifyInvoke.call()); + expect(result.failure, ServerFailure('testError')); + }, + ); + } + + void testParsingFailure(Function whenInvoke, Function actInvoke, Function verifyInvoke) { + test( + 'pastikan mengembalikan objek ParsingFailure ketika RemoteDataSource menerima respon kegagalan ' + 'dari endpoint', + () async { + // arrange + setUpMockNetworkConnected(); + when(whenInvoke.call()).thenThrow(TypeError()); + + // act + final result = await actInvoke.call(); + + // assert + verify(verifyInvoke.call()); + expect(result.failure, ParsingFailure(TypeError().toString())); + }, + ); + } + + group('ping', () { + final tResponse = GeneralResponse.fromJson( + json.decode( + fixture('general_response.json'), + ), + ); + + test( + 'pastikan mengembalikan objek model GeneralResponse ketika RemoteDataSource berhasil menerima ' + 'respon sukses dari endpoint', + () async { + // arrange + setUpMockNetworkConnected(); + when(mockRemoteDataSource.ping()).thenAnswer((_) async => tResponse); + + // act + final result = await repository.ping(); + + // assert + verify(mockRemoteDataSource.ping()); + expect(result.response, tResponse); + }, + ); + + test( + 'pastikan mengembalikan objek ServerFailure ketika RemoteDataSource berhasil menerima ' + 'respon timeout dari endpoint', + () async { + // arrange + setUpMockNetworkConnected(); + when(mockRemoteDataSource.ping()) + .thenThrow(DioException(requestOptions: tRequestOptions, message: 'testError')); + + // act + final result = await repository.ping(); + + // assert + verify(mockRemoteDataSource.ping()); + expect(result.failure, ServerFailure('testError')); + }, + ); + + test( + 'pastikan mengembalikan objek ServerFailure ketika RemoteDataSource menerima respon kegagaln ' + 'dari endpoint', + () async { + // arrange + setUpMockNetworkConnected(); + when(mockRemoteDataSource.ping()).thenThrow( + DioException( + requestOptions: tRequestOptions, + message: 'testError', + response: Response( + requestOptions: tRequestOptions, + data: { + 'title': 'testTitleError', + 'message': 'testMessageError', + }, + statusCode: 400, + ), + ), + ); + + // act + final result = await repository.ping(); + + // assert + verify(mockRemoteDataSource.ping()); + expect(result.failure, ServerFailure('400 testMessageError')); + }, + ); + + testServerFailureString( + () => mockRemoteDataSource.ping(), + () => repository.ping(), + () => mockRemoteDataSource.ping(), + ); + + testParsingFailure( + () => mockRemoteDataSource.ping(), + () => repository.ping(), + () => mockRemoteDataSource.ping(), + ); + + testDisconnected(() => repository.ping()); + }); +} From 37b3a0634628ba850ef28dc083d8488b5a70a4e5 Mon Sep 17 00:00:00 2001 From: CoderJava Date: Sat, 16 Nov 2024 21:06:38 +0700 Subject: [PATCH 12/18] Buat use case endpoint ping Sekalian buat unit testnya. --- lib/feature/domain/usecase/ping/ping.dart | 15 +++++++ .../domain/usecase/ping/ping_test.dart | 43 +++++++++++++++++++ 2 files changed, 58 insertions(+) create mode 100644 lib/feature/domain/usecase/ping/ping.dart create mode 100644 test/feature/domain/usecase/ping/ping_test.dart diff --git a/lib/feature/domain/usecase/ping/ping.dart b/lib/feature/domain/usecase/ping/ping.dart new file mode 100644 index 0000000..978bc5e --- /dev/null +++ b/lib/feature/domain/usecase/ping/ping.dart @@ -0,0 +1,15 @@ +import 'package:dipantau_desktop_client/core/error/failure.dart'; +import 'package:dipantau_desktop_client/core/usecase/usecase.dart'; +import 'package:dipantau_desktop_client/feature/data/model/general/general_response.dart'; +import 'package:dipantau_desktop_client/feature/domain/repository/general/general_repository.dart'; + +class Ping implements UseCaseRecords { + final GeneralRepository repository; + + Ping({required this.repository}); + + @override + Future<({Failure? failure, GeneralResponse? response})> call(NoParams params) { + return repository.ping(); + } +} \ No newline at end of file diff --git a/test/feature/domain/usecase/ping/ping_test.dart b/test/feature/domain/usecase/ping/ping_test.dart new file mode 100644 index 0000000..7ce5c03 --- /dev/null +++ b/test/feature/domain/usecase/ping/ping_test.dart @@ -0,0 +1,43 @@ +import 'dart:convert'; + +import 'package:dipantau_desktop_client/core/usecase/usecase.dart'; +import 'package:dipantau_desktop_client/feature/data/model/general/general_response.dart'; +import 'package:dipantau_desktop_client/feature/domain/usecase/ping/ping.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mockito/mockito.dart'; + +import '../../../../fixture/fixture_reader.dart'; +import '../../../../helper/mock_helper.mocks.dart'; + +void main() { + late Ping useCase; + late MockGeneralRepository mockRepository; + + setUp(() { + mockRepository = MockGeneralRepository(); + useCase = Ping(repository: mockRepository); + }); + + test( + 'pastikan objek repository berhasil menerima respon sukses atau gagal dari endpoint', + () async { + // arrange + final tResponse = GeneralResponse.fromJson( + json.decode( + fixture('general_response.json'), + ), + ); + final tParams = NoParams(); + final tResult = (failure: null, response: tResponse); + when(mockRepository.ping()).thenAnswer((_) async => tResult); + + // act + final result = await useCase(tParams); + + // assert + expect(result, tResult); + verify(mockRepository.ping()); + verifyNoMoreInteractions(mockRepository); + }, + ); +} From 02b6ab5f34026c43df21cc7e36f905e8f5968ce8 Mon Sep 17 00:00:00 2001 From: CoderJava Date: Sat, 16 Nov 2024 21:06:53 +0700 Subject: [PATCH 13/18] Daftarkan use case endpoint ping kedalam mock_helper.dart --- test/helper/mock_helper.dart | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/test/helper/mock_helper.dart b/test/helper/mock_helper.dart index 7cc2010..1913f43 100644 --- a/test/helper/mock_helper.dart +++ b/test/helper/mock_helper.dart @@ -10,6 +10,7 @@ import 'package:dipantau_desktop_client/feature/data/datasource/setting/setting_ import 'package:dipantau_desktop_client/feature/data/datasource/track/track_remote_data_source.dart'; import 'package:dipantau_desktop_client/feature/data/datasource/user/user_remote_data_source.dart'; import 'package:dipantau_desktop_client/feature/domain/repository/auth/auth_repository.dart'; +import 'package:dipantau_desktop_client/feature/domain/repository/general/general_repository.dart'; import 'package:dipantau_desktop_client/feature/domain/repository/project/project_repository.dart'; import 'package:dipantau_desktop_client/feature/domain/repository/setting/setting_repository.dart'; import 'package:dipantau_desktop_client/feature/domain/repository/track/track_repository.dart'; @@ -30,6 +31,7 @@ import 'package:dipantau_desktop_client/feature/domain/usecase/get_track_user/ge import 'package:dipantau_desktop_client/feature/domain/usecase/get_track_user_lite/get_track_user_lite.dart'; import 'package:dipantau_desktop_client/feature/domain/usecase/get_user_setting/get_user_setting.dart'; import 'package:dipantau_desktop_client/feature/domain/usecase/login/login.dart'; +import 'package:dipantau_desktop_client/feature/domain/usecase/ping/ping.dart'; import 'package:dipantau_desktop_client/feature/domain/usecase/refresh_token/refresh_token.dart'; import 'package:dipantau_desktop_client/feature/domain/usecase/reset_password/reset_password.dart'; import 'package:dipantau_desktop_client/feature/domain/usecase/send_app_version/send_app_version.dart'; @@ -60,6 +62,7 @@ import 'package:shared_preferences/shared_preferences.dart'; MockSpec(), MockSpec(), MockSpec(), + MockSpec(), MockSpec(), MockSpec(), MockSpec(), @@ -84,5 +87,6 @@ import 'package:shared_preferences/shared_preferences.dart'; MockSpec(), MockSpec(), MockSpec(), + MockSpec(), ]) void main() {} From 8631f77d92035d377bc0e786f0c8bd0a72410b49 Mon Sep 17 00:00:00 2001 From: CoderJava Date: Sat, 16 Nov 2024 21:09:13 +0700 Subject: [PATCH 14/18] Daftarkan instance `GeneralRemoteDataSource`, `GeneralRepository` dan use case `Ping` kedalam injection_container.dart --- lib/injection_container.dart | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/lib/injection_container.dart b/lib/injection_container.dart index 9d581a2..ca4f8e4 100644 --- a/lib/injection_container.dart +++ b/lib/injection_container.dart @@ -7,17 +7,20 @@ import 'package:dipantau_desktop_client/core/util/notification_helper.dart'; import 'package:dipantau_desktop_client/core/util/shared_preferences_manager.dart'; import 'package:dipantau_desktop_client/core/util/widget_helper.dart'; import 'package:dipantau_desktop_client/feature/data/datasource/auth/auth_remote_data_source.dart'; +import 'package:dipantau_desktop_client/feature/data/datasource/general/general_remote_data_source.dart'; import 'package:dipantau_desktop_client/feature/data/datasource/project/project_remote_data_source.dart'; import 'package:dipantau_desktop_client/feature/data/datasource/setting/setting_remote_data_source.dart'; import 'package:dipantau_desktop_client/feature/data/datasource/track/track_remote_data_source.dart'; import 'package:dipantau_desktop_client/feature/data/datasource/user/user_remote_data_source.dart'; import 'package:dipantau_desktop_client/feature/data/repository/auth/auth_repository_impl.dart'; +import 'package:dipantau_desktop_client/feature/data/repository/general/general_repository_impl.dart'; import 'package:dipantau_desktop_client/feature/data/repository/project/project_repository_impl.dart'; import 'package:dipantau_desktop_client/feature/data/repository/setting/setting_repository_impl.dart'; import 'package:dipantau_desktop_client/feature/data/repository/track/track_repository_impl.dart'; import 'package:dipantau_desktop_client/feature/data/repository/user/user_repository_impl.dart'; import 'package:dipantau_desktop_client/feature/database/app_database.dart'; import 'package:dipantau_desktop_client/feature/domain/repository/auth/auth_repository.dart'; +import 'package:dipantau_desktop_client/feature/domain/repository/general/general_repository.dart'; import 'package:dipantau_desktop_client/feature/domain/repository/project/project_repository.dart'; import 'package:dipantau_desktop_client/feature/domain/repository/setting/setting_repository.dart'; import 'package:dipantau_desktop_client/feature/domain/repository/track/track_repository.dart'; @@ -38,6 +41,7 @@ import 'package:dipantau_desktop_client/feature/domain/usecase/get_track_user/ge import 'package:dipantau_desktop_client/feature/domain/usecase/get_track_user_lite/get_track_user_lite.dart'; import 'package:dipantau_desktop_client/feature/domain/usecase/get_user_setting/get_user_setting.dart'; import 'package:dipantau_desktop_client/feature/domain/usecase/login/login.dart'; +import 'package:dipantau_desktop_client/feature/domain/usecase/ping/ping.dart'; import 'package:dipantau_desktop_client/feature/domain/usecase/refresh_token/refresh_token.dart'; import 'package:dipantau_desktop_client/feature/domain/usecase/reset_password/reset_password.dart'; import 'package:dipantau_desktop_client/feature/domain/usecase/send_app_version/send_app_version.dart'; @@ -195,6 +199,7 @@ void init() { sl.registerLazySingleton(() => GetAllUserSetting(repository: sl())); sl.registerLazySingleton(() => GetUserSetting(repository: sl())); sl.registerLazySingleton(() => UpdateUserSetting(repository: sl())); + sl.registerLazySingleton(() => Ping(repository: sl())); // repository sl.registerLazySingleton( @@ -227,6 +232,12 @@ void init() { networkInfo: sl(), ), ); + sl.registerLazySingleton( + () => GeneralRepositoryImpl( + remoteDataSource: sl(), + networkInfo: sl(), + ), + ); // data source sl.registerLazySingleton( @@ -254,6 +265,11 @@ void init() { dio: sl(instanceName: dioRefreshToken), ), ); + sl.registerLazySingleton( + () => GeneralRemoteDataSourceImpl( + dio: sl(instanceName: dioLogging), + ), + ); // core sl.registerLazySingleton(() => NetworkInfoImpl(sl())); From 0e9d736da7707ecfb1ccab34ac6e0d9012b36789 Mon Sep 17 00:00:00 2001 From: CoderJava Date: Sat, 16 Nov 2024 21:35:35 +0700 Subject: [PATCH 15/18] Buat business logic ping untuk memastikan hostname yang dimasukkan benar Sekalian buat unit testnya. --- .../setup_credential_bloc.dart | 38 +++++ .../setup_credential_event.dart | 5 + .../setup_credential_state.dart | 22 +++ .../setup_credential_bloc_test.dart | 130 ++++++++++++++++++ .../setup_credential_state_test.dart | 19 +++ 5 files changed, 214 insertions(+) create mode 100644 lib/feature/presentation/bloc/setup_credential/setup_credential_bloc.dart create mode 100644 lib/feature/presentation/bloc/setup_credential/setup_credential_event.dart create mode 100644 lib/feature/presentation/bloc/setup_credential/setup_credential_state.dart create mode 100644 test/feature/presentation/bloc/setup_credential/setup_credential_bloc_test.dart create mode 100644 test/feature/presentation/bloc/setup_credential/setup_credential_state_test.dart diff --git a/lib/feature/presentation/bloc/setup_credential/setup_credential_bloc.dart b/lib/feature/presentation/bloc/setup_credential/setup_credential_bloc.dart new file mode 100644 index 0000000..380f979 --- /dev/null +++ b/lib/feature/presentation/bloc/setup_credential/setup_credential_bloc.dart @@ -0,0 +1,38 @@ +import 'dart:async'; + +import 'package:bloc/bloc.dart'; +import 'package:dipantau_desktop_client/core/usecase/usecase.dart'; +import 'package:dipantau_desktop_client/core/util/helper.dart'; +import 'package:dipantau_desktop_client/feature/domain/usecase/ping/ping.dart'; + +part 'setup_credential_event.dart'; +part 'setup_credential_state.dart'; + +class SetupCredentialBloc extends Bloc { + final Helper helper; + final Ping ping; + + SetupCredentialBloc({ + required this.helper, + required this.ping, + }) : super(InitialSetupCredentialState()) { + on(_onPingSetupCredentialEvent); + } + + FutureOr _onPingSetupCredentialEvent( + PingSetupCredentialEvent event, + Emitter emit, + ) async { + emit(LoadingSetupCredentialState()); + final result = await ping(NoParams()); + final response = result.response; + final failure = result.failure; + if (response != null) { + emit(SuccessPingSetupCredentialState()); + return; + } + + final errorMessage = helper.getErrorMessageFromFailure(failure); + emit(FailureSetupCredentialState(errorMessage: errorMessage)); + } +} diff --git a/lib/feature/presentation/bloc/setup_credential/setup_credential_event.dart b/lib/feature/presentation/bloc/setup_credential/setup_credential_event.dart new file mode 100644 index 0000000..6895b35 --- /dev/null +++ b/lib/feature/presentation/bloc/setup_credential/setup_credential_event.dart @@ -0,0 +1,5 @@ +part of 'setup_credential_bloc.dart'; + +abstract class SetupCredentialEvent {} + +class PingSetupCredentialEvent extends SetupCredentialEvent {} \ No newline at end of file diff --git a/lib/feature/presentation/bloc/setup_credential/setup_credential_state.dart b/lib/feature/presentation/bloc/setup_credential/setup_credential_state.dart new file mode 100644 index 0000000..2c3d105 --- /dev/null +++ b/lib/feature/presentation/bloc/setup_credential/setup_credential_state.dart @@ -0,0 +1,22 @@ +part of 'setup_credential_bloc.dart'; + +abstract class SetupCredentialState {} + +class InitialSetupCredentialState extends SetupCredentialState {} + +class LoadingSetupCredentialState extends SetupCredentialState {} + +class FailureSetupCredentialState extends SetupCredentialState { + final String errorMessage; + + FailureSetupCredentialState({ + required this.errorMessage, + }); + + @override + String toString() { + return 'FailureSetupCredentialState{errorMessage: $errorMessage}'; + } +} + +class SuccessPingSetupCredentialState extends SetupCredentialState {} diff --git a/test/feature/presentation/bloc/setup_credential/setup_credential_bloc_test.dart b/test/feature/presentation/bloc/setup_credential/setup_credential_bloc_test.dart new file mode 100644 index 0000000..bbca100 --- /dev/null +++ b/test/feature/presentation/bloc/setup_credential/setup_credential_bloc_test.dart @@ -0,0 +1,130 @@ +import 'dart:convert'; + +import 'package:bloc_test/bloc_test.dart'; +import 'package:dipantau_desktop_client/core/error/failure.dart'; +import 'package:dipantau_desktop_client/core/usecase/usecase.dart'; +import 'package:dipantau_desktop_client/feature/data/model/general/general_response.dart'; +import 'package:dipantau_desktop_client/feature/presentation/bloc/setup_credential/setup_credential_bloc.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mockito/mockito.dart'; + +import '../../../../fixture/fixture_reader.dart'; +import '../../../../helper/mock_helper.mocks.dart'; + +void main() { + late SetupCredentialBloc bloc; + late MockHelper mockHelper; + late MockPing mockPing; + + const errorMessage = 'testErrorMessage'; + + setUp(() { + mockHelper = MockHelper(); + mockPing = MockPing(); + bloc = SetupCredentialBloc( + helper: mockHelper, + ping: mockPing, + ); + }); + + test( + 'pastikan output dari initial state', + () async { + // assert + expect( + bloc.state, + isA(), + ); + }, + ); + + group('ping setup credential', () { + final params = NoParams(); + final event = PingSetupCredentialEvent(); + + blocTest( + 'pastikan emit [LoadingSetupCredentialState, SuccessPingSetupCredentialState] ketika terima event ' + 'PingSetupCredentialEvent dengan proses berhasil', + build: () { + final response = GeneralResponse.fromJson( + json.decode( + fixture('general_response.json'), + ), + ); + final result = (failure: null, response: response); + when(mockPing(any)).thenAnswer((_) async => result); + return bloc; + }, + act: (SetupCredentialBloc bloc) { + return bloc.add(event); + }, + expect: () => [ + isA(), + isA(), + ], + verify: (_) { + verify(mockPing(params)); + }, + ); + + blocTest( + 'pastikan emit [LoadingSetupCredentialState, FailureSetupCredentialState] ketika terima event ' + 'PingSetupCredentialEvent dengan proses gagal dari endpoint', + build: () { + final result = (failure: ServerFailure(errorMessage), response: null); + when(mockPing(any)).thenAnswer((_) async => result); + return bloc; + }, + act: (SetupCredentialBloc bloc) { + return bloc.add(event); + }, + expect: () => [ + isA(), + isA(), + ], + verify: (_) { + verify(mockPing(params)); + }, + ); + + blocTest( + 'pastikan emit [LoadingSetupCredentialState, FailureSetupCredentialState] ketika terima event ' + 'PingSetupCredentialEvent dengan kondisi internet tidak terhubung ketika hit endpoint', + build: () { + final result = (failure: ConnectionFailure(), response: null); + when(mockPing(any)).thenAnswer((_) async => result); + return bloc; + }, + act: (SetupCredentialBloc bloc) { + return bloc.add(event); + }, + expect: () => [ + isA(), + isA(), + ], + verify: (_) { + verify(mockPing(params)); + }, + ); + + blocTest( + 'pastikan emit [LoadingSetupCredentialState, FailureSetupCredentialState] ketika terima event ' + 'PingSetupCredentialEvent dengan proses gagal parsing respon JSON endpoint', + build: () { + final result = (failure: ParsingFailure(errorMessage), response: null); + when(mockPing(any)).thenAnswer((_) async => result); + return bloc; + }, + act: (SetupCredentialBloc bloc) { + return bloc.add(event); + }, + expect: () => [ + isA(), + isA(), + ], + verify: (_) { + verify(mockPing(params)); + }, + ); + }); +} diff --git a/test/feature/presentation/bloc/setup_credential/setup_credential_state_test.dart b/test/feature/presentation/bloc/setup_credential/setup_credential_state_test.dart new file mode 100644 index 0000000..e28645a --- /dev/null +++ b/test/feature/presentation/bloc/setup_credential/setup_credential_state_test.dart @@ -0,0 +1,19 @@ +import 'package:dipantau_desktop_client/feature/presentation/bloc/setup_credential/setup_credential_bloc.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + group('FailureSetupCredentialState', () { + final state = FailureSetupCredentialState(errorMessage: 'errorMessage'); + + test( + 'pastikan output dari fungsi toString', + () async { + // assert + expect( + state.toString(), + 'FailureSetupCredentialState{errorMessage: ${state.errorMessage}}', + ); + }, + ); + }); +} From f5bbc6d69327a694427faafaf79010f0f8759e71 Mon Sep 17 00:00:00 2001 From: CoderJava Date: Sat, 16 Nov 2024 21:35:53 +0700 Subject: [PATCH 16/18] Daftarkan instance `SetupCredentialBloc` kedalam injection_container.dart --- lib/injection_container.dart | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/lib/injection_container.dart b/lib/injection_container.dart index ca4f8e4..a72e1a5 100644 --- a/lib/injection_container.dart +++ b/lib/injection_container.dart @@ -61,6 +61,7 @@ import 'package:dipantau_desktop_client/feature/presentation/bloc/project/projec import 'package:dipantau_desktop_client/feature/presentation/bloc/report_screenshot/report_screenshot_bloc.dart'; import 'package:dipantau_desktop_client/feature/presentation/bloc/reset_password/reset_password_bloc.dart'; import 'package:dipantau_desktop_client/feature/presentation/bloc/setting/setting_bloc.dart'; +import 'package:dipantau_desktop_client/feature/presentation/bloc/setup_credential/setup_credential_bloc.dart'; import 'package:dipantau_desktop_client/feature/presentation/bloc/sign_up/sign_up_bloc.dart'; import 'package:dipantau_desktop_client/feature/presentation/bloc/sync_manual/sync_manual_bloc.dart'; import 'package:dipantau_desktop_client/feature/presentation/bloc/tracking/tracking_bloc.dart'; @@ -173,6 +174,12 @@ void init() { getProjectTaskByUserId: sl(), ), ); + sl.registerFactory( + () => SetupCredentialBloc( + helper: sl(), + ping: sl(), + ), + ); // use case sl.registerLazySingleton(() => GetProject(repository: sl())); From 59b917dcbd31b9a7b08de6583015db0ef9382cb6 Mon Sep 17 00:00:00 2001 From: CoderJava Date: Sun, 17 Nov 2024 18:28:14 +0700 Subject: [PATCH 17/18] Tambahkan parameter `baseUrl` di endpoint ping Tambahkan parameter `baseUrl` di endpoint, implement function endpoint, use case, dan business logic ping. Sekalian update unit testnya. --- .../general/general_remote_data_source.dart | 4 +-- .../general/general_repository_impl.dart | 4 +-- .../general/general_repository.dart | 2 +- lib/feature/domain/usecase/ping/ping.dart | 27 ++++++++++++--- .../setup_credential_bloc.dart | 11 +++++-- .../setup_credential_event.dart | 13 +++++++- .../setup_credential_state.dart | 13 +++++++- .../general_remote_data_source_test.dart | 9 ++--- .../general/general_repository_impl_test.dart | 33 ++++++++++--------- .../domain/usecase/ping/ping_test.dart | 33 ++++++++++++++++--- .../setup_credential_bloc_test.dart | 7 ++-- .../setup_credential_event_test.dart | 19 +++++++++++ .../setup_credential_state_test.dart | 15 +++++++++ 13 files changed, 149 insertions(+), 41 deletions(-) create mode 100644 test/feature/presentation/bloc/setup_credential/setup_credential_event_test.dart diff --git a/lib/feature/data/datasource/general/general_remote_data_source.dart b/lib/feature/data/datasource/general/general_remote_data_source.dart index 6b79539..0a98915 100644 --- a/lib/feature/data/datasource/general/general_remote_data_source.dart +++ b/lib/feature/data/datasource/general/general_remote_data_source.dart @@ -8,7 +8,7 @@ abstract class GeneralRemoteDataSource { /// Throws [DioException] untuk semua error kode late String pathPing; - Future ping(); + Future ping(String baseUrl); } class GeneralRemoteDataSourceImpl implements GeneralRemoteDataSource { @@ -24,7 +24,7 @@ class GeneralRemoteDataSourceImpl implements GeneralRemoteDataSource { String pathPing = ''; @override - Future ping() async { + Future ping(String baseUrl) async { pathPing = '$baseUrl/api/ping'; final response = await dio.get(pathPing); if (response.statusCode.toString().startsWith('2')) { diff --git a/lib/feature/data/repository/general/general_repository_impl.dart b/lib/feature/data/repository/general/general_repository_impl.dart index 402903c..be2ad2e 100644 --- a/lib/feature/data/repository/general/general_repository_impl.dart +++ b/lib/feature/data/repository/general/general_repository_impl.dart @@ -25,13 +25,13 @@ class GeneralRepositoryImpl implements GeneralRepository { } @override - Future<({Failure? failure, GeneralResponse? response})> ping() async { + Future<({Failure? failure, GeneralResponse? response})> ping(String baseUrl) async { Failure? failure; GeneralResponse? response; final isConnected = await networkInfo.isConnected; if (isConnected) { try { - response = await remoteDataSource.ping(); + response = await remoteDataSource.ping(baseUrl); } on DioException catch (error) { final message = error.message ?? error.toString(); if (error.response == null) { diff --git a/lib/feature/domain/repository/general/general_repository.dart b/lib/feature/domain/repository/general/general_repository.dart index a740d15..3b26c36 100644 --- a/lib/feature/domain/repository/general/general_repository.dart +++ b/lib/feature/domain/repository/general/general_repository.dart @@ -2,5 +2,5 @@ import 'package:dipantau_desktop_client/core/error/failure.dart'; import 'package:dipantau_desktop_client/feature/data/model/general/general_response.dart'; abstract class GeneralRepository { - Future<({Failure? failure, GeneralResponse? response})> ping(); + Future<({Failure? failure, GeneralResponse? response})> ping(String baseUrl); } \ No newline at end of file diff --git a/lib/feature/domain/usecase/ping/ping.dart b/lib/feature/domain/usecase/ping/ping.dart index 978bc5e..3c28a00 100644 --- a/lib/feature/domain/usecase/ping/ping.dart +++ b/lib/feature/domain/usecase/ping/ping.dart @@ -2,14 +2,33 @@ import 'package:dipantau_desktop_client/core/error/failure.dart'; import 'package:dipantau_desktop_client/core/usecase/usecase.dart'; import 'package:dipantau_desktop_client/feature/data/model/general/general_response.dart'; import 'package:dipantau_desktop_client/feature/domain/repository/general/general_repository.dart'; +import 'package:equatable/equatable.dart'; -class Ping implements UseCaseRecords { +class Ping implements UseCaseRecords { final GeneralRepository repository; Ping({required this.repository}); @override - Future<({Failure? failure, GeneralResponse? response})> call(NoParams params) { - return repository.ping(); + Future<({Failure? failure, GeneralResponse? response})> call(ParamsPing params) { + return repository.ping(params.baseUrl); } -} \ No newline at end of file +} + +class ParamsPing extends Equatable { + final String baseUrl; + + ParamsPing({ + required this.baseUrl, + }); + + @override + List get props => [ + baseUrl, + ]; + + @override + String toString() { + return 'ParamsPing{baseUrl: $baseUrl}'; + } +} diff --git a/lib/feature/presentation/bloc/setup_credential/setup_credential_bloc.dart b/lib/feature/presentation/bloc/setup_credential/setup_credential_bloc.dart index 380f979..e089437 100644 --- a/lib/feature/presentation/bloc/setup_credential/setup_credential_bloc.dart +++ b/lib/feature/presentation/bloc/setup_credential/setup_credential_bloc.dart @@ -1,11 +1,11 @@ import 'dart:async'; import 'package:bloc/bloc.dart'; -import 'package:dipantau_desktop_client/core/usecase/usecase.dart'; import 'package:dipantau_desktop_client/core/util/helper.dart'; import 'package:dipantau_desktop_client/feature/domain/usecase/ping/ping.dart'; part 'setup_credential_event.dart'; + part 'setup_credential_state.dart'; class SetupCredentialBloc extends Bloc { @@ -23,12 +23,17 @@ class SetupCredentialBloc extends Bloc emit, ) async { + final baseUrl = event.baseUrl; emit(LoadingSetupCredentialState()); - final result = await ping(NoParams()); + final result = await ping( + ParamsPing( + baseUrl: baseUrl, + ), + ); final response = result.response; final failure = result.failure; if (response != null) { - emit(SuccessPingSetupCredentialState()); + emit(SuccessPingSetupCredentialState(baseUrl: baseUrl)); return; } diff --git a/lib/feature/presentation/bloc/setup_credential/setup_credential_event.dart b/lib/feature/presentation/bloc/setup_credential/setup_credential_event.dart index 6895b35..07219b2 100644 --- a/lib/feature/presentation/bloc/setup_credential/setup_credential_event.dart +++ b/lib/feature/presentation/bloc/setup_credential/setup_credential_event.dart @@ -2,4 +2,15 @@ part of 'setup_credential_bloc.dart'; abstract class SetupCredentialEvent {} -class PingSetupCredentialEvent extends SetupCredentialEvent {} \ No newline at end of file +class PingSetupCredentialEvent extends SetupCredentialEvent { + final String baseUrl; + + PingSetupCredentialEvent({ + required this.baseUrl, + }); + + @override + String toString() { + return 'PingSetupCredentialEvent{baseUrl: $baseUrl}'; + } +} diff --git a/lib/feature/presentation/bloc/setup_credential/setup_credential_state.dart b/lib/feature/presentation/bloc/setup_credential/setup_credential_state.dart index 2c3d105..f2127f6 100644 --- a/lib/feature/presentation/bloc/setup_credential/setup_credential_state.dart +++ b/lib/feature/presentation/bloc/setup_credential/setup_credential_state.dart @@ -19,4 +19,15 @@ class FailureSetupCredentialState extends SetupCredentialState { } } -class SuccessPingSetupCredentialState extends SetupCredentialState {} +class SuccessPingSetupCredentialState extends SetupCredentialState { + final String baseUrl; + + SuccessPingSetupCredentialState({ + required this.baseUrl, + }); + + @override + String toString() { + return 'SuccessPingSetupCredentialState{baseUrl: $baseUrl}'; + } +} diff --git a/test/feature/data/datasource/general/general_remote_data_source_test.dart b/test/feature/data/datasource/general/general_remote_data_source_test.dart index 02ad540..ad0373b 100644 --- a/test/feature/data/datasource/general/general_remote_data_source_test.dart +++ b/test/feature/data/datasource/general/general_remote_data_source_test.dart @@ -38,6 +38,7 @@ void main() { group('ping', () { const tPathResponse = 'general_response.json'; + const hostname = baseUrl; final tResponse = GeneralResponse.fromJson( json.decode( fixture(tPathResponse), @@ -64,10 +65,10 @@ void main() { setUpMockDioSuccess(); // act - await remoteDataSource.ping(); + await remoteDataSource.ping(hostname); // assert - verify(mockDio.get('$baseUrl/api/ping')); + verify(mockDio.get('$hostname/api/ping')); }, ); @@ -79,7 +80,7 @@ void main() { setUpMockDioSuccess(); // act - final result = await remoteDataSource.ping(); + final result = await remoteDataSource.ping(hostname); // assert expect(result, tResponse); @@ -98,7 +99,7 @@ void main() { when(mockDio.get(any)).thenAnswer((_) async => response); // act - final call = remoteDataSource.ping(); + final call = remoteDataSource.ping(hostname); // assert expect(() => call, throwsA(const TypeMatcher())); diff --git a/test/feature/data/repository/general/general_repository_impl_test.dart b/test/feature/data/repository/general/general_repository_impl_test.dart index b3595b8..82c1d33 100644 --- a/test/feature/data/repository/general/general_repository_impl_test.dart +++ b/test/feature/data/repository/general/general_repository_impl_test.dart @@ -105,6 +105,7 @@ void main() { fixture('general_response.json'), ), ); + const hostname = 'https://example.com'; test( 'pastikan mengembalikan objek model GeneralResponse ketika RemoteDataSource berhasil menerima ' @@ -112,13 +113,13 @@ void main() { () async { // arrange setUpMockNetworkConnected(); - when(mockRemoteDataSource.ping()).thenAnswer((_) async => tResponse); + when(mockRemoteDataSource.ping(any)).thenAnswer((_) async => tResponse); // act - final result = await repository.ping(); + final result = await repository.ping(hostname); // assert - verify(mockRemoteDataSource.ping()); + verify(mockRemoteDataSource.ping(hostname)); expect(result.response, tResponse); }, ); @@ -129,14 +130,14 @@ void main() { () async { // arrange setUpMockNetworkConnected(); - when(mockRemoteDataSource.ping()) + when(mockRemoteDataSource.ping(any)) .thenThrow(DioException(requestOptions: tRequestOptions, message: 'testError')); // act - final result = await repository.ping(); + final result = await repository.ping(hostname); // assert - verify(mockRemoteDataSource.ping()); + verify(mockRemoteDataSource.ping(hostname)); expect(result.failure, ServerFailure('testError')); }, ); @@ -147,7 +148,7 @@ void main() { () async { // arrange setUpMockNetworkConnected(); - when(mockRemoteDataSource.ping()).thenThrow( + when(mockRemoteDataSource.ping(any)).thenThrow( DioException( requestOptions: tRequestOptions, message: 'testError', @@ -163,26 +164,26 @@ void main() { ); // act - final result = await repository.ping(); + final result = await repository.ping(hostname); // assert - verify(mockRemoteDataSource.ping()); + verify(mockRemoteDataSource.ping(hostname)); expect(result.failure, ServerFailure('400 testMessageError')); }, ); testServerFailureString( - () => mockRemoteDataSource.ping(), - () => repository.ping(), - () => mockRemoteDataSource.ping(), + () => mockRemoteDataSource.ping(any), + () => repository.ping(hostname), + () => mockRemoteDataSource.ping(hostname), ); testParsingFailure( - () => mockRemoteDataSource.ping(), - () => repository.ping(), - () => mockRemoteDataSource.ping(), + () => mockRemoteDataSource.ping(any), + () => repository.ping(hostname), + () => mockRemoteDataSource.ping(hostname), ); - testDisconnected(() => repository.ping()); + testDisconnected(() => repository.ping(hostname)); }); } diff --git a/test/feature/domain/usecase/ping/ping_test.dart b/test/feature/domain/usecase/ping/ping_test.dart index 7ce5c03..609071e 100644 --- a/test/feature/domain/usecase/ping/ping_test.dart +++ b/test/feature/domain/usecase/ping/ping_test.dart @@ -1,6 +1,5 @@ import 'dart:convert'; -import 'package:dipantau_desktop_client/core/usecase/usecase.dart'; import 'package:dipantau_desktop_client/feature/data/model/general/general_response.dart'; import 'package:dipantau_desktop_client/feature/domain/usecase/ping/ping.dart'; import 'package:flutter_test/flutter_test.dart'; @@ -18,6 +17,9 @@ void main() { useCase = Ping(repository: mockRepository); }); + const hostname = 'https://example.com'; + final tParams = ParamsPing(baseUrl: hostname); + test( 'pastikan objek repository berhasil menerima respon sukses atau gagal dari endpoint', () async { @@ -27,17 +29,40 @@ void main() { fixture('general_response.json'), ), ); - final tParams = NoParams(); final tResult = (failure: null, response: tResponse); - when(mockRepository.ping()).thenAnswer((_) async => tResult); + when(mockRepository.ping(any)).thenAnswer((_) async => tResult); // act final result = await useCase(tParams); // assert expect(result, tResult); - verify(mockRepository.ping()); + verify(mockRepository.ping(hostname)); verifyNoMoreInteractions(mockRepository); }, ); + + test( + 'pastikan output dari nilai props', + () async { + // assert + expect( + tParams.props, + [ + tParams.baseUrl, + ], + ); + }, + ); + + test( + 'pastikan output dari fungsi toString', + () async { + // assert + expect( + tParams.toString(), + 'ParamsPing{baseUrl: ${tParams.baseUrl}}', + ); + }, + ); } diff --git a/test/feature/presentation/bloc/setup_credential/setup_credential_bloc_test.dart b/test/feature/presentation/bloc/setup_credential/setup_credential_bloc_test.dart index bbca100..7da245a 100644 --- a/test/feature/presentation/bloc/setup_credential/setup_credential_bloc_test.dart +++ b/test/feature/presentation/bloc/setup_credential/setup_credential_bloc_test.dart @@ -2,8 +2,8 @@ import 'dart:convert'; import 'package:bloc_test/bloc_test.dart'; import 'package:dipantau_desktop_client/core/error/failure.dart'; -import 'package:dipantau_desktop_client/core/usecase/usecase.dart'; import 'package:dipantau_desktop_client/feature/data/model/general/general_response.dart'; +import 'package:dipantau_desktop_client/feature/domain/usecase/ping/ping.dart'; import 'package:dipantau_desktop_client/feature/presentation/bloc/setup_credential/setup_credential_bloc.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:mockito/mockito.dart'; @@ -39,8 +39,9 @@ void main() { ); group('ping setup credential', () { - final params = NoParams(); - final event = PingSetupCredentialEvent(); + const baseUrl = 'https://example.com'; + final params = ParamsPing(baseUrl: baseUrl); + final event = PingSetupCredentialEvent(baseUrl: baseUrl); blocTest( 'pastikan emit [LoadingSetupCredentialState, SuccessPingSetupCredentialState] ketika terima event ' diff --git a/test/feature/presentation/bloc/setup_credential/setup_credential_event_test.dart b/test/feature/presentation/bloc/setup_credential/setup_credential_event_test.dart new file mode 100644 index 0000000..576022b --- /dev/null +++ b/test/feature/presentation/bloc/setup_credential/setup_credential_event_test.dart @@ -0,0 +1,19 @@ +import 'package:dipantau_desktop_client/feature/presentation/bloc/setup_credential/setup_credential_bloc.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + group('PingSetupCredentialEvent', () { + final event = PingSetupCredentialEvent(baseUrl: 'https://example.com'); + + test( + 'pastikan output dari fungsi toString', + () async { + // assert + expect( + event.toString(), + 'PingSetupCredentialEvent{baseUrl: ${event.baseUrl}}', + ); + }, + ); + }); +} diff --git a/test/feature/presentation/bloc/setup_credential/setup_credential_state_test.dart b/test/feature/presentation/bloc/setup_credential/setup_credential_state_test.dart index e28645a..f20f2ef 100644 --- a/test/feature/presentation/bloc/setup_credential/setup_credential_state_test.dart +++ b/test/feature/presentation/bloc/setup_credential/setup_credential_state_test.dart @@ -16,4 +16,19 @@ void main() { }, ); }); + + group('SuccessPingSetupCredentialState', () { + final state = SuccessPingSetupCredentialState(baseUrl: 'https://example.com'); + + test( + 'pastikan output dari fungsi toString', + () async { + // assert + expect( + state.toString(), + 'SuccessPingSetupCredentialState{baseUrl: ${state.baseUrl}}', + ); + }, + ); + }); } From f0e2a8fc1c1bd77a8401a29739e75d5295c23c6c Mon Sep 17 00:00:00 2001 From: CoderJava Date: Sun, 17 Nov 2024 18:28:40 +0700 Subject: [PATCH 18/18] Tambahkan pengecekan dengan cara hit ke endpoint ping ketika ubah hostname --- .../setup_credential_page.dart | 120 ++++++++++++------ 1 file changed, 84 insertions(+), 36 deletions(-) diff --git a/lib/feature/presentation/page/setup_credential/setup_credential_page.dart b/lib/feature/presentation/page/setup_credential/setup_credential_page.dart index dd76164..4a7ec98 100644 --- a/lib/feature/presentation/page/setup_credential/setup_credential_page.dart +++ b/lib/feature/presentation/page/setup_credential/setup_credential_page.dart @@ -2,10 +2,14 @@ import 'package:dipantau_desktop_client/core/util/enum/global_variable.dart'; import 'package:dipantau_desktop_client/core/util/helper.dart'; import 'package:dipantau_desktop_client/core/util/shared_preferences_manager.dart'; import 'package:dipantau_desktop_client/core/util/widget_helper.dart'; +import 'package:dipantau_desktop_client/feature/presentation/bloc/setup_credential/setup_credential_bloc.dart'; +import 'package:dipantau_desktop_client/feature/presentation/widget/widget_loading_center_full_screen.dart'; import 'package:dipantau_desktop_client/feature/presentation/widget/widget_primary_button.dart'; import 'package:dipantau_desktop_client/injection_container.dart' as di; +import 'package:dipantau_desktop_client/injection_container.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:go_router/go_router.dart'; class SetupCredentialPage extends StatefulWidget { @@ -32,6 +36,7 @@ class _SetupCredentialPageState extends State { final controllerHostname = TextEditingController(); final widgetHelper = WidgetHelper(); final formState = GlobalKey(); + final setupCredentialBloc = sl(); var isLogin = false; var defaultDomainApi = ''; @@ -52,37 +57,81 @@ class _SetupCredentialPageState extends State { appBar: AppBar( automaticallyImplyLeading: widget.isFromSplashScreen ? false : true, ), - body: Padding( - padding: EdgeInsets.only( - left: helper.getDefaultPaddingLayout, - top: helper.getDefaultPaddingLayoutTop, - right: helper.getDefaultPaddingLayout, - bottom: helper.getDefaultPaddingLayout, + body: BlocProvider( + create: (context) => setupCredentialBloc, + child: BlocListener( + listener: (context, state) { + if (state is FailureSetupCredentialState) { + final errorMessage = 'invalid_hostname'.tr(); + widgetHelper.showDialogMessage( + context, + 'info'.tr(), + errorMessage, + ); + } else if (state is SuccessPingSetupCredentialState) { + final hostname = state.baseUrl; + sharedPreferencesManager.putString(SharedPreferencesManager.keyDomainApi, hostname).then((value) { + helper.setDomainApiToFlavor(hostname); + di.init(); + if (mounted) { + context.go('/'); + } + }); + } + }, + child: Stack( + children: [ + buildWidgetBody(), + buildWidgetLoadingOverlay(), + ], + ), ), - child: SizedBox( - width: double.infinity, - child: Form( - key: formState, - autovalidateMode: AutovalidateMode.onUserInteraction, - child: Column( - mainAxisSize: MainAxisSize.max, - mainAxisAlignment: MainAxisAlignment.center, - children: [ - buildWidgetTitle(), - const SizedBox(height: 8), - Text( - 'subtitle_set_hostname'.tr(), - style: Theme.of(context).textTheme.bodyMedium?.copyWith( - color: Colors.grey, - ), - textAlign: TextAlign.center, - ), - const SizedBox(height: 24), - buildWidgetTextFieldHostname(), - const SizedBox(height: 24), - buildWidgetButtonSave(), - ], - ), + ), + ); + } + + Widget buildWidgetLoadingOverlay() { + return BlocBuilder( + builder: (context, state) { + if (state is LoadingSetupCredentialState) { + return const WidgetLoadingCenterFullScreen(); + } + return Container(); + }, + ); + } + + Widget buildWidgetBody() { + return Padding( + padding: EdgeInsets.only( + left: helper.getDefaultPaddingLayout, + top: helper.getDefaultPaddingLayoutTop, + right: helper.getDefaultPaddingLayout, + bottom: helper.getDefaultPaddingLayout, + ), + child: SizedBox( + width: double.infinity, + child: Form( + key: formState, + autovalidateMode: AutovalidateMode.onUserInteraction, + child: Column( + mainAxisSize: MainAxisSize.max, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + buildWidgetTitle(), + const SizedBox(height: 8), + Text( + 'subtitle_set_hostname'.tr(), + style: Theme.of(context).textTheme.bodyMedium?.copyWith( + color: Colors.grey, + ), + textAlign: TextAlign.center, + ), + const SizedBox(height: 24), + buildWidgetTextFieldHostname(), + const SizedBox(height: 24), + buildWidgetButtonSave(), + ], ), ), ), @@ -149,12 +198,11 @@ class _SetupCredentialPageState extends State { } if (isContinue != null && isContinue) { final hostname = helper.removeTrailingSlash(controllerHostname.text.trim()).trim(); - await sharedPreferencesManager.putString(SharedPreferencesManager.keyDomainApi, hostname); - helper.setDomainApiToFlavor(hostname); - di.init(); - if (mounted) { - context.go('/'); - } + setupCredentialBloc.add( + PingSetupCredentialEvent( + baseUrl: hostname, + ), + ); } } }