Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature - Tambahkan pengecekan apakah hostname-nya valid atau tidak ke endpoint ping #38

Merged
Merged
1 change: 1 addition & 0 deletions devtools_options.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
extensions:
Original file line number Diff line number Diff line change
@@ -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<GeneralResponse> ping(String baseUrl);
}

class GeneralRemoteDataSourceImpl implements GeneralRemoteDataSource {
final Dio dio;

GeneralRemoteDataSourceImpl({
required this.dio,
});

final baseUrl = FlavorConfig.instance.values.baseUrl;

@override
String pathPing = '';

@override
Future<GeneralResponse> ping(String baseUrl) 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));
}
}
}
56 changes: 56 additions & 0 deletions lib/feature/data/repository/general/general_repository_impl.dart
Original file line number Diff line number Diff line change
@@ -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(String baseUrl) async {
Failure? failure;
GeneralResponse? response;
final isConnected = await networkInfo.isConnected;
if (isConnected) {
try {
response = await remoteDataSource.ping(baseUrl);
} 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);
}
}
6 changes: 6 additions & 0 deletions lib/feature/domain/repository/general/general_repository.dart
Original file line number Diff line number Diff line change
@@ -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(String baseUrl);
}
34 changes: 34 additions & 0 deletions lib/feature/domain/usecase/ping/ping.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
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<GeneralResponse, ParamsPing> {
final GeneralRepository repository;

Ping({required this.repository});

@override
Future<({Failure? failure, GeneralResponse? response})> call(ParamsPing params) {
return repository.ping(params.baseUrl);
}
}

class ParamsPing extends Equatable {
final String baseUrl;

ParamsPing({
required this.baseUrl,
});

@override
List<Object?> get props => [
baseUrl,
];

@override
String toString() {
return 'ParamsPing{baseUrl: $baseUrl}';
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import 'dart:async';

import 'package:bloc/bloc.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<SetupCredentialEvent, SetupCredentialState> {
final Helper helper;
final Ping ping;

SetupCredentialBloc({
required this.helper,
required this.ping,
}) : super(InitialSetupCredentialState()) {
on<PingSetupCredentialEvent>(_onPingSetupCredentialEvent);
}

FutureOr<void> _onPingSetupCredentialEvent(
PingSetupCredentialEvent event,
Emitter<SetupCredentialState> emit,
) async {
final baseUrl = event.baseUrl;
emit(LoadingSetupCredentialState());
final result = await ping(
ParamsPing(
baseUrl: baseUrl,
),
);
final response = result.response;
final failure = result.failure;
if (response != null) {
emit(SuccessPingSetupCredentialState(baseUrl: baseUrl));
return;
}

final errorMessage = helper.getErrorMessageFromFailure(failure);
emit(FailureSetupCredentialState(errorMessage: errorMessage));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
part of 'setup_credential_bloc.dart';

abstract class SetupCredentialEvent {}

class PingSetupCredentialEvent extends SetupCredentialEvent {
final String baseUrl;

PingSetupCredentialEvent({
required this.baseUrl,
});

@override
String toString() {
return 'PingSetupCredentialEvent{baseUrl: $baseUrl}';
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
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 {
final String baseUrl;

SuccessPingSetupCredentialState({
required this.baseUrl,
});

@override
String toString() {
return 'SuccessPingSetupCredentialState{baseUrl: $baseUrl}';
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -32,6 +36,7 @@ class _SetupCredentialPageState extends State<SetupCredentialPage> {
final controllerHostname = TextEditingController();
final widgetHelper = WidgetHelper();
final formState = GlobalKey<FormState>();
final setupCredentialBloc = sl<SetupCredentialBloc>();

var isLogin = false;
var defaultDomainApi = '';
Expand All @@ -52,37 +57,81 @@ class _SetupCredentialPageState extends State<SetupCredentialPage> {
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<SetupCredentialBloc, SetupCredentialState>(
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<SetupCredentialBloc, SetupCredentialState>(
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(),
],
),
),
),
Expand Down Expand Up @@ -149,12 +198,11 @@ class _SetupCredentialPageState extends State<SetupCredentialPage> {
}
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,
),
);
}
}
}
Expand Down
Loading
Loading