From 2540815267cc56d3c0684825ae02778279751439 Mon Sep 17 00:00:00 2001 From: SiongSng Date: Sat, 2 Jul 2022 19:04:12 +0800 Subject: [PATCH 1/4] feat: add `http` dependency --- pubspec.lock | 7 +++++++ pubspec.yaml | 1 + 2 files changed, 8 insertions(+) diff --git a/pubspec.lock b/pubspec.lock index d910ab3..b22eeb9 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -268,6 +268,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.1.3" + http: + dependency: "direct main" + description: + name: http + url: "https://pub.dartlang.org" + source: hosted + version: "0.13.4" http_multi_server: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 74d692b..3eb723e 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -20,6 +20,7 @@ dependencies: font_awesome_flutter: ^10.1.0 hive: ^2.2.1 path_provider: ^2.0.10 + http: ^0.13.4 dev_dependencies: flutter_test: From 71cfa9005a6adc1e5ca3b670c0f0aa796ce08f24 Mon Sep 17 00:00:00 2001 From: SiongSng Date: Wed, 6 Jul 2022 10:46:19 +0800 Subject: [PATCH 2/4] feat: http cleint --- analysis_options.yaml | 3 + lib/src/api/api.dart | 3 + lib/src/api/api_http_client.dart | 108 +++++++++++++++++++++++++++++ lib/src/api/api_response.dart | 55 +++++++++++++++ lib/src/api/code.dart | 35 ++++++++++ lib/src/config/config_storage.dart | 3 + 6 files changed, 207 insertions(+) create mode 100644 lib/src/api/api.dart create mode 100644 lib/src/api/api_http_client.dart create mode 100644 lib/src/api/api_response.dart create mode 100644 lib/src/api/code.dart diff --git a/analysis_options.yaml b/analysis_options.yaml index 61b6c4d..ce8daea 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -25,5 +25,8 @@ linter: # avoid_print: false # Uncomment to disable the `avoid_print` rule # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule +analyzer: + errors: + todo: ignore # Additional information about this file can be found at # https://dart.dev/guides/language/analysis-options diff --git a/lib/src/api/api.dart b/lib/src/api/api.dart new file mode 100644 index 0000000..7d96744 --- /dev/null +++ b/lib/src/api/api.dart @@ -0,0 +1,3 @@ +export 'api_http_client.dart'; +export 'api_response.dart'; +export 'code.dart'; diff --git a/lib/src/api/api_http_client.dart b/lib/src/api/api_http_client.dart new file mode 100644 index 0000000..8536b8d --- /dev/null +++ b/lib/src/api/api_http_client.dart @@ -0,0 +1,108 @@ +import 'package:http/http.dart' as http; +import 'package:lipoic/src/api/api_response.dart'; +import 'package:lipoic/src/config/config.dart'; + +class HttpClient { + final String baseUrl; + final http.Client _client; + + HttpClient({required this.baseUrl, http.Client? client}) + : _client = client ?? http.Client(); + + /// Development environment client + factory HttpClient.dev() => + HttpClient(baseUrl: 'https://lipoic-test-server.herokuapp.com'); + + // TODO: change the url to api.lipoic.org + /// Production environment client + factory HttpClient.prod() => + HttpClient(baseUrl: 'https://lipoic-server.herokuapp.com'); + + Future> _request( + {required String method, + required String path, + Map? query, + Object? body, + String? token, + Map headers = const {}}) async { + token ??= appConfig.token; + final Uri uri = + Uri.parse(baseUrl + (path.startsWith('/') ? path : '/$path')).replace( + query: query?.entries + .where((e) => e.value != null) + .map((e) => '${e.key}=${e.value}') + .join('&')); + + final http.Response response; + headers.addAll({ + 'Content-Type': 'application/json', + 'Accept': 'application/json', + if (token != null) 'Authorization': 'Bearer $token', + 'User-Agent': 'RPMTW-Application', + }); + + if (method == 'GET') { + response = await _client.get(uri, headers: headers); + } else if (method == 'POST') { + response = await _client.post(uri, headers: headers, body: body); + } else if (method == 'PATCH') { + response = await _client.patch(uri, headers: headers, body: body); + } else if (method == 'DELETE') { + response = await _client.delete(uri, headers: headers, body: body); + } else { + throw Exception('Invalid method: $method'); + } + + return Response.fromJson(response.body); + } + + Future> get(String path, + {Map? query, + String? token, + Map headers = const {}}) => + _request( + method: 'GET', + path: path, + query: query, + token: token, + headers: headers); + + Future> post(String path, + {Map? query, + Object? body, + String? token, + Map headers = const {}}) => + _request( + method: 'POST', + path: path, + query: query, + body: body, + token: token, + headers: headers); + + Future> patch(String path, + {Map? query, + Object? body, + String? token, + Map headers = const {}}) => + _request( + method: 'PATCH', + path: path, + query: query, + body: body, + token: token, + headers: headers); + + Future> delete(String path, + {Map? query, + Object? body, + String? token, + Map headers = const {}}) => + _request( + method: 'DELETE', + path: path, + query: query, + body: body, + token: token, + headers: headers); +} diff --git a/lib/src/api/api_response.dart b/lib/src/api/api_response.dart new file mode 100644 index 0000000..4008db5 --- /dev/null +++ b/lib/src/api/api_response.dart @@ -0,0 +1,55 @@ +import 'dart:convert'; + +import 'package:lipoic/src/api/code.dart'; + +class Response { + final Code code; + final String message; + final T? data; + + const Response({ + required this.code, + required this.message, + this.data, + }); + + Map toMap() { + return { + 'code': code.value, + 'message': message, + 'data': data, + }; + } + + factory Response.fromMap(Map map) { + return Response( + code: Code.values.firstWhere((value) => value == map['code']), + message: map['message'], + data: map['data'] != null + ? T.noSuchMethod( + Invocation.method(const Symbol('fromMap'), map['data'])) + : null, + ); + } + + String toJson() => json.encode(toMap()); + + factory Response.fromJson(String source) => + Response.fromMap(json.decode(source)); + + @override + String toString() => 'Response(code: $code, message: $message, data: $data)'; + + @override + bool operator ==(Object other) { + if (identical(this, other)) return true; + + return other is Response && + other.code == code && + other.message == message && + other.data == data; + } + + @override + int get hashCode => code.hashCode ^ message.hashCode ^ data.hashCode; +} diff --git a/lib/src/api/code.dart b/lib/src/api/code.dart new file mode 100644 index 0000000..4967754 --- /dev/null +++ b/lib/src/api/code.dart @@ -0,0 +1,35 @@ +// ignore_for_file: constant_identifier_names + +/// Lipoic API response code. +/// Also see https://api-docs.lipoic.org/router/data/code/struct.Code.html +enum Code { + /// OK. + OK(200), + + /// Resource not found. + NotFound(404), + + /// OAuth auth code error. + OAuthCodeError(1), + + /// OAuth get user info error. + OAuthGetUserInfoError(2), + + /// User not found error. + LoginUserNotFoundError(3), + + /// Input password error. + LoginPasswordError(4), + + /// This email is already registered. + SignUpEmailAlreadyRegistered(5), + + /// This code is invalid. + VerifyEmailError(6), + + /// This token is invalid. + AuthError(7); + + const Code(this.value); + final int value; +} diff --git a/lib/src/config/config_storage.dart b/lib/src/config/config_storage.dart index 5142072..5c17b60 100644 --- a/lib/src/config/config_storage.dart +++ b/lib/src/config/config_storage.dart @@ -7,4 +7,7 @@ class ConfigStorage { bool get init => ConfigHelper.get('init') ?? false; set init(bool value) => ConfigHelper.set('init', value); + + String? get token => ConfigHelper.get('token'); + set token(String? value) => ConfigHelper.set('token', value); } From e60b552500fdf1c927cd1375100717348f9b3f59 Mon Sep 17 00:00:00 2001 From: SiongSng Date: Wed, 6 Jul 2022 11:06:07 +0800 Subject: [PATCH 3/4] test: config helper --- .gitignore | 2 ++ test/config/config_helper_test.dart | 17 +++++++++++++++++ test/widget_test.dart | 1 - 3 files changed, 19 insertions(+), 1 deletion(-) create mode 100644 test/config/config_helper_test.dart delete mode 100644 test/widget_test.dart diff --git a/.gitignore b/.gitignore index a8e938c..9be3891 100644 --- a/.gitignore +++ b/.gitignore @@ -45,3 +45,5 @@ app.*.map.json /android/app/debug /android/app/profile /android/app/release + +coverage/lcov.info diff --git a/test/config/config_helper_test.dart b/test/config/config_helper_test.dart new file mode 100644 index 0000000..d5de567 --- /dev/null +++ b/test/config/config_helper_test.dart @@ -0,0 +1,17 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:lipoic/src/config/config.dart'; + +void main() { + setUpAll(() { + return ConfigHelper.init(); + }); + + test('set value', () { + const key = 'key'; + const value = 'value'; + + ConfigHelper.set(key, value); + + expect(ConfigHelper.get(key), value); + }); +} diff --git a/test/widget_test.dart b/test/widget_test.dart deleted file mode 100644 index ab73b3a..0000000 --- a/test/widget_test.dart +++ /dev/null @@ -1 +0,0 @@ -void main() {} From 9c6f72922df0642d9e583c656f40bb656b3c38fd Mon Sep 17 00:00:00 2001 From: SiongSng Date: Wed, 6 Jul 2022 11:09:02 +0800 Subject: [PATCH 4/4] test: coverage --- .github/workflows/analyze.yml | 18 +++++++++++++++++- .github/workflows/build.yml | 10 +++++----- .gitpod.Dockerfile | 2 +- pubspec.lock | 2 +- 4 files changed, 24 insertions(+), 8 deletions(-) diff --git a/.github/workflows/analyze.yml b/.github/workflows/analyze.yml index ef28f43..8be3cdb 100644 --- a/.github/workflows/analyze.yml +++ b/.github/workflows/analyze.yml @@ -9,7 +9,7 @@ jobs: - uses: actions/checkout@v2 - uses: subosito/flutter-action@main with: - flutter-version: 3.0.1 + flutter-version: 3.0.4 cache: true - run: flutter pub get - name: Analyze the code @@ -19,3 +19,19 @@ jobs: fatal-warnings: true annotate: true annotate-only: false + Coverage: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: subosito/flutter-action@main + with: + flutter-version: 3.0.4 + cache: true + - name: Run tests and generate coverage + run: | + flutter pub get + flutter test --coverage + - name: Upload to Codecov + uses: codecov/codecov-action@v2 + with: + file: ./coverage/lcov.info diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 967238a..1c6efc6 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -13,7 +13,7 @@ jobs: - uses: actions/checkout@v2 - uses: subosito/flutter-action@main with: - flutter-version: 3.0.1 + flutter-version: 3.0.4 cache: true - name: Build run: flutter build appbundle @@ -30,7 +30,7 @@ jobs: - uses: actions/checkout@v2 - uses: subosito/flutter-action@main with: - flutter-version: 3.0.1 + flutter-version: 3.0.4 cache: true - name: Build run: flutter build apk @@ -47,10 +47,10 @@ jobs: - uses: actions/checkout@v2 - uses: subosito/flutter-action@main with: - flutter-version: 3.0.1 + flutter-version: 3.0.4 cache: true - name: Build - run: | + run: | flutter build ios --no-codesign - name: Upload File uses: actions/upload-artifact@v2 @@ -78,7 +78,7 @@ jobs: - uses: actions/checkout@v2 - uses: subosito/flutter-action@main with: - flutter-version: 3.0.1 + flutter-version: 3.0.4 cache: true - name: Install dependencies if: ${{ runner.os == 'Linux' }} diff --git a/.gitpod.Dockerfile b/.gitpod.Dockerfile index d3d39d0..f2a00d5 100644 --- a/.gitpod.Dockerfile +++ b/.gitpod.Dockerfile @@ -2,7 +2,7 @@ FROM gitpod/workspace-full-vnc:2022-05-17-12-26-08 SHELL ["/bin/bash", "-c"] ENV ANDROID_HOME=$HOME/androidsdk \ - FLUTTER_VERSION=3.0.1-stable \ + FLUTTER_VERSION=3.0.4-stable \ QTWEBENGINE_DISABLE_SANDBOX=1 ENV PATH="$HOME/flutter/bin:$ANDROID_HOME/emulator:$ANDROID_HOME/tools:$ANDROID_HOME/cmdline-tools/latest/bin:$ANDROID_HOME/platform-tools:$PATH" diff --git a/pubspec.lock b/pubspec.lock index b22eeb9..4911048 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -147,7 +147,7 @@ packages: name: convert url: "https://pub.dartlang.org" source: hosted - version: "3.0.1" + version: "3.0.4" crypto: dependency: transitive description: