Skip to content

Commit

Permalink
Merge pull request #61 from nimblehq/release/0.3.0
Browse files Browse the repository at this point in the history
[Release] 0.3.0
  • Loading branch information
nkhanh44 authored Aug 18, 2023
2 parents 3382c2e + dd86778 commit d5e3bdf
Show file tree
Hide file tree
Showing 51 changed files with 1,439 additions and 72 deletions.
Binary file added assets/images/2.0x/next.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/images/3.0x/next.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/images/dummy_avatar.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/images/dummy_background.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/images/next.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
6 changes: 6 additions & 0 deletions ios/Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ PODS:
- Flutter (1.0.0)
- flutter_config (0.0.1):
- Flutter
- flutter_secure_storage (6.0.0):
- Flutter
- integration_test (0.0.1):
- Flutter
- package_info_plus (0.4.5):
Expand All @@ -12,6 +14,7 @@ PODS:
DEPENDENCIES:
- Flutter (from `Flutter`)
- flutter_config (from `.symlinks/plugins/flutter_config/ios`)
- flutter_secure_storage (from `.symlinks/plugins/flutter_secure_storage/ios`)
- integration_test (from `.symlinks/plugins/integration_test/ios`)
- package_info_plus (from `.symlinks/plugins/package_info_plus/ios`)
- permission_handler_apple (from `.symlinks/plugins/permission_handler_apple/ios`)
Expand All @@ -21,6 +24,8 @@ EXTERNAL SOURCES:
:path: Flutter
flutter_config:
:path: ".symlinks/plugins/flutter_config/ios"
flutter_secure_storage:
:path: ".symlinks/plugins/flutter_secure_storage/ios"
integration_test:
:path: ".symlinks/plugins/integration_test/ios"
package_info_plus:
Expand All @@ -31,6 +36,7 @@ EXTERNAL SOURCES:
SPEC CHECKSUMS:
Flutter: f04841e97a9d0b0a8025694d0796dd46242b2854
flutter_config: 2226c1df19c78fe34a05eb7f1363445f18e76fc1
flutter_secure_storage: 23fc622d89d073675f2eaa109381aefbcf5a49be
integration_test: 13825b8a9334a850581300559b8839134b124670
package_info_plus: fd030dabf36271f146f1f3beacd48f564b0f17f7
permission_handler_apple: e76247795d700c14ea09e3a2d8855d41ee80a2e6
Expand Down
5 changes: 4 additions & 1 deletion lib/api/response_decoder.dart
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import 'package:japx/japx.dart';

class ResponseDecoder {
static Map<String, dynamic> decode(Map<String, dynamic> json) {
static Map<String, dynamic> decodeData(Map<String, dynamic> json) {
return Japx.decode(json)['data'];
}

static Map<String, dynamic> decode(Map<String, dynamic> json) =>
Japx.decode(json);
}
16 changes: 16 additions & 0 deletions lib/api/survey_api_service.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import 'package:dio/dio.dart';
import 'package:retrofit/http.dart';
import 'package:survey_flutter/model/response/surveys_container_response.dart';

part 'survey_api_service.g.dart';

@RestApi()
abstract class SurveyApiService {
factory SurveyApiService(Dio dio, {String baseUrl}) = _SurveyApiService;

@GET('/surveys')
Future<SurveysContainerResponse> getSurveys(
@Query('page[number]') int pageNumber,
@Query('page[size]') int pageSize,
);
}
14 changes: 8 additions & 6 deletions lib/di/provider/dio_provider.dart
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import 'package:dio/dio.dart';
import 'package:flutter/foundation.dart';
import 'package:survey_flutter/di/interceptor/app_interceptor.dart';
import 'package:survey_flutter/env.dart';

const String headerContentType = 'Content-Type';
const String defaultContentType = 'application/json; charset=utf-8';
const String _headerContentType = 'Content-Type';
const String _defaultContentType = 'application/json; charset=utf-8';

class DioProvider {
Dio? _dio;
Expand Down Expand Up @@ -31,9 +32,10 @@ class DioProvider {
}

return dio
..options.connectTimeout = const Duration(seconds: 3000)
..options.receiveTimeout = const Duration(seconds: 5000)
..options.headers = {headerContentType: defaultContentType}
..interceptors.addAll(interceptors);
..options.connectTimeout = const Duration(seconds: 3)
..options.receiveTimeout = const Duration(seconds: 5)
..options.headers = {_headerContentType: _defaultContentType}
..interceptors.addAll(interceptors)
..options.baseUrl = Env.restApiEndpoint;
}
}
18 changes: 18 additions & 0 deletions lib/di/provider/flutter_secure_storage.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import 'package:flutter_secure_storage/flutter_secure_storage.dart';

