Skip to content

Commit

Permalink
Merge pull request #38 from CoderJava/feature/tambahkan-pengecekan-ap…
Browse files Browse the repository at this point in the history
…akah-hostname-nya-valid-atau-tidak

Feature - Tambahkan pengecekan apakah hostname-nya valid atau tidak ke endpoint ping
  • Loading branch information
CoderJava authored Nov 17, 2024
2 parents 23128ff + f0e2a8f commit f4b27a9
Show file tree
Hide file tree
Showing 18 changed files with 891 additions and 36 deletions.
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

0 comments on commit f4b27a9

Please sign in to comment.