Skip to content

Commit f4b27a9

Browse files
authored
Merge pull request #38 from CoderJava/feature/tambahkan-pengecekan-apakah-hostname-nya-valid-atau-tidak
Feature - Tambahkan pengecekan apakah hostname-nya valid atau tidak ke endpoint ping
2 parents 23128ff + f0e2a8f commit f4b27a9

File tree

18 files changed

+891
-36
lines changed

18 files changed

+891
-36
lines changed

devtools_options.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
extensions:
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import 'package:dio/dio.dart';
2+
import 'package:dipantau_desktop_client/config/flavor_config.dart';
3+
import 'package:dipantau_desktop_client/feature/data/model/general/general_response.dart';
4+
5+
abstract class GeneralRemoteDataSource {
6+
/// Panggil endpoint [host]/api/ping
7+
///
8+
/// Throws [DioException] untuk semua error kode
9+
late String pathPing;
10+
11+
Future<GeneralResponse> ping(String baseUrl);
12+
}
13+
14+
class GeneralRemoteDataSourceImpl implements GeneralRemoteDataSource {
15+
final Dio dio;
16+
17+
GeneralRemoteDataSourceImpl({
18+
required this.dio,
19+
});
20+
21+
final baseUrl = FlavorConfig.instance.values.baseUrl;
22+
23+
@override
24+
String pathPing = '';
25+
26+
@override
27+
Future<GeneralResponse> ping(String baseUrl) async {
28+
pathPing = '$baseUrl/api/ping';
29+
final response = await dio.get(pathPing);
30+
if (response.statusCode.toString().startsWith('2')) {
31+
return GeneralResponse.fromJson(response.data);
32+
} else {
33+
throw DioException(requestOptions: RequestOptions(path: pathPing));
34+
}
35+
}
36+
}
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import 'package:dio/dio.dart';
2+
import 'package:dipantau_desktop_client/core/error/failure.dart';
3+
import 'package:dipantau_desktop_client/core/network/network_info.dart';
4+
import 'package:dipantau_desktop_client/feature/data/datasource/general/general_remote_data_source.dart';
5+
import 'package:dipantau_desktop_client/feature/data/model/general/general_response.dart';
6+
import 'package:dipantau_desktop_client/feature/domain/repository/general/general_repository.dart';
7+
8+
class GeneralRepositoryImpl implements GeneralRepository {
9+
final GeneralRemoteDataSource remoteDataSource;
10+
final NetworkInfo networkInfo;
11+
12+
GeneralRepositoryImpl({
13+
required this.remoteDataSource,
14+
required this.networkInfo,
15+
});
16+
17+
String getErrorMessageFromEndpoint(dynamic dynamicErrorMessage, String httpErrorMessage, int? statusCode) {
18+
if (dynamicErrorMessage is Map && dynamicErrorMessage.containsKey('message')) {
19+
return '$statusCode ${dynamicErrorMessage['message']}';
20+
} else if (dynamicErrorMessage is String) {
21+
return httpErrorMessage;
22+
} else {
23+
return httpErrorMessage;
24+
}
25+
}
26+
27+
@override
28+
Future<({Failure? failure, GeneralResponse? response})> ping(String baseUrl) async {
29+
Failure? failure;
30+
GeneralResponse? response;
31+
final isConnected = await networkInfo.isConnected;
32+
if (isConnected) {
33+
try {
34+
response = await remoteDataSource.ping(baseUrl);
35+
} on DioException catch (error) {
36+
final message = error.message ?? error.toString();
37+
if (error.response == null) {
38+
failure = ServerFailure(message);
39+
} else {
40+
final errorMessage = getErrorMessageFromEndpoint(
41+
error.response?.data,
42+
message,
43+
error.response?.statusCode,
44+
);
45+
failure = ServerFailure(errorMessage);
46+
}
47+
} on TypeError catch (error) {
48+
final errorMessage = error.toString();
49+
failure = ParsingFailure(errorMessage);
50+
}
51+
} else {
52+
failure = ConnectionFailure();
53+
}
54+
return (failure: failure, response: response);
55+
}
56+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import 'package:dipantau_desktop_client/core/error/failure.dart';
2+
import 'package:dipantau_desktop_client/feature/data/model/general/general_response.dart';
3+
4+
abstract class GeneralRepository {
5+
Future<({Failure? failure, GeneralResponse? response})> ping(String baseUrl);
6+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import 'package:dipantau_desktop_client/core/error/failure.dart';
2+
import 'package:dipantau_desktop_client/core/usecase/usecase.dart';
3+
import 'package:dipantau_desktop_client/feature/data/model/general/general_response.dart';
4+
import 'package:dipantau_desktop_client/feature/domain/repository/general/general_repository.dart';
5+
import 'package:equatable/equatable.dart';
6+
7+
class Ping implements UseCaseRecords<GeneralResponse, ParamsPing> {
8+
final GeneralRepository repository;
9+
10+
Ping({required this.repository});
11+
12+
@override
13+
Future<({Failure? failure, GeneralResponse? response})> call(ParamsPing params) {
14+
return repository.ping(params.baseUrl);
15+
}
16+
}
17+
18+
class ParamsPing extends Equatable {
19+
final String baseUrl;
20+
21+
ParamsPing({
22+
required this.baseUrl,
23+
});
24+
25+
@override
26+
List<Object?> get props => [
27+
baseUrl,
28+
];
29+
30+
@override
31+
String toString() {
32+
return 'ParamsPing{baseUrl: $baseUrl}';
33+
}
34+
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import 'dart:async';
2+
3+
import 'package:bloc/bloc.dart';
4+
import 'package:dipantau_desktop_client/core/util/helper.dart';
5+
import 'package:dipantau_desktop_client/feature/domain/usecase/ping/ping.dart';
6+
7+
part 'setup_credential_event.dart';
8+
9+
part 'setup_credential_state.dart';
10+
11+
class SetupCredentialBloc extends Bloc<SetupCredentialEvent, SetupCredentialState> {
12+
final Helper helper;
13+
final Ping ping;
14+
15+
SetupCredentialBloc({
16+
required this.helper,
17+
required this.ping,
18+
}) : super(InitialSetupCredentialState()) {
19+
on<PingSetupCredentialEvent>(_onPingSetupCredentialEvent);
20+
}
21+
22+
FutureOr<void> _onPingSetupCredentialEvent(
23+
PingSetupCredentialEvent event,
24+
Emitter<SetupCredentialState> emit,
25+
) async {
26+
final baseUrl = event.baseUrl;
27+
emit(LoadingSetupCredentialState());
28+
final result = await ping(
29+
ParamsPing(
30+
baseUrl: baseUrl,
31+
),
32+
);
33+
final response = result.response;
34+
final failure = result.failure;
35+
if (response != null) {
36+
emit(SuccessPingSetupCredentialState(baseUrl: baseUrl));
37+
return;
38+
}
39+
40+
final errorMessage = helper.getErrorMessageFromFailure(failure);
41+
emit(FailureSetupCredentialState(errorMessage: errorMessage));
42+
}
43+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
part of 'setup_credential_bloc.dart';
2+
3+
abstract class SetupCredentialEvent {}
4+
5+
class PingSetupCredentialEvent extends SetupCredentialEvent {
6+
final String baseUrl;
7+
8+
PingSetupCredentialEvent({
9+
required this.baseUrl,
10+
});
11+
12+
@override
13+
String toString() {
14+
return 'PingSetupCredentialEvent{baseUrl: $baseUrl}';
15+
}
16+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
part of 'setup_credential_bloc.dart';
2+
3+
abstract class SetupCredentialState {}
4+
5+
class InitialSetupCredentialState extends SetupCredentialState {}
6+
7+
class LoadingSetupCredentialState extends SetupCredentialState {}
8+
9+
class FailureSetupCredentialState extends SetupCredentialState {
10+
final String errorMessage;
11+
12+
FailureSetupCredentialState({
13+
required this.errorMessage,
14+
});
15+
16+
@override
17+
String toString() {
18+
return 'FailureSetupCredentialState{errorMessage: $errorMessage}';
19+
}
20+
}
21+
22+
class SuccessPingSetupCredentialState extends SetupCredentialState {
23+
final String baseUrl;
24+
25+
SuccessPingSetupCredentialState({
26+
required this.baseUrl,
27+
});
28+
29+
@override
30+
String toString() {
31+
return 'SuccessPingSetupCredentialState{baseUrl: $baseUrl}';
32+
}
33+
}

lib/feature/presentation/page/setup_credential/setup_credential_page.dart

Lines changed: 84 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,14 @@ import 'package:dipantau_desktop_client/core/util/enum/global_variable.dart';
22
import 'package:dipantau_desktop_client/core/util/helper.dart';
33
import 'package:dipantau_desktop_client/core/util/shared_preferences_manager.dart';
44
import 'package:dipantau_desktop_client/core/util/widget_helper.dart';
5+
import 'package:dipantau_desktop_client/feature/presentation/bloc/setup_credential/setup_credential_bloc.dart';
6+
import 'package:dipantau_desktop_client/feature/presentation/widget/widget_loading_center_full_screen.dart';
57
import 'package:dipantau_desktop_client/feature/presentation/widget/widget_primary_button.dart';
68
import 'package:dipantau_desktop_client/injection_container.dart' as di;
9+
import 'package:dipantau_desktop_client/injection_container.dart';
710
import 'package:easy_localization/easy_localization.dart';
811
import 'package:flutter/material.dart';
12+
import 'package:flutter_bloc/flutter_bloc.dart';
913
import 'package:go_router/go_router.dart';
1014

1115
class SetupCredentialPage extends StatefulWidget {
@@ -32,6 +36,7 @@ class _SetupCredentialPageState extends State<SetupCredentialPage> {
3236
final controllerHostname = TextEditingController();
3337
final widgetHelper = WidgetHelper();
3438
final formState = GlobalKey<FormState>();
39+
final setupCredentialBloc = sl<SetupCredentialBloc>();
3540

3641
var isLogin = false;
3742
var defaultDomainApi = '';
@@ -52,37 +57,81 @@ class _SetupCredentialPageState extends State<SetupCredentialPage> {
5257
appBar: AppBar(
5358
automaticallyImplyLeading: widget.isFromSplashScreen ? false : true,
5459
),
55-
body: Padding(
56-
padding: EdgeInsets.only(
57-
left: helper.getDefaultPaddingLayout,
58-
top: helper.getDefaultPaddingLayoutTop,
59-
right: helper.getDefaultPaddingLayout,
60-
bottom: helper.getDefaultPaddingLayout,
60+
body: BlocProvider(
61+
create: (context) => setupCredentialBloc,
62+
child: BlocListener<SetupCredentialBloc, SetupCredentialState>(
63+
listener: (context, state) {
64+
if (state is FailureSetupCredentialState) {
65+
final errorMessage = 'invalid_hostname'.tr();
66+
widgetHelper.showDialogMessage(
67+
context,
68+
'info'.tr(),
69+
errorMessage,
70+
);
71+
} else if (state is SuccessPingSetupCredentialState) {
72+
final hostname = state.baseUrl;
73+
sharedPreferencesManager.putString(SharedPreferencesManager.keyDomainApi, hostname).then((value) {
74+
helper.setDomainApiToFlavor(hostname);
75+
di.init();
76+
if (mounted) {
77+
context.go('/');
78+
}
79+
});
80+
}
81+
},
82+
child: Stack(
83+
children: [
84+
buildWidgetBody(),
85+
buildWidgetLoadingOverlay(),
86+
],
87+
),
6188
),
62-
child: SizedBox(
63-
width: double.infinity,
64-
child: Form(
65-
key: formState,
66-
autovalidateMode: AutovalidateMode.onUserInteraction,
67-
child: Column(
68-
mainAxisSize: MainAxisSize.max,
69-
mainAxisAlignment: MainAxisAlignment.center,
70-
children: [
71-
buildWidgetTitle(),
72-
const SizedBox(height: 8),
73-
Text(
74-
'subtitle_set_hostname'.tr(),
75-
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
76-
color: Colors.grey,
77-
),
78-
textAlign: TextAlign.center,
79-
),
80-
const SizedBox(height: 24),
81-
buildWidgetTextFieldHostname(),
82-
const SizedBox(height: 24),
83-
buildWidgetButtonSave(),
84-
],
85-
),
89+
),
90+
);
91+
}
92+
93+
Widget buildWidgetLoadingOverlay() {
94+
return BlocBuilder<SetupCredentialBloc, SetupCredentialState>(
95+
builder: (context, state) {
96+
if (state is LoadingSetupCredentialState) {
97+
return const WidgetLoadingCenterFullScreen();
98+
}
99+
return Container();
100+
},
101+
);
102+
}
103+
104+
Widget buildWidgetBody() {
105+
return Padding(
106+
padding: EdgeInsets.only(
107+
left: helper.getDefaultPaddingLayout,
108+
top: helper.getDefaultPaddingLayoutTop,
109+
right: helper.getDefaultPaddingLayout,
110+
bottom: helper.getDefaultPaddingLayout,
111+
),
112+
child: SizedBox(
113+
width: double.infinity,
114+
child: Form(
115+
key: formState,
116+
autovalidateMode: AutovalidateMode.onUserInteraction,
117+
child: Column(
118+
mainAxisSize: MainAxisSize.max,
119+
mainAxisAlignment: MainAxisAlignment.center,
120+
children: [
121+
buildWidgetTitle(),
122+
const SizedBox(height: 8),
123+
Text(
124+
'subtitle_set_hostname'.tr(),
125+
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
126+
color: Colors.grey,
127+
),
128+
textAlign: TextAlign.center,
129+
),
130+
const SizedBox(height: 24),
131+
buildWidgetTextFieldHostname(),
132+
const SizedBox(height: 24),
133+
buildWidgetButtonSave(),
134+
],
86135
),
87136
),
88137
),
@@ -149,12 +198,11 @@ class _SetupCredentialPageState extends State<SetupCredentialPage> {
149198
}
150199
if (isContinue != null && isContinue) {
151200
final hostname = helper.removeTrailingSlash(controllerHostname.text.trim()).trim();
152-
await sharedPreferencesManager.putString(SharedPreferencesManager.keyDomainApi, hostname);
153-
helper.setDomainApiToFlavor(hostname);
154-
di.init();
155-
if (mounted) {
156-
context.go('/');
157-
}
201+
setupCredentialBloc.add(
202+
PingSetupCredentialEvent(
203+
baseUrl: hostname,
204+
),
205+
);
158206
}
159207
}
160208
}

0 commit comments

Comments
 (0)