class FlutterSecureStorageProvider {
FlutterSecureStorage? _storage;

FlutterSecureStorage getStorage() {
_storage ??= _createStorage();
return _storage!;
}

FlutterSecureStorage _createStorage() {
return const FlutterSecureStorage(
aOptions: AndroidOptions(
encryptedSharedPreferences: true,
),
);
}
}
8 changes: 7 additions & 1 deletion lib/l10n/app_en.arb
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
{
"okText": "OK",
"emailInputHint": "Email",
"passwordInputHint": "Password",
"loginButton": "Log in",
"invalidEmailError": "Please enter the valid email format.",
"invalidPasswordError": "Password must be at least 8 characters long."
"invalidPasswordError": "Password must be at least 8 characters long.",
"unauthorizedError": "Your email or password is incorrect.\nPlease try again.",
"noInternetConnectionError": "You seem to be offline.\nPlease try again!",
"genericError": "Something went wrong.\nPlease try again!",
"loginFailAlertTitle": "Unable to log in",
"today": "Today"
}
5 changes: 5 additions & 0 deletions lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import 'package:flutter_config/flutter_config.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:go_router/go_router.dart';
import 'package:survey_flutter/screens/home/home_screen.dart';
import 'package:survey_flutter/screens/login/login_screen.dart';
import 'package:survey_flutter/screens/splash/splash_screen.dart';
import 'package:survey_flutter/theme/app_theme.dart';
Expand Down Expand Up @@ -34,6 +35,10 @@ class App extends StatelessWidget {
child: LoginScreen(),
),
),
GoRoute(
path: routePathHomeScreen,
builder: (_, __) => const HomeScreen(),
),
],
);

Expand Down
35 changes: 35 additions & 0 deletions lib/model/api_token.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import 'package:json_annotation/json_annotation.dart';
import 'package:survey_flutter/storage/secure_storage.dart';

part 'api_token.g.dart';

@JsonSerializable()
class ApiToken extends SecureStorageModel {
@JsonKey(name: 'access_token')
final String accessToken;
@JsonKey(name: 'refresh_token')
final String refreshToken;
@JsonKey(name: 'token_type')
final String tokenType;

ApiToken({
required this.accessToken,
required this.refreshToken,
required this.tokenType,
});

factory ApiToken.fromJson(Map<String, dynamic> json) =>
_$ApiTokenFromJson(json);

Map<String, dynamic> toJson() => _$ApiTokenToJson(this);

@override
bool operator ==(Object other) =>
other is ApiToken &&
accessToken == other.accessToken &&
refreshToken == other.refreshToken &&
tokenType == other.tokenType;

@override
int get hashCode => (accessToken + refreshToken + tokenType).hashCode;
}
31 changes: 31 additions & 0 deletions lib/model/meta_model.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import 'package:equatable/equatable.dart';

class MetaModel extends Equatable {
final int page;
final int pages;
final int pageSize;
final int records;

const MetaModel({
required this.page,
required this.pages,
required this.pageSize,
required this.records,
});

const MetaModel.dummy()
: this(
page: 0,
pages: 0,
pageSize: 0,
records: 0,
);

@override
List<Object?> get props => [
page,
pages,
pageSize,
records,
];
}
9 changes: 8 additions & 1 deletion lib/model/response/login_response.dart
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import 'package:json_annotation/json_annotation.dart';
import 'package:survey_flutter/api/response_decoder.dart';
import 'package:survey_flutter/model/api_token.dart';
import 'package:survey_flutter/model/login_model.dart';

part 'login_response.g.dart';
Expand All @@ -23,7 +24,7 @@ class LoginResponse {
});

factory LoginResponse.fromJson(Map<String, dynamic> json) =>
_$LoginResponseFromJson(ResponseDecoder.decode(json));
_$LoginResponseFromJson(ResponseDecoder.decodeData(json));

