diff --git a/.github/workflows/PR-open-test-build.yaml b/.github/workflows/PR-open-test-build.yaml index fe37ffa..467c87f 100644 --- a/.github/workflows/PR-open-test-build.yaml +++ b/.github/workflows/PR-open-test-build.yaml @@ -30,7 +30,7 @@ jobs: with: path: "./coverage/lcov.info" min_coverage: 4.5 - exclude: "**/*.freezed.dart **/*.g.dart **/*.gr.dart **/constants.dart **/custom_theme.dart **/assets_helper.dart" + exclude: "**/*.freezed.dart **/*.g.dart **/*.gr.dart **/*.mocks.dart **/constants.dart **/custom_theme.dart **/assets_helper.dart" - name: Upload coverage to Codecov uses: codecov/codecov-action@v2.0.2 with: diff --git a/.gitignore b/.gitignore index 929d44e..10cb6d6 100644 --- a/.gitignore +++ b/.gitignore @@ -49,6 +49,7 @@ app.*.map.json *.g.dart *.freezed.dart *.gr.dart +*.mocks.dart #keystore *.jks diff --git a/Prototype/Theater class code.txt b/Prototype/Theater class code.txt index 1607111..6c58088 100644 --- a/Prototype/Theater class code.txt +++ b/Prototype/Theater class code.txt @@ -30,7 +30,7 @@ class Hall { required this.name, }); - factory Hall.fromJson(Map json) { + factory Hall.fromJson(JSON json) { return Hall( numRows: json["num_rows"] as int, numCols: json["num_cols"] as int, diff --git a/analysis_options.yaml b/analysis_options.yaml index 37c53c1..96747c5 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -5,6 +5,7 @@ analyzer: - "**/*.freezed.dart" - "**/*.g.dart" - "**/*.gr.dart" + - "**/*.mocks.dart" errors: missing_required_param: error missing_return: error diff --git a/codecov.yml b/codecov.yml index fae4ac4..c684ddb 100644 --- a/codecov.yml +++ b/codecov.yml @@ -2,6 +2,7 @@ ignore: - '**/*.gr.dart' - '**/*.g.dart' - '**/*.freezed.dart' + - "**/*.mocks.dart" - '**/constants.dart' - '**/custom_theme.dart' - '**/assets_helper.dart' diff --git a/coverage_helper_script.bash b/coverage_helper_script.bash index 2368a90..90db6bb 100644 --- a/coverage_helper_script.bash +++ b/coverage_helper_script.bash @@ -5,5 +5,5 @@ echo "/// *** GENERATED FILE - ANY CHANGES WOULD BE OBSOLETE ON NEXT GENERATION echo "// Helper file to make coverage work for all dart files\n" > $file echo "// ignore_for_file: unused_import" >> $file packageName="$(cat pubspec.yaml| grep '^name: ' | awk '{print $2}')" -find lib '!' -path '*generated*/*' '!' -name '*.g.dart' '!' -name '*.freezed.dart' '!' -name '*.gr.dart' '!' -name '*.part.dart' -name '*.dart' | cut -c4- | awk -v package="$packageName" '{printf "import '\''package:%s%s'\'';\n", package, $1}' >> $file +find lib '!' -path '*generated*/*' '!' -name '*.g.dart' '!' -name '*.freezed.dart' '!' -name '*.gr.dart' '!' -name '*.part.dart' '!' -name '*.mocks.dart' -name '*.dart' | cut -c4- | awk -v package="$packageName" '{printf "import '\''package:%s%s'\'';\n", package, $1}' >> $file echo "\nvoid main(){}" >> $file \ No newline at end of file diff --git a/lib/helper/typedefs.dart b/lib/helper/typedefs.dart new file mode 100644 index 0000000..d620968 --- /dev/null +++ b/lib/helper/typedefs.dart @@ -0,0 +1,2 @@ +typedef JSON = Map; +typedef QueryParams = Map; \ No newline at end of file diff --git a/lib/helper/utils/constants.dart b/lib/helper/utils/constants.dart index 6bc5510..7c29bf3 100644 --- a/lib/helper/utils/constants.dart +++ b/lib/helper/utils/constants.dart @@ -187,5 +187,5 @@ class Constants { /// The error message for invalid credit card expiry input. static const invalidCreditCardExpiryError = 'Please enter a valid expiry date'; - static T? toNull(dynamic _) => null; + static T? toNull(Object? _) => null; } diff --git a/lib/helper/utils/exception_constants.dart b/lib/helper/utils/exception_constants.dart new file mode 100644 index 0000000..2a1d537 --- /dev/null +++ b/lib/helper/utils/exception_constants.dart @@ -0,0 +1,36 @@ +// ignore_for_file: constant_identifier_names +import 'package:flutter/foundation.dart'; + +/// A utility class that holds constants for our custom exception names. +/// This class has no constructor and all variables are `static`. +@immutable +class ExceptionConstants { + const ExceptionConstants._(); + + /// The name of the exception for an expired bearer token. + static const String TokenExpiredException = 'TokenExpiredException'; + + /// The name of the exception for a prematurely cancelled request. + static const String CancelException = 'CancelException'; + + /// The name of the exception for a failed connection attempt. + static const String ConnectTimeoutException = 'ConnectTimeoutException'; + + /// The name of the exception for failing to send a request. + static const String SendTimeoutException = 'SendTimeoutException'; + + /// The name of the exception for failing to receive a response. + static const String ReceiveTimeoutException = 'ReceiveTimeoutException'; + + /// The name of the exception for no internet connectivity. + static const String SocketException = 'SocketException'; + + /// A better name for the socket exception. + static const String FetchDataException = 'FetchDataException'; + + /// The name of the exception for an incorrect parameter in a request/response. + static const String FormatException = 'FormatException'; + + /// The name of the exception for an unknown type of failure. + static const String UnrecognizedException = 'UnrecognizedException'; +} diff --git a/lib/models/booking_model.dart b/lib/models/booking_model.dart index f414794..ed562e3 100644 --- a/lib/models/booking_model.dart +++ b/lib/models/booking_model.dart @@ -2,6 +2,7 @@ import 'package:freezed_annotation/freezed_annotation.dart'; import '../enums/booking_status_enum.dart'; import '../helper/utils/constants.dart'; +import '../helper/typedefs.dart'; part 'booking_model.freezed.dart'; @@ -24,7 +25,7 @@ class BookingModel with _$BookingModel { required DateTime bookingDatetime, }) = _BookingModel; - Map toUpdateJson({ + JSON toUpdateJson({ int? userId, int? showId, String? seatRow, @@ -39,7 +40,7 @@ class BookingModel with _$BookingModel { seatNumber == null && price == null && bookingStatus == null && - bookingDatetime == null) return const {}; + bookingDatetime == null) return const {}; return copyWith( userId: userId, showId: showId ?? this.showId, @@ -51,6 +52,6 @@ class BookingModel with _$BookingModel { ).toJson(); } - factory BookingModel.fromJson(Map json) => + factory BookingModel.fromJson(JSON json) => _$BookingModelFromJson(json); } diff --git a/lib/models/genre_model.dart b/lib/models/genre_model.dart index 9ad9558..45a6e8f 100644 --- a/lib/models/genre_model.dart +++ b/lib/models/genre_model.dart @@ -1,4 +1,5 @@ import 'package:freezed_annotation/freezed_annotation.dart'; +import '../helper/typedefs.dart'; part 'genre_model.freezed.dart'; @@ -12,6 +13,6 @@ class GenreModel with _$GenreModel { required String genre, }) = _GenreModel; - factory GenreModel.fromJson(Map json) => + factory GenreModel.fromJson(JSON json) => _$GenreModelFromJson(json); } diff --git a/lib/models/movie_model.dart b/lib/models/movie_model.dart index 1f8a13c..f756891 100644 --- a/lib/models/movie_model.dart +++ b/lib/models/movie_model.dart @@ -2,6 +2,7 @@ import 'package:freezed_annotation/freezed_annotation.dart'; import '../enums/movie_type_enum.dart'; import '../helper/utils/constants.dart'; +import '../helper/typedefs.dart'; import 'genre_model.dart'; part 'movie_model.freezed.dart'; @@ -42,7 +43,7 @@ class MovieModel with _$MovieModel { ); } - Map toUpdateJson({ + JSON toUpdateJson({ int? year, String? title, String? summary, @@ -58,7 +59,7 @@ class MovieModel with _$MovieModel { posterUrl == null && rating == null && movieType == null - ) return const {}; + ) return const {}; return copyWith( movieId: movieId, year: year ?? this.year, @@ -71,7 +72,7 @@ class MovieModel with _$MovieModel { ).toJson(); } - factory MovieModel.fromJson(Map json) => + factory MovieModel.fromJson(JSON json) => _$MovieModelFromJson(json); late final List genreNames = diff --git a/lib/models/movie_role_model.dart b/lib/models/movie_role_model.dart index 5f8befc..252c9b6 100644 --- a/lib/models/movie_role_model.dart +++ b/lib/models/movie_role_model.dart @@ -2,6 +2,7 @@ import 'package:freezed_annotation/freezed_annotation.dart'; //Enums import '../enums/role_type_enum.dart'; +import '../helper/typedefs.dart'; //Models import 'role_model.dart'; @@ -20,11 +21,11 @@ class MovieRoleModel with _$MovieRoleModel { required RoleType roleType, }) = _MovieRoleModel; - factory MovieRoleModel.fromJson(Map json) => + factory MovieRoleModel.fromJson(JSON json) => _$MovieRoleModelFromJson(json); - Map toCustomJson() { - return { + JSON toCustomJson() { + return { 'role_id': role.roleId, 'role_type': roleType.toJson, }; diff --git a/lib/models/payment_model.dart b/lib/models/payment_model.dart index cfb686e..6341f8c 100644 --- a/lib/models/payment_model.dart +++ b/lib/models/payment_model.dart @@ -2,6 +2,7 @@ import 'package:freezed_annotation/freezed_annotation.dart'; import '../enums/payment_method_enum.dart'; import '../helper/utils/constants.dart'; +import '../helper/typedefs.dart'; part 'payment_model.freezed.dart'; @@ -23,7 +24,7 @@ class PaymentModel with _$PaymentModel { required List? bookings, }) = _PaymentModel; - Map toUpdateJson({ + JSON toUpdateJson({ int? userId, int? showId, double? amount, @@ -34,7 +35,7 @@ class PaymentModel with _$PaymentModel { showId == null && amount == null && paymentMethod == null && - paymentDatetime == null) return const {}; + paymentDatetime == null) return const {}; return copyWith( paymentId: paymentId, showId: showId ?? this.showId, @@ -44,6 +45,6 @@ class PaymentModel with _$PaymentModel { ).toJson(); } - factory PaymentModel.fromJson(Map json) => + factory PaymentModel.fromJson(JSON json) => _$PaymentModelFromJson(json); } diff --git a/lib/models/role_model.dart b/lib/models/role_model.dart index f464f91..c8abdb7 100644 --- a/lib/models/role_model.dart +++ b/lib/models/role_model.dart @@ -1,5 +1,7 @@ import 'package:freezed_annotation/freezed_annotation.dart'; +import '../helper/typedefs.dart'; + part 'role_model.freezed.dart'; part 'role_model.g.dart'; @@ -14,6 +16,6 @@ class RoleModel with _$RoleModel { required String pictureUrl, }) = _RoleModel; - factory RoleModel.fromJson(Map json) => + factory RoleModel.fromJson(JSON json) => _$RoleModelFromJson(json); } diff --git a/lib/models/seat_model.dart b/lib/models/seat_model.dart index 8beaceb..5581e81 100644 --- a/lib/models/seat_model.dart +++ b/lib/models/seat_model.dart @@ -1,5 +1,7 @@ import 'package:freezed_annotation/freezed_annotation.dart'; +import '../helper/typedefs.dart'; + part 'seat_model.freezed.dart'; part 'seat_model.g.dart'; @@ -11,5 +13,5 @@ class SeatModel with _$SeatModel { required int seatNumber, }) = _SeatModel; - factory SeatModel.fromJson(Map json) => _$SeatModelFromJson(json); + factory SeatModel.fromJson(JSON json) => _$SeatModelFromJson(json); } diff --git a/lib/models/show_model.dart b/lib/models/show_model.dart index 9339887..f582d9d 100644 --- a/lib/models/show_model.dart +++ b/lib/models/show_model.dart @@ -2,6 +2,7 @@ import 'package:clock/clock.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; import 'show_time_model.dart'; +import '../helper/typedefs.dart'; part 'show_model.freezed.dart'; part 'show_model.g.dart'; @@ -25,6 +26,6 @@ class ShowModel with _$ShowModel { ); } - factory ShowModel.fromJson(Map json) => + factory ShowModel.fromJson(JSON json) => _$ShowModelFromJson(json); } diff --git a/lib/models/show_time_model.dart b/lib/models/show_time_model.dart index 3072cee..c53d7bb 100644 --- a/lib/models/show_time_model.dart +++ b/lib/models/show_time_model.dart @@ -2,6 +2,7 @@ import 'package:clock/clock.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; import '../helper/utils/constants.dart'; +import '../helper/typedefs.dart'; import '../enums/show_status_enum.dart'; import '../enums/show_type_enum.dart'; @@ -34,6 +35,6 @@ class ShowTimeModel with _$ShowTimeModel { ); } - factory ShowTimeModel.fromJson(Map json) => + factory ShowTimeModel.fromJson(JSON json) => _$ShowTimeModelFromJson(json); } diff --git a/lib/models/theater_model.dart b/lib/models/theater_model.dart index b393822..a7f3a3d 100644 --- a/lib/models/theater_model.dart +++ b/lib/models/theater_model.dart @@ -1,6 +1,7 @@ import 'package:freezed_annotation/freezed_annotation.dart'; import '../helper/utils/constants.dart'; +import '../helper/typedefs.dart'; import '../enums/theater_type_enum.dart'; import 'seat_model.dart'; @@ -23,7 +24,7 @@ class TheaterModel with _$TheaterModel { required List blocked, }) = _TheaterModel; - Map toUpdateJson({ + JSON toUpdateJson({ String? theaterName, int? numOfRows, int? seatsPerRow, @@ -37,7 +38,7 @@ class TheaterModel with _$TheaterModel { theaterType == null && missing == null && blocked == null - ) return const {}; + ) return const {}; return copyWith( theaterId: theaterId, numOfRows: numOfRows ?? this.numOfRows, @@ -48,6 +49,6 @@ class TheaterModel with _$TheaterModel { ).toJson(); } - factory TheaterModel.fromJson(Map json) => + factory TheaterModel.fromJson(JSON json) => _$TheaterModelFromJson(json); } diff --git a/lib/models/user_booking_model.dart b/lib/models/user_booking_model.dart index 111e847..3dba426 100644 --- a/lib/models/user_booking_model.dart +++ b/lib/models/user_booking_model.dart @@ -2,6 +2,7 @@ import 'package:freezed_annotation/freezed_annotation.dart'; import 'booking_model.dart'; import 'user_booking_show_model.dart'; +import '../helper/typedefs.dart'; part 'user_booking_model.freezed.dart'; part 'user_booking_model.g.dart'; @@ -16,5 +17,5 @@ class UserBookingModel with _$UserBookingModel { required List bookings, }) = _UserBookingModel; - factory UserBookingModel.fromJson(Map json) => _$UserBookingModelFromJson(json); + factory UserBookingModel.fromJson(JSON json) => _$UserBookingModelFromJson(json); } diff --git a/lib/models/user_booking_show_model.dart b/lib/models/user_booking_show_model.dart index 4a84cba..79cb0b2 100644 --- a/lib/models/user_booking_show_model.dart +++ b/lib/models/user_booking_show_model.dart @@ -1,4 +1,6 @@ import 'package:freezed_annotation/freezed_annotation.dart'; + +import '../helper/typedefs.dart'; import '../enums/show_type_enum.dart'; part 'user_booking_show_model.freezed.dart'; @@ -13,5 +15,5 @@ class UserBookingShowModel with _$UserBookingShowModel { required DateTime showDatetime, }) = _UserBookingShowModel; - factory UserBookingShowModel.fromJson(Map json) => _$UserBookingShowModelFromJson(json); + factory UserBookingShowModel.fromJson(JSON json) => _$UserBookingShowModelFromJson(json); } diff --git a/lib/models/user_model.dart b/lib/models/user_model.dart index 2646ca1..5fd97e7 100644 --- a/lib/models/user_model.dart +++ b/lib/models/user_model.dart @@ -1,5 +1,6 @@ import 'package:freezed_annotation/freezed_annotation.dart'; +import '../helper/typedefs.dart'; import '../enums/user_role_enum.dart'; part 'user_model.freezed.dart'; @@ -18,6 +19,6 @@ class UserModel with _$UserModel { required UserRole role, }) = _UserModel; - factory UserModel.fromJson(Map json) => + factory UserModel.fromJson(JSON json) => _$UserModelFromJson(json); } diff --git a/lib/models/user_payment_model.dart b/lib/models/user_payment_model.dart index 55b7d2b..566af90 100644 --- a/lib/models/user_payment_model.dart +++ b/lib/models/user_payment_model.dart @@ -1,5 +1,6 @@ import 'package:freezed_annotation/freezed_annotation.dart'; +import '../helper/typedefs.dart'; import '../enums/payment_method_enum.dart'; part 'user_payment_model.freezed.dart'; @@ -16,7 +17,7 @@ class UserPaymentModel with _$UserPaymentModel { required UserPaymentMovieModel movie, }) = _UserPaymentModel; - factory UserPaymentModel.fromJson(Map json) => _$UserPaymentModelFromJson(json); + factory UserPaymentModel.fromJson(JSON json) => _$UserPaymentModelFromJson(json); } @freezed @@ -28,7 +29,7 @@ class UserPaymentMovieModel with _$UserPaymentMovieModel { required String posterUrl, }) = _UserPaymentMovieModel; - factory UserPaymentMovieModel.fromJson(Map json) => _$UserPaymentMovieModelFromJson(json); + factory UserPaymentMovieModel.fromJson(JSON json) => _$UserPaymentMovieModelFromJson(json); } diff --git a/lib/providers/bookings_provider.dart b/lib/providers/bookings_provider.dart index 2d37cc9..be13972 100644 --- a/lib/providers/bookings_provider.dart +++ b/lib/providers/bookings_provider.dart @@ -6,6 +6,7 @@ import '../enums/booking_status_enum.dart'; //Helpers import '../helper/utils/constants.dart'; +import '../helper/typedefs.dart'; //Models import '../models/booking_model.dart'; @@ -46,10 +47,9 @@ class BookingsProvider { int? userId, int? showId, }) async { - final Map? queryParams = { + final JSON? queryParams = { if (bookingStatus != null) 'booking_status': bookingStatus.toJson, - if (bookingDatetime != null) - 'booking_datetime': bookingDatetime.toString(), + if (bookingDatetime != null) 'booking_datetime': bookingDatetime.toString(), if (userId != null) 'user_id': userId, if (showId != null) 'show_id': showId, }; diff --git a/lib/providers/movies_provider.dart b/lib/providers/movies_provider.dart index 3d40e02..04a4a59 100644 --- a/lib/providers/movies_provider.dart +++ b/lib/providers/movies_provider.dart @@ -1,5 +1,8 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; +//Helpers +import '../helper/typedefs.dart'; + //Enums import '../enums/movie_type_enum.dart'; @@ -47,7 +50,7 @@ class MoviesProvider { Future> getAllMovies({ MovieType? movieType, }) async { - final Map? queryParams = { + final QueryParams? queryParams = { if (movieType != null) 'movie_type': movieType.toJson, }; return await _moviesRepository.fetchAll(queryParameters: queryParams); @@ -89,7 +92,7 @@ class MoviesProvider { ); final roles = movieRoles.map((movieRole) => movieRole.toCustomJson()).toList(); - final data = { + final data = { ...movie.toJson(), 'roles': roles, }; diff --git a/lib/providers/payments_provider.dart b/lib/providers/payments_provider.dart index 7fa1d10..e7ceb30 100644 --- a/lib/providers/payments_provider.dart +++ b/lib/providers/payments_provider.dart @@ -5,6 +5,7 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; import '../enums/payment_method_enum.dart'; //Helpers +import '../helper/typedefs.dart'; import '../helper/utils/constants.dart'; //Models @@ -45,7 +46,7 @@ class PaymentsProvider { Future> getAllPayments({ PaymentMethod? paymentMethod, }) async { - final Map? queryParams = { + final QueryParams? queryParams = { if (paymentMethod != null) 'payment_method': paymentMethod.toJson, }; return await _paymentsRepository.fetchAll(queryParameters: queryParams); diff --git a/lib/providers/shows_provider.dart b/lib/providers/shows_provider.dart index bfc9e43..8f62f50 100644 --- a/lib/providers/shows_provider.dart +++ b/lib/providers/shows_provider.dart @@ -1,5 +1,8 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; +//Helpers +import '../helper/typedefs.dart'; + //Enums import '../enums/show_status_enum.dart'; import '../enums/show_type_enum.dart'; @@ -47,7 +50,7 @@ class ShowsProvider { Future> getAllShows({ required int movieId, }) async { - final Map? queryParams = { + final JSON? queryParams = { 'movie_id': movieId, }; return await _showsRepository.fetchAll(queryParameters: queryParams); @@ -69,7 +72,7 @@ class ShowsProvider { required ShowStatus showStatus, }) async { //TODO: Improve API for Show times and Show - final data = { + final data = { 'movie_id': movieId, 'theater_id': theaterId, 'start_time': startTime, @@ -102,7 +105,7 @@ class ShowsProvider { ShowType? showType, ShowStatus? showStatus, }) async { - final data = { + final data = { if (movieId != null) 'movie_id': movieId, if (theaterId != null) 'theater_id': theaterId, if (startTime != null) 'start_time': startTime, diff --git a/lib/providers/theaters_provider.dart b/lib/providers/theaters_provider.dart index 2b23f4f..04492eb 100644 --- a/lib/providers/theaters_provider.dart +++ b/lib/providers/theaters_provider.dart @@ -2,6 +2,9 @@ import 'package:flutter/foundation.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +//Helpers +import '../helper/typedefs.dart'; + //Enums import '../enums/theater_type_enum.dart'; @@ -72,7 +75,7 @@ class TheatersProvider extends ChangeNotifier { Future> getAllTheaters({ TheaterType? theaterType, }) async { - final Map? queryParams = { + final QueryParams? queryParams = { if (theaterType != null) 'theater_type': theaterType.toJson, }; final theaters = await _theatersRepository.fetchAll(queryParameters: queryParams); diff --git a/lib/services/local_storage/key_value_storage_service.dart b/lib/services/local_storage/key_value_storage_service.dart index a80ee35..5cfbae1 100644 --- a/lib/services/local_storage/key_value_storage_service.dart +++ b/lib/services/local_storage/key_value_storage_service.dart @@ -6,6 +6,9 @@ import 'key_value_storage_base.dart'; //models import '../../models/user_model.dart'; +//helpers +import '../../helper/typedefs.dart'; + //states import '../../providers/states/auth_state.dart'; @@ -42,7 +45,7 @@ class KeyValueStorageService { UserModel? getAuthUser() { final user = _keyValueStorage.getCommon(_authUserKey); if(user == null) return null; - return UserModel.fromJson(jsonDecode(user) as Map); + return UserModel.fromJson(jsonDecode(user) as JSON); } /// Returns last authentication token diff --git a/lib/services/networking/api_interface.dart b/lib/services/networking/api_interface.dart index b7dfac3..f9c80cd 100644 --- a/lib/services/networking/api_interface.dart +++ b/lib/services/networking/api_interface.dart @@ -1,5 +1,8 @@ import 'package:dio/dio.dart'; +//helpers +import '../../helper/typedefs.dart'; + /// A base class containing methods for basic API functionality. /// /// Should be implemented by any service class that wishes to interact @@ -24,10 +27,10 @@ abstract class ApiInterface { /// in the **headers** of the request using an [ApiInterceptor]. Future> getCollectionData({ required String endpoint, - Map? queryParams, + JSON? queryParams, CancelToken? cancelToken, bool requiresAuthToken = true, - required T Function(Map responseBody) converter, + required T Function(JSON responseBody) converter, }); /// Base method for requesting a document of data from the [endpoint]. @@ -44,10 +47,10 @@ abstract class ApiInterface { /// in the **headers** of the request using an [ApiInterceptor] Future getDocumentData({ required String endpoint, - Map? queryParams, + JSON? queryParams, CancelToken? cancelToken, bool requiresAuthToken = true, - required T Function(Map responseBody) converter, + required T Function(JSON responseBody) converter, }); /// Base method for inserting [data] at the [endpoint]. @@ -64,10 +67,10 @@ abstract class ApiInterface { /// in the **headers** of the request using an [ApiInterceptor] Future setData({ required String endpoint, - required Map data, + required JSON data, CancelToken? cancelToken, bool requiresAuthToken = true, - required T Function(Map response) converter, + required T Function(JSON response) converter, }); /// Base method for updating [data] at the [endpoint]. @@ -84,10 +87,10 @@ abstract class ApiInterface { /// in the **headers** of the request using an [ApiInterceptor] Future updateData({ required String endpoint, - required Map data, + required JSON data, CancelToken? cancelToken, bool requiresAuthToken = true, - required T Function(Map response) converter, + required T Function(JSON response) converter, }); /// Base method for deleting [data] at the [endpoint]. @@ -104,10 +107,10 @@ abstract class ApiInterface { /// in the **headers** of the request using an [ApiInterceptor] Future deleteData({ required String endpoint, - Map? data, + JSON? data, CancelToken? cancelToken, bool requiresAuthToken = true, - required T Function(Map response) converter, + required T Function(JSON response) converter, }); /// Base method for cancelling requests pre-maturely diff --git a/lib/services/networking/api_service.dart b/lib/services/networking/api_service.dart index 40dda2b..c2505fd 100644 --- a/lib/services/networking/api_service.dart +++ b/lib/services/networking/api_service.dart @@ -1,8 +1,12 @@ import 'package:dio/dio.dart'; +//services import 'api_interface.dart'; import 'dio_service.dart'; +//helpers +import '../../helper/typedefs.dart'; + /// A service class implementing methods for basic API requests. class ApiService implements ApiInterface{ @@ -33,24 +37,24 @@ class ApiService implements ApiInterface{ @override Future> getCollectionData({ required String endpoint, - Map? queryParams, + JSON? queryParams, CancelToken? cancelToken, bool requiresAuthToken = true, - required T Function(Map responseBody) converter, + required T Function(JSON responseBody) converter, }) async { //Entire map of response final data = await _dioService.get( endpoint: endpoint, - options: Options(headers: {'requiresAuthToken': requiresAuthToken}), + options: Options(headers: {'requiresAuthToken': requiresAuthToken}), queryParams: queryParams, cancelToken: cancelToken, ); //Items of table as json - final List body = data['body'] as List; + final body = data['body'] as List; //Returning the deserialized objects - return body.map((dynamic dataMap) => converter(dataMap as Map)).toList(); + return body.map((dataMap) => converter(dataMap as JSON)).toList(); } /// An implementation of the base method for requesting a document of data @@ -73,21 +77,21 @@ class ApiService implements ApiInterface{ @override Future getDocumentData({ required String endpoint, - Map? queryParams, + JSON? queryParams, CancelToken? cancelToken, bool requiresAuthToken = true, - required T Function(Map responseBody) converter, + required T Function(JSON responseBody) converter, }) async { //Entire map of response final data = await _dioService.get( endpoint: endpoint, queryParams: queryParams, - options: Options(headers: {'requiresAuthToken': requiresAuthToken}), + options: Options(headers: {'requiresAuthToken': requiresAuthToken}), cancelToken: cancelToken, ); //Returning the deserialized object - return converter(data['body'] as Map); + return converter(data['body'] as JSON); } /// An implementation of the base method for inserting [data] at @@ -109,16 +113,16 @@ class ApiService implements ApiInterface{ @override Future setData({ required String endpoint, - required Map data, + required JSON data, CancelToken? cancelToken, bool requiresAuthToken = true, - required T Function(Map response) converter, + required T Function(JSON response) converter, }) async { //Entire map of response final dataMap = await _dioService.post( endpoint: endpoint, data: data, - options: Options(headers: {'requiresAuthToken': requiresAuthToken}), + options: Options(headers: {'requiresAuthToken': requiresAuthToken}), cancelToken: cancelToken, ); @@ -144,16 +148,16 @@ class ApiService implements ApiInterface{ @override Future updateData({ required String endpoint, - required Map data, + required JSON data, CancelToken? cancelToken, bool requiresAuthToken = true, - required T Function(Map response) converter, + required T Function(JSON response) converter, }) async { //Entire map of response final dataMap = await _dioService.patch( endpoint: endpoint, data: data, - options: Options(headers: {'requiresAuthToken': requiresAuthToken}), + options: Options(headers: {'requiresAuthToken': requiresAuthToken}), cancelToken: cancelToken, ); @@ -179,16 +183,16 @@ class ApiService implements ApiInterface{ @override Future deleteData({ required String endpoint, - Map? data, + JSON? data, CancelToken? cancelToken, bool requiresAuthToken = true, - required T Function(Map response) converter, + required T Function(JSON response) converter, }) async { //Entire map of response final dataMap = await _dioService.delete( endpoint: endpoint, data: data, - options: Options(headers: {'requiresAuthToken': requiresAuthToken}), + options: Options(headers: {'requiresAuthToken': requiresAuthToken}), cancelToken: cancelToken, ); diff --git a/lib/services/networking/dio_service.dart b/lib/services/networking/dio_service.dart index 87b0699..dab6338 100644 --- a/lib/services/networking/dio_service.dart +++ b/lib/services/networking/dio_service.dart @@ -5,6 +5,9 @@ import 'package:dio/dio.dart'; //Exceptions import 'network_exception.dart'; +//helpers +import '../../helper/typedefs.dart'; + /// A service class that wraps the [Dio] instance and provides methods for /// basic network requests. class DioService { @@ -47,20 +50,20 @@ class DioService { /// the **default** [cancelToken] inside [DioService] is used. /// /// [options] are special instructions that can be merged with the request. - Future> get({ + Future get({ required String endpoint, - Map? queryParams, + JSON? queryParams, Options? options, CancelToken? cancelToken, }) async { try { - final response = await _dio.get>( + final response = await _dio.get( endpoint, queryParameters: queryParams, options: options, cancelToken: cancelToken ?? _cancelToken, ); - return response.data as Map; + return response.data as JSON; } on Exception catch (ex) { throw NetworkException.getDioException(ex); } @@ -78,20 +81,20 @@ class DioService { /// the **default** [cancelToken] inside [DioService] is used. /// /// [options] are special instructions that can be merged with the request. - Future> post({ + Future post({ required String endpoint, - Map? data, + JSON? data, Options? options, CancelToken? cancelToken, }) async { try { - final response = await _dio.post>( + final response = await _dio.post( endpoint, data: data, options: options, cancelToken: cancelToken ?? _cancelToken, ); - return response.data as Map; + return response.data as JSON; } on Exception catch (ex) { throw NetworkException.getDioException(ex); } @@ -109,20 +112,20 @@ class DioService { /// the **default** [cancelToken] inside [DioService] is used. /// /// [options] are special instructions that can be merged with the request. - Future> patch({ + Future patch({ required String endpoint, - Map? data, + JSON? data, Options? options, CancelToken? cancelToken, }) async { try { - final response = await _dio.put>( + final response = await _dio.put( endpoint, data: data, options: options, cancelToken: cancelToken ?? _cancelToken, ); - return response.data as Map; + return response.data as JSON; } on Exception catch (ex) { throw NetworkException.getDioException(ex); } @@ -140,20 +143,20 @@ class DioService { /// the **default** [cancelToken] inside [DioService] is used. /// /// [options] are special instructions that can be merged with the request. - Future> delete({ + Future delete({ required String endpoint, - Map? data, + JSON? data, Options? options, CancelToken? cancelToken, }) async { try { - final response = await _dio.delete>( + final response = await _dio.delete( endpoint, data: data, options: options, cancelToken: cancelToken ?? _cancelToken, ); - return response.data as Map; + return response.data as JSON; } on Exception catch (ex) { throw NetworkException.getDioException(ex); } diff --git a/lib/services/networking/interceptors/api_interceptor.dart b/lib/services/networking/interceptors/api_interceptor.dart index eb33a5a..a2be5cd 100644 --- a/lib/services/networking/interceptors/api_interceptor.dart +++ b/lib/services/networking/interceptors/api_interceptor.dart @@ -39,7 +39,7 @@ class ApiInterceptor extends Interceptor { if (options.headers.containsKey('requiresAuthToken')) { if(options.headers['requiresAuthToken'] == true){ final token = await _ref.read(keyValueStorageServiceProvider).getAuthToken(); - options.headers.addAll({'Authorization': 'Bearer $token'}); + options.headers.addAll({'Authorization': 'Bearer $token'}); } options.headers.remove('requiresAuthToken'); diff --git a/lib/services/networking/interceptors/logging_interceptor.dart b/lib/services/networking/interceptors/logging_interceptor.dart index 1c94ecf..fed65e8 100644 --- a/lib/services/networking/interceptors/logging_interceptor.dart +++ b/lib/services/networking/interceptors/logging_interceptor.dart @@ -3,6 +3,9 @@ import 'dart:io'; import 'package:flutter/foundation.dart'; import 'package:dio/dio.dart'; +//helpers +import '../../../helper/typedefs.dart'; + /// A class that intercepts network requests for logging purposes only. This is /// the second interceptor in case of both request and response. /// @@ -39,11 +42,11 @@ class LoggingInterceptor extends Interceptor { debugPrint('--> $httpMethod $url'); //GET www.example.com/mock_path/all debugPrint('\tHeaders:'); - options.headers.forEach((k, dynamic v) => debugPrint('\t\t$k: $v')); + options.headers.forEach((k, Object? v) => debugPrint('\t\t$k: $v')); if(options.queryParameters.isNotEmpty){ debugPrint('\tqueryParams:'); - options.queryParameters.forEach((k, dynamic v) => debugPrint('\t\t$k: $v')); + options.queryParameters.forEach((k, Object? v) => debugPrint('\t\t$k: $v')); } if (options.data != null) { @@ -120,13 +123,13 @@ class LoggingInterceptor extends Interceptor { if(dioError.response != null){ debugPrint('\tStatus code: ${dioError.response!.statusCode}'); if(dioError.response!.data != null){ - final headers = dioError.response!.data['headers'] as Map; //API Dependant + final headers = dioError.response!.data['headers'] as JSON; //API Dependant var message = headers['message'] as String; //API Dependant var error = headers['error'] as String; //API Dependant debugPrint('\tException: $error'); debugPrint('\tMessage: $message'); if(headers.containsKey('data')){ //API Dependant - var data = headers['data'] as List; + var data = headers['data'] as List; if(data.isNotEmpty) { debugPrint('\tData: $data'); } diff --git a/lib/services/networking/interceptors/refresh_token_interceptor.dart b/lib/services/networking/interceptors/refresh_token_interceptor.dart index a039cba..6d29b96 100644 --- a/lib/services/networking/interceptors/refresh_token_interceptor.dart +++ b/lib/services/networking/interceptors/refresh_token_interceptor.dart @@ -8,6 +8,9 @@ import '../../../providers/all_providers.dart'; //Endpoints import '../api_endpoint.dart'; +//Helpers +import '../../../helper/typedefs.dart'; + /// A class that holds intercepting logic for refreshing expired tokens. This /// is the last interceptor in the queue. class RefreshTokenInterceptor extends Interceptor { @@ -45,7 +48,7 @@ class RefreshTokenInterceptor extends Interceptor { ) async { if (dioError.response != null) { if (dioError.response!.data != null) { - final headers = dioError.response!.data['headers'] as Map; + final headers = dioError.response!.data['headers'] as JSON; //Check error type to be token expired error var error = headers['error'] as String; @@ -80,12 +83,12 @@ class RefreshTokenInterceptor extends Interceptor { _dio.clear(); //Make original req with new token - final response = await _dio.request>( + final response = await _dio.request( dioError.requestOptions.path, data: dioError.requestOptions.data, cancelToken: dioError.requestOptions.cancelToken, options: Options( - headers: {'Authorization': 'Bearer $newToken'}, + headers: {'Authorization': 'Bearer $newToken'}, ), ); return handler.resolve(response); @@ -106,13 +109,13 @@ class RefreshTokenInterceptor extends Interceptor { required DioError dioError, required ErrorInterceptorHandler handler, required Dio tokenDio, - required Map data, + required JSON data, }) async { debugPrint('--> REFRESHING TOKEN'); try { debugPrint('\tBody: $data'); - final response = await tokenDio.post>( + final response = await tokenDio.post( ApiEndpoint.auth(AuthEndpoint.REFRESH_TOKEN), data: data, ); diff --git a/lib/services/networking/network_exception.dart b/lib/services/networking/network_exception.dart index 7ecd415..d1f2530 100644 --- a/lib/services/networking/network_exception.dart +++ b/lib/services/networking/network_exception.dart @@ -1,59 +1,52 @@ +// ignore_for_file: non_constant_identifier_names import 'package:dio/dio.dart'; +import 'package:ez_ticketz_app/helper/utils/exception_constants.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; part 'network_exception.freezed.dart'; @freezed class NetworkException with _$NetworkException { - // ignore: non_constant_identifier_names const factory NetworkException.FormatException({ required String name, required String message, }) = _FormatException; - // ignore: non_constant_identifier_names const factory NetworkException.FetchDataException({ required String name, required String message, }) = _FetchDataException; - // ignore: non_constant_identifier_names const factory NetworkException.ApiException({ required String name, required String message, }) = _ApiException; - // ignore: non_constant_identifier_names const factory NetworkException.TokenExpiredException({ required String name, required String message, }) = _TokenExpiredException; - // ignore: non_constant_identifier_names const factory NetworkException.UnrecognizedException({ required String name, required String message, }) = _UnrecognizedException; - // ignore: non_constant_identifier_names const factory NetworkException.CancelException({ required String name, required String message, }) = _CancelException; - // ignore: non_constant_identifier_names const factory NetworkException.ConnectTimeoutException({ required String name, required String message, }) = _ConnectTimeoutException; - // ignore: non_constant_identifier_names const factory NetworkException.ReceiveTimeoutException({ required String name, required String message, }) = _ReceiveTimeoutException; - // ignore: non_constant_identifier_names const factory NetworkException.SendTimeoutException({ required String name, required String message, @@ -65,36 +58,36 @@ class NetworkException with _$NetworkException { switch (error.type) { case DioErrorType.cancel: return const NetworkException.CancelException( - name: 'CancelException', + name: ExceptionConstants.CancelException, message: 'Request cancelled prematurely', ); case DioErrorType.connectTimeout: return const NetworkException.ConnectTimeoutException( - name: 'ConnectTimeoutException', + name: ExceptionConstants.ConnectTimeoutException, message: 'Connection not established', ); - case DioErrorType.receiveTimeout: + case DioErrorType.sendTimeout: return const NetworkException.SendTimeoutException( - name: 'SendTimeoutException', + name: ExceptionConstants.SendTimeoutException, message: 'Failed to send', ); - case DioErrorType.sendTimeout: + case DioErrorType.receiveTimeout: return const NetworkException.ReceiveTimeoutException( - name: 'ReceiveTimeoutException', + name: ExceptionConstants.ReceiveTimeoutException, message: 'Failed to receive', ); case DioErrorType.response: case DioErrorType.other: - if(error.message.contains('SocketException')) { + if(error.message.contains(ExceptionConstants.SocketException)) { return const NetworkException.FetchDataException( - name: 'FetchDataException', + name: ExceptionConstants.FetchDataException, message: 'No internet connectivity', ); } final name = error.response?.data['headers']['error'] as String; final message = error.response?.data['headers']['message'] as String; switch (name) { - case 'TokenExpiredException': + case ExceptionConstants.TokenExpiredException: return NetworkException.TokenExpiredException( name: name, message: message, @@ -108,18 +101,18 @@ class NetworkException with _$NetworkException { } } else { return const NetworkException.UnrecognizedException( - name: 'UnrecognizedException', + name: ExceptionConstants.UnrecognizedException, message: 'Error unrecognized', ); } } on FormatException catch (e) { return NetworkException.FormatException( - name: 'FormatException', + name: ExceptionConstants.FormatException, message: e.message, ); } on Exception catch (_) { return const NetworkException.UnrecognizedException( - name: 'UnrecognizedException', + name: ExceptionConstants.UnrecognizedException, message: 'Error unrecognized', ); } diff --git a/lib/services/repositories/auth_repository.dart b/lib/services/repositories/auth_repository.dart index f79d4de..a3bb444 100644 --- a/lib/services/repositories/auth_repository.dart +++ b/lib/services/repositories/auth_repository.dart @@ -5,13 +5,16 @@ import '../../models/user_model.dart'; import '../networking/api_endpoint.dart'; import '../networking/api_service.dart'; +//helpers +import '../../helper/typedefs.dart'; + class AuthRepository { final ApiService _apiService; AuthRepository({required ApiService apiService}) : _apiService = apiService; Future sendLoginData({ - required Map data, + required JSON data, required void Function(String newToken) updateTokenCallback, }) async { return await _apiService.setData( @@ -20,13 +23,13 @@ class AuthRepository { requiresAuthToken: false, converter: (response) { updateTokenCallback(response['body']['token'] as String); - return UserModel.fromJson(response['body'] as Map); + return UserModel.fromJson(response['body'] as JSON); }, ); } Future sendRegisterData({ - required Map data, + required JSON data, required void Function(String newToken) updateTokenCallback, }) async { return await _apiService.setData( @@ -42,7 +45,7 @@ class AuthRepository { } Future sendForgotPasswordData({ - required Map data, + required JSON data, }) async { return await _apiService.setData( endpoint: ApiEndpoint.auth(AuthEndpoint.FORGOT_PASSWORD), @@ -53,7 +56,7 @@ class AuthRepository { } Future sendResetPasswordData({ - required Map data, + required JSON data, }) async { return await _apiService.setData( endpoint: ApiEndpoint.auth(AuthEndpoint.RESET_PASSWORD), @@ -64,7 +67,7 @@ class AuthRepository { } Future sendChangePasswordData({ - required Map data, + required JSON data, }) async { return await _apiService.setData( endpoint: ApiEndpoint.auth(AuthEndpoint.CHANGE_PASSWORD), @@ -74,7 +77,7 @@ class AuthRepository { ); } - Future sendOtpData({required Map data}) async { + Future sendOtpData({required JSON data}) async { return await _apiService.setData( endpoint: ApiEndpoint.auth(AuthEndpoint.VERIFY_OTP), data: data, diff --git a/lib/services/repositories/bookings_repository.dart b/lib/services/repositories/bookings_repository.dart index 8ce5fbc..2efca26 100644 --- a/lib/services/repositories/bookings_repository.dart +++ b/lib/services/repositories/bookings_repository.dart @@ -9,6 +9,9 @@ import '../../models/seat_model.dart'; import '../networking/api_endpoint.dart'; import '../networking/api_service.dart'; +//helpers +import '../../helper/typedefs.dart'; + class BookingsRepository { final ApiService _apiService; final CancelToken? _cancelToken; @@ -38,7 +41,7 @@ class BookingsRepository { } Future create({ - required Map data, + required JSON data, }) async { return await _apiService.setData( endpoint: ApiEndpoint.bookings(BookingEndpoint.BASE), @@ -50,7 +53,7 @@ class BookingsRepository { Future update({ required int bookingId, - required Map data, + required JSON data, }) async { return await _apiService.updateData( endpoint: ApiEndpoint.bookings(BookingEndpoint.BY_ID, id: bookingId), @@ -62,7 +65,7 @@ class BookingsRepository { Future delete({ required int bookingId, - Map? data, + JSON? data, }) async { return await _apiService.deleteData( endpoint: ApiEndpoint.bookings(BookingEndpoint.BY_ID, id: bookingId), @@ -79,8 +82,8 @@ class BookingsRepository { endpoint: ApiEndpoint.bookings(BookingEndpoint.SHOWS, id: showId), cancelToken: _cancelToken, converter: (responseBody) { - return responseBody['booked_seats'].map((Map seat) { - return SeatModel.fromJson(seat); + return responseBody['booked_seats'].map((dynamic seat) { + return SeatModel.fromJson(seat as JSON); }).toList() as List; }, ); @@ -97,7 +100,7 @@ class BookingsRepository { } Future> fetchFilteredBookings({ - Map? queryParameters, + JSON? queryParameters, }) async { return await _apiService.getCollectionData( endpoint: ApiEndpoint.bookings(BookingEndpoint.FILTERS), diff --git a/lib/services/repositories/movies_repository.dart b/lib/services/repositories/movies_repository.dart index d01cca3..9d239ae 100644 --- a/lib/services/repositories/movies_repository.dart +++ b/lib/services/repositories/movies_repository.dart @@ -8,6 +8,9 @@ import '../../models/movie_role_model.dart'; import '../networking/api_endpoint.dart'; import '../networking/api_service.dart'; +//helpers +import '../../helper/typedefs.dart'; + class MoviesRepository { final ApiService _apiService; final CancelToken? _cancelToken; @@ -19,7 +22,7 @@ class MoviesRepository { _cancelToken = cancelToken; Future create({ - required Map data, + required JSON data, }) async { return await _apiService.setData( endpoint: ApiEndpoint.movies(MovieEndpoint.BASE), @@ -31,7 +34,7 @@ class MoviesRepository { Future update({ required int movieId, - required Map data, + required JSON data, }) async { return await _apiService.updateData( endpoint: ApiEndpoint.movies(MovieEndpoint.BY_ID, id: movieId), @@ -43,7 +46,7 @@ class MoviesRepository { Future delete({ required int movieId, - Map? data, + JSON? data, }) async { return await _apiService.deleteData( endpoint: ApiEndpoint.movies(MovieEndpoint.BY_ID, id: movieId), @@ -54,7 +57,7 @@ class MoviesRepository { } Future> fetchAll({ - Map? queryParameters, + JSON? queryParameters, }) async { return await _apiService.getCollectionData( endpoint: ApiEndpoint.movies(MovieEndpoint.BASE), diff --git a/lib/services/repositories/payments_repository.dart b/lib/services/repositories/payments_repository.dart index 276b38d..c4e9095 100644 --- a/lib/services/repositories/payments_repository.dart +++ b/lib/services/repositories/payments_repository.dart @@ -8,6 +8,9 @@ import '../../models/user_payment_model.dart'; import '../networking/api_endpoint.dart'; import '../networking/api_service.dart'; +//helpers +import '../../helper/typedefs.dart'; + class PaymentsRepository { final ApiService _apiService; final CancelToken? _cancelToken; @@ -19,7 +22,7 @@ class PaymentsRepository { _cancelToken = cancelToken; Future> fetchAll({ - Map? queryParameters, + JSON? queryParameters, }) async { return await _apiService.getCollectionData( endpoint: ApiEndpoint.payments(PaymentEndpoint.BASE), @@ -40,7 +43,7 @@ class PaymentsRepository { } Future create({ - required Map data, + required JSON data, }) async { return await _apiService.setData( endpoint: ApiEndpoint.payments(PaymentEndpoint.BASE), @@ -52,7 +55,7 @@ class PaymentsRepository { Future update({ required int paymentId, - required Map data, + required JSON data, }) async { return await _apiService.updateData( endpoint: ApiEndpoint.payments(PaymentEndpoint.BY_ID, id: paymentId), @@ -64,7 +67,7 @@ class PaymentsRepository { Future delete({ required int paymentId, - Map? data, + JSON? data, }) async { return await _apiService.deleteData( endpoint: ApiEndpoint.payments(PaymentEndpoint.BY_ID, id: paymentId), diff --git a/lib/services/repositories/shows_repository.dart b/lib/services/repositories/shows_repository.dart index 9a731fe..b19267c 100644 --- a/lib/services/repositories/shows_repository.dart +++ b/lib/services/repositories/shows_repository.dart @@ -7,6 +7,9 @@ import '../../models/show_model.dart'; import '../networking/api_endpoint.dart'; import '../networking/api_service.dart'; +//helpers +import '../../helper/typedefs.dart'; + class ShowsRepository { final ApiService _apiService; final CancelToken? _cancelToken; @@ -18,7 +21,7 @@ class ShowsRepository { _cancelToken = cancelToken; Future> fetchAll({ - Map? queryParameters, + JSON? queryParameters, }) async { return await _apiService.getCollectionData( endpoint: ApiEndpoint.shows( @@ -41,7 +44,7 @@ class ShowsRepository { } Future create({ - required Map data, + required JSON data, }) async { return await _apiService.setData( endpoint: ApiEndpoint.shows(ShowEndpoint.BASE), @@ -53,7 +56,7 @@ class ShowsRepository { Future update({ required int showId, - required Map data, + required JSON data, }) async { return await _apiService.updateData( endpoint: ApiEndpoint.shows(ShowEndpoint.BY_ID, id: showId), @@ -65,7 +68,7 @@ class ShowsRepository { Future delete({ required int showId, - Map? data, + JSON? data, }) async { return await _apiService.deleteData( endpoint: ApiEndpoint.shows(ShowEndpoint.BY_ID, id: showId), diff --git a/lib/services/repositories/theaters_repository.dart b/lib/services/repositories/theaters_repository.dart index 3f71212..432a52b 100644 --- a/lib/services/repositories/theaters_repository.dart +++ b/lib/services/repositories/theaters_repository.dart @@ -7,6 +7,9 @@ import '../../models/theater_model.dart'; import '../networking/api_endpoint.dart'; import '../networking/api_service.dart'; +//helpers +import '../../helper/typedefs.dart'; + class TheatersRepository { final ApiService _apiService; final CancelToken? _cancelToken; @@ -18,7 +21,7 @@ class TheatersRepository { _cancelToken = cancelToken; Future> fetchAll({ - Map? queryParameters, + JSON? queryParameters, }) async { return await _apiService.getCollectionData( endpoint: ApiEndpoint.theaters(TheaterEndpoint.BASE), @@ -39,7 +42,7 @@ class TheatersRepository { } Future create({ - required Map data, + required JSON data, }) async { return await _apiService.setData( endpoint: ApiEndpoint.theaters(TheaterEndpoint.BASE), @@ -51,7 +54,7 @@ class TheatersRepository { Future update({ required int theaterId, - required Map data, + required JSON data, }) async { return await _apiService.updateData( endpoint: ApiEndpoint.theaters(TheaterEndpoint.BY_ID, id: theaterId), @@ -63,7 +66,7 @@ class TheatersRepository { Future delete({ required int theaterId, - Map? data, + JSON? data, }) async { return await _apiService.deleteData( endpoint: ApiEndpoint.theaters(TheaterEndpoint.BY_ID, id: theaterId), diff --git a/lib/views/widgets/common/custom_network_image.dart b/lib/views/widgets/common/custom_network_image.dart index b6472a3..140a2cd 100644 --- a/lib/views/widgets/common/custom_network_image.dart +++ b/lib/views/widgets/common/custom_network_image.dart @@ -37,7 +37,7 @@ class CustomNetworkImage extends StatelessWidget { padding: margin ?? EdgeInsets.zero, child: placeholder ?? const SizedBox.shrink(), ), - errorWidget: (_,__,dynamic ___) => Padding( + errorWidget: (_,__,Object? ___) => Padding( padding: margin ?? EdgeInsets.zero, child: errorWidget ?? const SizedBox.shrink(), ), diff --git a/lib/views/widgets/movies/movie_backdrop_view.dart b/lib/views/widgets/movies/movie_backdrop_view.dart index b558989..69d6f7b 100644 --- a/lib/views/widgets/movies/movie_backdrop_view.dart +++ b/lib/views/widgets/movies/movie_backdrop_view.dart @@ -34,7 +34,7 @@ class MovieBackdropView extends HookWidget { iconSize: 85, borderRadius: 0, ), - errorWidget: (_, __, dynamic ___) => const MoviePosterPlaceholder( + errorWidget: (_, __, Object? ___) => const MoviePosterPlaceholder( childXAlign: Alignment.topCenter, borderRadius: 0, iconSize: 85, diff --git a/lib/views/widgets/user_bookings/booking_details_dialog.dart b/lib/views/widgets/user_bookings/booking_details_dialog.dart index bcf572b..4892602 100644 --- a/lib/views/widgets/user_bookings/booking_details_dialog.dart +++ b/lib/views/widgets/user_bookings/booking_details_dialog.dart @@ -1,3 +1,5 @@ +import 'dart:ui'; + import 'package:flutter/material.dart'; //Helpers @@ -48,12 +50,6 @@ class BookingDetailsDialog extends StatelessWidget { Expanded( child: Material( color: Constants.scaffoldColor, - shape: const RoundedRectangleBorder( - borderRadius: BorderRadius.only( - bottomRight: Radius.circular(20), - bottomLeft: Radius.circular(20), - ), - ), child: Padding( padding: const EdgeInsets.fromLTRB(15, 12, 15, 0), child: Column( @@ -120,8 +116,8 @@ class BookingDetailsDialog extends StatelessWidget { decoration: const BoxDecoration( color: Constants.primaryColor, borderRadius: BorderRadius.only( - bottomLeft: Radius.circular(10), - bottomRight: Radius.circular(10), + bottomLeft: Radius.circular(20), + bottomRight: Radius.circular(20), ), ), child: const Icon( diff --git a/pubspec.lock b/pubspec.lock index b07893c..6c64693 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -520,6 +520,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.0.0" + mockito: + dependency: "direct main" + description: + name: mockito + url: "https://pub.dartlang.org" + source: hosted + version: "5.0.13" octo_image: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index a0e835b..56d1ed3 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -4,7 +4,7 @@ description: A new Flutter application. publish_to: none version: 1.0.0+1 environment: - sdk: '>=2.12.0 <3.0.0' + sdk: '>=2.13.0 <3.0.0' dependencies: flutter: sdk: flutter @@ -27,6 +27,7 @@ dependencies: flutter_spinkit: 5.0.0 flutter_secure_storage: ^4.2.1 clock: ^1.1.0 + mockito: ^5.0.13 dev_dependencies: flutter_test: sdk: flutter diff --git a/test/helper/extensions/context_extension_test.dart b/test/helper/extensions/context_extension_test.dart new file mode 100644 index 0000000..010e286 --- /dev/null +++ b/test/helper/extensions/context_extension_test.dart @@ -0,0 +1,75 @@ +import 'package:ez_ticketz_app/helper/extensions/context_extensions.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mockito/annotations.dart'; +import 'package:mockito/mockito.dart'; + +import 'context_extension_test.mocks.dart'; + +@GenerateMocks([BuildContext]) +void main() { + group('ContextUtils', () { + group('extensions related to MediaQuery', () { + BuildContext _mockContextWithMediaQuery(Size size) { + final context = MockBuildContext(); + final mediaQuery = MediaQuery( + data: MediaQueryData(size: size), + child: const SizedBox.shrink(), + ); + when(context.widget).thenReturn(const SizedBox.shrink()); + when(context.findAncestorWidgetOfExactType()).thenReturn(mediaQuery); + when(context.dependOnInheritedWidgetOfExactType()) + .thenReturn(mediaQuery); + return context; + } + + group('screenWidth', () { + test( + 'GIVEN a Size created with a screen width ' + 'AND a BuildContext with MediaQuery ' + 'AND that MediaQuery has that Size ' + 'WHEN extension method `.screenWidth` is called ' + 'THEN a double is returned ' + 'AND is equal to that screen width', + () { + //given + const screenWidth = 500.0; + const size = Size.fromWidth(screenWidth); + final context = _mockContextWithMediaQuery(size); + + //when + final actual = context.screenWidth; + + //then + expect(actual, isA()); + expect(actual, screenWidth); + }, + ); + }); + + group('screenHeight', () { + test( + 'GIVEN a Size created with a screen height ' + 'AND a BuildContext with MediaQuery ' + 'AND that MediaQuery has that Size ' + 'WHEN extension method `.screenHeight` is called ' + 'THEN a double is returned ' + 'AND is equal to that screen height', + () { + //given + const screenHeight = 650.0; + const size = Size.fromHeight(screenHeight); + final context = _mockContextWithMediaQuery(size); + + //when + final actual = context.screenHeight; + + //then + expect(actual, isA()); + expect(actual, screenHeight); + }, + ); + }); + }); + }); +} diff --git a/test/helper/extensions/int_extension_test.dart b/test/helper/extensions/int_extension_test.dart new file mode 100644 index 0000000..1474bb9 --- /dev/null +++ b/test/helper/extensions/int_extension_test.dart @@ -0,0 +1,66 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:ez_ticketz_app/helper/extensions/int_extension.dart'; + +void main() { + group('IntExt', () { + group('milliseconds', () { + test( + 'GIVEN a valid integer ' + 'WHEN extension method `.milliseconds is called ' + 'THEN a Datetime object returned ' + 'AND the milliseconds are equal to that `integer`', + () { + //given + const integer = 500; + + //when + final millisecondsDuration = integer.milliseconds; + + //then + expect(millisecondsDuration, isA()); + expect(millisecondsDuration.inMilliseconds, integer); + }, + ); + }); + + group('microseconds', () { + test( + 'GIVEN a valid integer ' + 'WHEN extension method `.microseconds is called ' + 'THEN a Datetime object returned ' + 'AND the microseconds are equal to that `integer`', + () { + //given + const integer = 5000; + + //when + final microsecondsDuration = integer.microseconds; + + //then + expect(microsecondsDuration, isA()); + expect(microsecondsDuration.inMicroseconds, integer); + }, + ); + }); + + group('seconds', () { + test( + 'GIVEN a valid integer ' + 'WHEN extension method `.seconds is called ' + 'THEN a Datetime object returned ' + 'AND the seconds are equal to that `integer`', + () { + //given + const integer = 5; + + //when + final secondsDuration = integer.seconds; + + //then + expect(secondsDuration, isA()); + expect(secondsDuration.inSeconds, integer); + }, + ); + }); + }); +} diff --git a/test/helper/extensions/string_extension_test.dart b/test/helper/extensions/string_extension_test.dart new file mode 100644 index 0000000..f33935f --- /dev/null +++ b/test/helper/extensions/string_extension_test.dart @@ -0,0 +1,336 @@ +import 'package:ez_ticketz_app/helper/extensions/string_extension.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + group('StringExt', () { + group('isValidEmail', () { + test( + 'GIVEN a valid email ' + 'WHEN extension method `.isValidEmail is called ' + 'THEN true is returned', + () { + //given + const email = 'test.user@domain.com'; + + //when + final isValid = email.isValidEmail; + + //then + expect(isValid, true); + }, + ); + test( + 'GIVEN an invalid email ' + 'WHEN extension method `.isValidEmail is called ' + 'THEN false is returned', + () { + //given + var email = 'test@user@domain.com'; + + //when + var isValid = email.isValidEmail; + + //then + expect(isValid, false); + + //given + email = 'user@domain'; + + //when + isValid = email.isValidEmail; + + //then + expect(isValid, false); + }, + ); + }); + + group('isValidFullName', () { + test( + 'GIVEN a valid full name ' + 'WHEN extension method `.isValidFullName is called ' + 'THEN true is returned', + () { + //given + const fullName = 'Test User'; + + //when + final isValid = fullName.isValidFullName; + + //then + expect(isValid, true); + }, + ); + test( + 'GIVEN an invalid full name ' + 'WHEN extension method `.isValidFullName is called ' + 'THEN false is returned', + () { + //given + var fullName = 'Test2User'; + + //when + var isValid = fullName.isValidFullName; + + //then + expect(isValid, false); + + //given + fullName = 'Test @- User'; + + //when + isValid = fullName.isValidFullName; + + //then + expect(isValid, false); + }, + ); + }); + + group('isValidContact', () { + test( + 'GIVEN a valid contact ' + 'WHEN extension method `.isValidContact is called ' + 'THEN true is returned', + () { + //given + var contact = '03001234567'; + + //when + var isValid = contact.isValidContact; + + //then + expect(isValid, true); + + //given + contact = '3001234567'; + + //when + isValid = contact.isValidContact; + + //then + expect(isValid, true); + }, + ); + test( + 'GIVEN an invalid contact ' + 'WHEN extension method `.isValidContact is called ' + 'THEN false is returned', + () { + //given + var contact = '1234'; + + //when + var isValid = contact.isValidContact; + + //then + expect(isValid, false); + + //given + contact = '02001234567'; + + //when + isValid = contact.isValidContact; + + //then + expect(isValid, false); + + //given + contact = 'abc'; + + //when + isValid = contact.isValidContact; + + //then + expect(isValid, false); + }, + ); + }); + + group('isValidZipCode', () { + test( + 'GIVEN a valid zip code ' + 'WHEN extension method `.isValidZipCode is called ' + 'THEN true is returned', + () { + //given + const zipCode = '75400'; + + //when + final isValid = zipCode.isValidZipCode; + + //then + expect(isValid, true); + }, + ); + test( + 'GIVEN an invalid zip code ' + 'WHEN extension method `.isValidZipCode is called ' + 'THEN false is returned', + () { + //given + var zipCode = '123'; + + //when + var isValid = zipCode.isValidZipCode; + + //then + expect(isValid, false); + + //given + zipCode = 'abc'; + + //when + isValid = zipCode.isValidZipCode; + + //then + expect(isValid, false); + }, + ); + }); + + group('isValidCreditCardNumber', () { + test( + 'GIVEN a valid credit card number ' + 'WHEN extension method `.isValidCreditCardNumber is called ' + 'THEN true is returned', + () { + //given + var creditCardNumber = '5555555555550000'; + + //when + var isValid = creditCardNumber.isValidCreditCardNumber; + + //then + expect(isValid, true); + + //given + creditCardNumber = '4555555555550000'; + + //when + isValid = creditCardNumber.isValidCreditCardNumber; + + //then + expect(isValid, true); + }, + ); + test( + 'GIVEN an invalid credit card number ' + 'WHEN extension method `.isValidCreditCardNumber is called ' + 'THEN false is returned', + () { + //given + var creditCardNumber = '1234567890000000'; + + //when + var isValid = creditCardNumber.isValidCreditCardNumber; + + //then + expect(isValid, false); + + //given + creditCardNumber = 'asd'; + + //when + isValid = creditCardNumber.isValidCreditCardNumber; + + //then + expect(isValid, false); + }, + ); + }); + + group('isValidCreditCardCVV', () { + test( + 'GIVEN a valid credit card cvv ' + 'WHEN extension method `.isValidCreditCardCVV is called ' + 'THEN true is returned', + () { + //given + var creditCardCVV = '178'; + + //when + var isValid = creditCardCVV.isValidCreditCardCVV; + + //then + expect(isValid, true); + }, + ); + test( + 'GIVEN an invalid credit card cvv ' + 'WHEN extension method `.isValidCreditCardCVV is called ' + 'THEN false is returned', + () { + //given + var creditCardCVV = '1'; + + //when + var isValid = creditCardCVV.isValidCreditCardCVV; + + //then + expect(isValid, false); + + //given + creditCardCVV = 'asd'; + + //when + isValid = creditCardCVV.isValidCreditCardCVV; + + //then + expect(isValid, false); + }, + ); + }); + + group('isValidCreditCardExpiry', () { + test( + 'GIVEN a valid credit card expiry ' + 'WHEN extension method `.isValidCreditCardExpiry is called ' + 'THEN true is returned', + () { + //given + const creditCardExpiry = '09/2021'; + + //when + final isValid = creditCardExpiry.isValidCreditCardExpiry; + + //then + expect(isValid, true); + }, + ); + test( + 'GIVEN an invalid credit card expiry ' + 'WHEN extension method `.isValidCreditCardExpiry is called ' + 'THEN false is returned', + () { + //given + var creditCardExpiry = '123'; + + //when + var isValid = creditCardExpiry.isValidCreditCardExpiry; + + //then + expect(isValid, false); + + //given + creditCardExpiry = '09-2021'; + + //when + isValid = creditCardExpiry.isValidCreditCardExpiry; + + //then + expect(isValid, false); + + //given + creditCardExpiry = '00/1900'; + + //when + isValid = creditCardExpiry.isValidCreditCardExpiry; + + //then + expect(isValid, false); + }, + ); + }); + }); +}