LoginModel toLoginModel() => LoginModel(
id: id,
Expand All @@ -32,6 +33,12 @@ class LoginResponse {
refreshToken: refreshToken,
);

ApiToken toApiToken() => ApiToken(
accessToken: accessToken,
refreshToken: refreshToken,
tokenType: tokenType,
);

static LoginResponse dummy() {
return LoginResponse(
id: "",
Expand Down
39 changes: 39 additions & 0 deletions lib/model/response/meta_response.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:survey_flutter/api/response_decoder.dart';
import 'package:survey_flutter/model/meta_model.dart';

part 'meta_response.g.dart';

@JsonSerializable()
class MetaResponse {
final int? page;
final int? pages;
final int? pageSize;
final int? records;

MetaResponse({
required this.page,
required this.pages,
required this.pageSize,
required this.records,
});

factory MetaResponse.fromJson(Map<String, dynamic> json) =>
_$MetaResponseFromJson(ResponseDecoder.decode(json));

static MetaResponse dummy() {
return MetaResponse(
page: 0,
pages: 0,
pageSize: 0,
records: 0,
);
}

MetaModel toMetaModel() => MetaModel(
page: page ?? 0,
pages: pages ?? 0,
pageSize: pageSize ?? 0,
records: records ?? 0,
);
}
31 changes: 31 additions & 0 deletions lib/model/response/survey_response.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:json_annotation/json_annotation.dart';
import 'package:survey_flutter/api/response_decoder.dart';
import 'package:survey_flutter/model/survey_model.dart';

part 'survey_response.g.dart';

@JsonSerializable()
class SurveyResponse {
final String? id;
final String? title;
final String? description;
final String? coverImageUrl;

SurveyResponse({
required this.id,
required this.title,
required this.description,
required this.coverImageUrl,
});

factory SurveyResponse.fromJson(Map<String, dynamic> json) =>
_$SurveyResponseFromJson(ResponseDecoder.decodeData(json));

SurveyModel toSurveyModel() => SurveyModel(
id: id ?? '',
title: title ?? '',
description: description ?? '',
coverImageUrl: coverImageUrl ?? '',
);
}
29 changes: 29 additions & 0 deletions lib/model/response/surveys_container_response.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:survey_flutter/api/response_decoder.dart';
import 'package:survey_flutter/model/meta_model.dart';
import 'package:survey_flutter/model/response/meta_response.dart';
import 'package:survey_flutter/model/response/survey_response.dart';
import 'package:survey_flutter/model/surveys_container_model.dart';

part 'surveys_container_response.g.dart';

@JsonSerializable()
class SurveysContainerResponse {
final List<SurveyResponse>? data;
final MetaResponse? meta;

const SurveysContainerResponse({
required this.data,
required this.meta,
});

factory SurveysContainerResponse.fromJson(Map<String, dynamic> json) {
return _$SurveysContainerResponseFromJson(ResponseDecoder.decode(json));
}

SurveysContainerModel toSurveysContainerModel() => SurveysContainerModel(
surveys:
data?.map((item) => item.toSurveyModel()).toList() ?? List.empty(),
meta: meta?.toMetaModel() ?? const MetaModel.dummy(),
);
}
23 changes: 23 additions & 0 deletions lib/model/survey_model.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import 'package:equatable/equatable.dart';

class SurveyModel extends Equatable {
final String id;
final String title;
final String description;
final String coverImageUrl;

const SurveyModel({
required this.id,
required this.title,
required this.description,
required this.coverImageUrl,
});

@override
List<Object?> get props => [
id,
title,
description,
coverImageUrl,
];
}
25 changes: 25 additions & 0 deletions lib/model/surveys_container_model.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import 'package:equatable/equatable.dart';
import 'package:survey_flutter/model/meta_model.dart';
import 'package:survey_flutter/model/survey_model.dart';

class SurveysContainerModel extends Equatable {
final List<SurveyModel> surveys;
final MetaModel meta;

const SurveysContainerModel({
required this.surveys,
required this.meta,
});

SurveysContainerModel.dummy()
: this(
surveys: List.empty(),
meta: const MetaModel.dummy(),
);

@override
List<Object?> get props => [
surveys,
meta,
];
}
Loading

0 comments on commit d5e3bdf

Please sign in to comment.