Skip to content

Commit

Permalink
Release 17/08/2021 (#78)
Browse files Browse the repository at this point in the history
Release 17/08/2021
arafaysaleem authored Aug 17, 2021
2 parents 094bad5 + 9a045e0 commit a6a4245
Showing 98 changed files with 5,529 additions and 2,045 deletions.
43 changes: 43 additions & 0 deletions .github/workflows/PR-generate-goldens.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
name: Generate Updated Goldens
on:
push:
branches-ignore: [master, release, dev] # only run on feature branches
paths:
- '**/golden_tests/**.dart'

workflow_dispatch:

jobs:
generate-goldens:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
with:
persist-credentials: false # otherwise, the token used is the GITHUB_TOKEN, instead of your personal access token.
fetch-depth: 0
- name: Setup Java JDK
uses: actions/setup-java@v2.2.0
with:
distribution: 'adopt'
java-version: '12.x'
- name: Checkout Flutter Stable Channel
uses: subosito/flutter-action@v1.5.3
with:
channel: 'stable'
- name: Get Pub Dependencies
run: flutter pub get
- name: Run Build Runner For Codegen Files
run: flutter packages pub run build_runner build --delete-conflicting-outputs
- name: Run tests
run: flutter test --update-goldens test/golden_tests
- name: Commit Updated Goldens
run: |
git config --global user.name 'arafaysaleem'
git config --global user.email 'arafaysaleem@users.noreply.github.com'
git add -A
git commit -m "test(Goldens): update generated goldens for new changes"
- name: GitHub Push To Repository
uses: ad-m/github-push-action@v0.6.0
with:
github_token: ${{ secrets.EZ_TICKETS_APP_TOKEN }}
branch: ${{ github.ref }}
6 changes: 4 additions & 2 deletions .github/workflows/PR-merge-build-release.yaml
Original file line number Diff line number Diff line change
@@ -15,11 +15,13 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-java@v1
- name: Setup Java JDK
uses: actions/setup-java@v2.2.0
with:
distribution: 'adopt'
java-version: '12.x'
- name: Checkout Flutter Stable Channel
uses: subosito/flutter-action@v1.5.1
uses: subosito/flutter-action@v1.5.3
with:
channel: 'stable'
- name: Get Pub Dependencies
14 changes: 11 additions & 3 deletions .github/workflows/PR-open-test-build.yaml
Original file line number Diff line number Diff line change
@@ -7,12 +7,20 @@ jobs:
name: Test APK
runs-on: ubuntu-latest
steps:
- name: Wait For Generate Goldens Workflow To Complete
uses: fountainhead/action-wait-for-check@v1.0.0
with:
token: ${{ secrets.GITHUB_TOKEN }}
checkName: generate-goldens
ref: ${{ github.event.pull_request.head.sha || github.sha }}
- uses: actions/checkout@v2
- uses: actions/setup-java@v1
- name: Setup Java JDK
uses: actions/setup-java@v2.2.0
with:
distribution: 'adopt'
java-version: '12.x'
- name: Checkout Flutter Stable Channel
uses: subosito/flutter-action@v1
uses: subosito/flutter-action@v1.5.3
with:
channel: 'stable'
- name: Get Pub Dependencies
@@ -30,7 +38,7 @@ jobs:
with:
path: "./coverage/lcov.info"
min_coverage: 4.5
exclude: "**/*.freezed.dart **/*.g.dart **/*.gr.dart **/*.mocks.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 **/routes.dart"
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v2.0.2
with:
8 changes: 7 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -45,11 +45,17 @@ app.*.map.json
/android/app/profile
/android/app/release

# Generated model files
# Generated files
*.g.dart
*.freezed.dart
*.gr.dart
*.mocks.dart

#keystore
*.jks

# don't check in golden failure output
**/failures/*.png

# don't check in goldens since we are using Windows and CI uses Ubuntu
**/goldens_local/*.png
8 changes: 5 additions & 3 deletions analysis_options.yaml
Original file line number Diff line number Diff line change
@@ -9,9 +9,8 @@ analyzer:
errors:
missing_required_param: error
missing_return: error
prefer_const_constructors: error
prefer_const_constructors_in_immutables: error
todo: ignore
invalid_annotation_target: ignore
strong-mode:
implicit-casts: false
implicit-dynamic: false
@@ -22,10 +21,13 @@ linter:
directives_ordering: false
prefer_double_quotes: false
use_key_in_widget_constructors: false
prefer_const_constructors: true
prefer_const_constructors_in_immutables: true
always_specify_types: false
unnecessary_final: false
public_member_api_docs: false
prefer_expression_function_bodies: false
avoid_classes_with_only_static_members: false
prefer_const_literals_to_create_immutables: true
lines_longer_than_80_chars: false
lines_longer_than_80_chars: false
prefer_relative_imports: true
4 changes: 0 additions & 4 deletions build.yaml
Original file line number Diff line number Diff line change
@@ -1,10 +1,6 @@
targets:
$default:
builders:
auto_route_generator:autoRouteGenerator:
generate_for:
include:
- lib/routes/**.dart
json_serializable:
options:
explicit_to_json: true
1 change: 1 addition & 0 deletions codecov.yml
Original file line number Diff line number Diff line change
@@ -4,6 +4,7 @@ ignore:
- '**/*.freezed.dart'
- "**/*.mocks.dart"
- '**/constants.dart'
- '**/routes.dart'
- '**/custom_theme.dart'
- '**/assets_helper.dart'
- '/android/'
5,462 changes: 3,714 additions & 1,748 deletions coverage/lcov.info

Large diffs are not rendered by default.

Binary file added google_fonts/Lato-Bold.ttf
Binary file not shown.
Binary file added google_fonts/Lato-Light.ttf
Binary file not shown.
Binary file added google_fonts/Lato-Regular.ttf
Binary file not shown.
Binary file added google_fonts/Poppins-Bold.ttf
Binary file not shown.
Binary file added google_fonts/Poppins-ExtraLight.ttf
Binary file not shown.
Binary file added google_fonts/Poppins-Light.ttf
Binary file not shown.
Binary file added google_fonts/Poppins-Regular.ttf
Binary file not shown.
Binary file added google_fonts/Poppins-SemiBold.ttf
Binary file not shown.
17 changes: 10 additions & 7 deletions lib/helper/extensions/string_extension.dart
Original file line number Diff line number Diff line change
@@ -3,26 +3,29 @@ import '../utils/constants.dart';

/// A utility with extensions for strings.
extension StringExt on String {
/// An extension for validating String as an email.
/// An extension for validating String is an email.
bool get isValidEmail => Constants.emailRegex.hasMatch(this);

/// An extension for validating String as a full name.
/// An extension for validating String is a full name.
bool get isValidFullName => Constants.fullNameRegex.hasMatch(this);

/// An extension for validating String as a contact.
/// An extension for validating String is a contact.
bool get isValidContact => Constants.contactRegex.hasMatch(this);

/// An extension for validating String as a zipcode.
/// An extension for validating String is a zipcode.
bool get isValidZipCode => Constants.zipCodeRegex.hasMatch(this);

/// An extension for validating String as a credit card number.
/// An extension for validating String is a credit card number.
bool get isValidCreditCardNumber => Constants.creditCardNumberRegex.hasMatch(this);

/// An extension for validating String as a credit card CVV.
/// An extension for validating String is a credit card CVV.
bool get isValidCreditCardCVV => Constants.creditCardCVVRegex.hasMatch(this);

/// An extension for validating String as a credit card expiry.
/// An extension for validating String is a credit card expiry.
bool get isValidCreditCardExpiry => Constants.creditCardExpiryRegex.hasMatch(this);

/// An extension for validating String is a valid OTP digit
bool get isValidOtpDigit => Constants.otpDigitRegex.hasMatch(this);

/// An extension for converting String to Capitalcase.
String get capitalize => this[0].toUpperCase() + this.substring(1).toLowerCase();
7 changes: 6 additions & 1 deletion lib/helper/typedefs.dart
Original file line number Diff line number Diff line change
@@ -1,2 +1,7 @@
import 'package:hooks_riverpod/hooks_riverpod.dart';
import '../providers/states/future_state.dart';

typedef JSON = Map<String, dynamic>;
typedef QueryParams = Map<String, String>;
typedef QueryParams = Map<String, String>;
typedef StateListener<T> = ProviderListener<StateController<T>>;
typedef FutureStateListener<T> = ProviderListener<StateController<FutureState<T>>>;
3 changes: 3 additions & 0 deletions lib/helper/utils/constants.dart
Original file line number Diff line number Diff line change
@@ -142,6 +142,9 @@ class Constants {
/// The regular expression for validating credit card expiry in the app.
static RegExp creditCardExpiryRegex = RegExp(r'(0[1-9]|10|11|12)/20[0-9]{2}$');

/// The regular expression for validating credit card expiry in the app.
static final RegExp otpDigitRegex = RegExp('^[0-9]{1}\$');

/// The error message for invalid email input.
static const invalidEmailError = 'Please enter a valid email address';

2 changes: 1 addition & 1 deletion lib/helper/utils/custom_theme.dart
Original file line number Diff line number Diff line change
@@ -38,7 +38,7 @@ class CustomTheme {
height: 1.15,
),
headline4: Constants.latoFont.copyWith(
fontWeight: FontWeight.w500,
fontWeight: FontWeight.w400,
fontSize: 26,
height: 1.15,
),
6 changes: 6 additions & 0 deletions lib/helper/utils/form_validator.dart
Original file line number Diff line number Diff line change
@@ -104,4 +104,10 @@ class FormValidator{
return Constants.invalidCreditCardExpiryError;
}

/// A method containing validation logic for single otp digit input.
static String? otpDigitValidator(String? digit){
if (digit != null && digit.isValidOtpDigit) return null;
return '!';
}

}
12 changes: 6 additions & 6 deletions lib/main.dart
Original file line number Diff line number Diff line change
@@ -7,8 +7,8 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
//Helper
import 'helper/utils/custom_theme.dart';

//Router
import 'routes/app_router.gr.dart';
//Routers
import 'routes/app_router.dart';

//Services
import 'services/local_storage/key_value_storage_base.dart';
@@ -36,17 +36,17 @@ void setDebugPrint(String? message, {int? wrapWidth}) {
}

class MyApp extends StatelessWidget {
final _appRouter = AppRouter();

@override
Widget build(BuildContext context) {
return ProviderScope(
child: MaterialApp.router(
routerDelegate: _appRouter.delegate(),
routeInformationParser: _appRouter.defaultRouteParser(),
child: MaterialApp(
debugShowCheckedModeBanner: false,
title: 'EZ Tickets',
theme: CustomTheme.mainTheme,
initialRoute: AppRouter.initialRoute,
onGenerateRoute: AppRouter.generateRoute,
navigatorKey: AppRouter.navigatorKey,
),
);
}
1 change: 0 additions & 1 deletion lib/models/booking_model.dart
Original file line number Diff line number Diff line change
@@ -12,7 +12,6 @@ part 'booking_model.g.dart';
class BookingModel with _$BookingModel {
const BookingModel._();

@JsonSerializable()
const factory BookingModel({
@JsonKey(toJson: Constants.toNull, includeIfNull: false) required int? bookingId,
@JsonKey(includeIfNull: false) required int? userId,
1 change: 0 additions & 1 deletion lib/models/genre_model.dart
Original file line number Diff line number Diff line change
@@ -7,7 +7,6 @@ part 'genre_model.g.dart';

@freezed
class GenreModel with _$GenreModel {
@JsonSerializable()
const factory GenreModel({
required int genreId,
required String genre,
1 change: 0 additions & 1 deletion lib/models/movie_model.dart
Original file line number Diff line number Diff line change
@@ -17,7 +17,6 @@ List<int> toJsonGenres(List<GenreModel> genres) {
class MovieModel with _$MovieModel {
MovieModel._();

@JsonSerializable()
factory MovieModel({
@JsonKey(toJson: Constants.toNull, includeIfNull: false) required int? movieId,
required int year,
1 change: 0 additions & 1 deletion lib/models/movie_role_model.dart
Original file line number Diff line number Diff line change
@@ -14,7 +14,6 @@ part 'movie_role_model.g.dart';
class MovieRoleModel with _$MovieRoleModel {
const MovieRoleModel._();

@JsonSerializable()
const factory MovieRoleModel({
required int movieId,
required RoleModel role,
1 change: 0 additions & 1 deletion lib/models/payment_model.dart
Original file line number Diff line number Diff line change
@@ -12,7 +12,6 @@ part 'payment_model.g.dart';
class PaymentModel with _$PaymentModel {
const PaymentModel._();

@JsonSerializable()
const factory PaymentModel({
@JsonKey(toJson: Constants.toNull, includeIfNull: false) required int? paymentId,
required double amount,
1 change: 0 additions & 1 deletion lib/models/role_model.dart
Original file line number Diff line number Diff line change
@@ -8,7 +8,6 @@ part 'role_model.g.dart';
@freezed
class RoleModel with _$RoleModel {

@JsonSerializable()
const factory RoleModel({
required int roleId,
required String fullName,
1 change: 0 additions & 1 deletion lib/models/seat_model.dart
Original file line number Diff line number Diff line change
@@ -7,7 +7,6 @@ part 'seat_model.g.dart';

@freezed
class SeatModel with _$SeatModel {
@JsonSerializable()
const factory SeatModel({
required String seatRow,
required int seatNumber,
1 change: 0 additions & 1 deletion lib/models/show_model.dart
Original file line number Diff line number Diff line change
@@ -11,7 +11,6 @@ part 'show_model.g.dart';
class ShowModel with _$ShowModel {
const ShowModel._();

@JsonSerializable()
const factory ShowModel({
required DateTime date,
required int movieId,
1 change: 0 additions & 1 deletion lib/models/show_time_model.dart
Original file line number Diff line number Diff line change
@@ -13,7 +13,6 @@ part 'show_time_model.g.dart';
class ShowTimeModel with _$ShowTimeModel {
const ShowTimeModel._();

@JsonSerializable()
const factory ShowTimeModel({
@JsonKey(toJson: Constants.toNull, includeIfNull: false)
@Default(0) int showId,
1 change: 0 additions & 1 deletion lib/models/theater_model.dart
Original file line number Diff line number Diff line change
@@ -13,7 +13,6 @@ part 'theater_model.g.dart';
class TheaterModel with _$TheaterModel {
const TheaterModel._();

@JsonSerializable()
const factory TheaterModel({
@JsonKey(toJson: Constants.toNull, includeIfNull: false) required int? theaterId,
required String theaterName,
1 change: 0 additions & 1 deletion lib/models/user_booking_model.dart
Original file line number Diff line number Diff line change
@@ -9,7 +9,6 @@ part 'user_booking_model.g.dart';

@freezed
class UserBookingModel with _$UserBookingModel {
@JsonSerializable()
const factory UserBookingModel({
required String title,
required String posterUrl,
1 change: 0 additions & 1 deletion lib/models/user_booking_show_model.dart
Original file line number Diff line number Diff line change
@@ -8,7 +8,6 @@ part 'user_booking_show_model.g.dart';

@freezed
class UserBookingShowModel with _$UserBookingShowModel {
@JsonSerializable(fieldRename: FieldRename.snake)
const factory UserBookingShowModel({
required int showId,
required ShowType showType,
1 change: 0 additions & 1 deletion lib/models/user_model.dart
Original file line number Diff line number Diff line change
@@ -9,7 +9,6 @@ part 'user_model.g.dart';
@freezed
class UserModel with _$UserModel {

@JsonSerializable()
const factory UserModel({
@JsonKey(includeIfNull: false) required int? userId,
required String fullName,
2 changes: 0 additions & 2 deletions lib/models/user_payment_model.dart
Original file line number Diff line number Diff line change
@@ -8,7 +8,6 @@ part 'user_payment_model.g.dart';

@freezed
class UserPaymentModel with _$UserPaymentModel {
@JsonSerializable()
const factory UserPaymentModel({
required int paymentId,
required double amount,
@@ -23,7 +22,6 @@ class UserPaymentModel with _$UserPaymentModel {
@freezed
@visibleForTesting
class UserPaymentMovieModel with _$UserPaymentMovieModel {
@JsonSerializable()
const factory UserPaymentMovieModel({
required String title,
required String posterUrl,
17 changes: 14 additions & 3 deletions lib/providers/all_providers.dart
Original file line number Diff line number Diff line change
@@ -3,10 +3,10 @@ import 'package:flutter/foundation.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';

//Service imports
import '../services/networking/dio_service.dart';
import '../services/local_storage/key_value_storage_service.dart';
import '../services/networking/api_service.dart';
import '../services/networking/api_endpoint.dart';
import '../services/networking/api_service.dart';
import '../services/networking/dio_service.dart';

//Interceptor imports
import '../services/networking/interceptors/api_interceptor.dart';
@@ -24,13 +24,15 @@ import '../services/repositories/theaters_repository.dart';
//Provider imports
import 'auth_provider.dart';
import 'bookings_provider.dart';
import 'forgot_password_provider.dart';
import 'movies_provider.dart';
import 'payments_provider.dart';
import 'shows_provider.dart';
import 'theaters_provider.dart';

//State imports
import 'states/auth_state.dart';
import 'theaters_provider.dart';
import 'states/forgot_password_state.dart';

//Services
final keyValueStorageServiceProvider = Provider<KeyValueStorageService>(
@@ -104,6 +106,15 @@ final authProvider = StateNotifierProvider<AuthProvider, AuthState>((ref) {
);
});

final forgotPasswordProvider = StateNotifierProvider.autoDispose<
ForgotPasswordProvider, ForgotPasswordState>((ref) {
final _authRepository = ref.watch(_authRepositoryProvider);
return ForgotPasswordProvider(
authRepository: _authRepository,
initialState: const ForgotPasswordState.email(),
);
});

//data providers
final moviesProvider = Provider<MoviesProvider>((ref) {
final _moviesRepository = ref.watch(_moviesRepositoryProvider);
25 changes: 1 addition & 24 deletions lib/providers/auth_provider.dart
Original file line number Diff line number Diff line change
@@ -10,9 +10,9 @@ import '../models/user_model.dart';
import '../services/local_storage/key_value_storage_service.dart';
import '../services/networking/network_exception.dart';
import '../services/repositories/auth_repository.dart';
import 'states/auth_state.dart';

//States
import 'states/auth_state.dart';
import 'states/future_state.dart';

final changePasswordStateProvider = StateProvider(
@@ -120,21 +120,6 @@ class AuthProvider extends StateNotifier<AuthState> {
}
}

Future<String> forgotPassword(String email) async {
final data = {'email': email};
return await _authRepository.sendForgotPasswordData(data: data);
}

Future<bool> resetPassword({
required String email,
required String password,
}) async {
final data = {'email': email, 'password': password};
final result = await _authRepository.sendResetPasswordData(data: data);
if (result) _updatePassword(password);
return result;
}

Future<void> changePassword({required String newPassword}) async {
final data = {
'email': currentUserEmail,
@@ -152,14 +137,6 @@ class AuthProvider extends StateNotifier<AuthState> {
}
}

Future<bool> verifyOtp({required String email, required int otp}) async {
final data = {
'email': email,
'OTP': otp,
};
return await _authRepository.sendOtpData(data: data);
}

void _updateAuthProfile() {
_keyValueStorageService.setAuthState(state);
_keyValueStorageService.setAuthUser(_currentUser!);
82 changes: 82 additions & 0 deletions lib/providers/forgot_password_provider.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import 'package:hooks_riverpod/hooks_riverpod.dart';

//Services
import '../services/networking/network_exception.dart';

//Repositories
import '../services/repositories/auth_repository.dart';

//States
import 'states/forgot_password_state.dart';

class ForgotPasswordProvider extends StateNotifier<ForgotPasswordState> {
final _otpDigits = ['0', '0', '0', '0'];
final AuthRepository _authRepository;
String? _email;

ForgotPasswordProvider({
required AuthRepository authRepository,
required ForgotPasswordState initialState,
})
: _authRepository = authRepository, super(initialState);

String get _otpCode => _otpDigits.fold('', (otp, digit) => '$otp$digit');

void setOtpDigit(int i, String digit) {
_otpDigits[i] = digit;
}

Future<void> requestOtpCode(String email) async {
final lastState = state;
state = const ForgotPasswordState.loading(loading: 'Verifying user email');
try {
final data = {'email': email};
final result = await _authRepository.sendForgotPasswordData(data: data);
_email = email;
state = ForgotPasswordState.otp(otpSentMessage: result);
} on NetworkException catch (e) {
state = ForgotPasswordState.failed(
reason: e.message,
lastState: lastState,
);
}
}

Future<void> resendOtpCode() async => requestOtpCode(_email!);

Future<void> verifyOtp() async {
final lastState = state;
state = const ForgotPasswordState.loading(loading: 'Verifying otp code');
try {
final data = {'email': _email, 'OTP': _otpCode};
final result = await _authRepository.sendOtpData(data: data);
state = ForgotPasswordState.resetPassword(otpVerifiedMessage: result);
} on NetworkException catch (e) {
state = ForgotPasswordState.failed(
reason: e.message,
lastState: lastState,
);
}
}

Future<void> resetPassword({required String password}) async {
final lastState = state;
state = const ForgotPasswordState.loading(loading: 'Resetting password');
try {
final data = {'email': _email, 'password': password};
final result = await _authRepository.sendResetPasswordData(data: data);
state = ForgotPasswordState.success(
success: result,
);
} on NetworkException catch (e) {
state = ForgotPasswordState.failed(
reason: e.message,
lastState: lastState,
);
}
}

void retryForgotPassword(ForgotPasswordState lastState) {
state = lastState;
}
}
29 changes: 29 additions & 0 deletions lib/providers/states/forgot_password_state.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import 'package:freezed_annotation/freezed_annotation.dart';

part 'forgot_password_state.freezed.dart';

@freezed
class ForgotPasswordState with _$ForgotPasswordState {
const factory ForgotPasswordState.email() = FORGOT_PW_EMAIL;

const factory ForgotPasswordState.otp({
required String otpSentMessage,
}) = FORGOT_PW_OTP;

const factory ForgotPasswordState.resetPassword({
required String otpVerifiedMessage,
}) = FORGOT_PW_RESET_PASSWORD;

const factory ForgotPasswordState.loading({
required String loading,
}) = LOADING;

const factory ForgotPasswordState.failed({
required String reason,
required ForgotPasswordState lastState,
}) = FORGOT_PW_FAILED;

const factory ForgotPasswordState.success({
String? success,
}) = FORGOT_PW_SUCCESS;
}
185 changes: 160 additions & 25 deletions lib/routes/app_router.dart
Original file line number Diff line number Diff line change
@@ -1,35 +1,170 @@
import 'package:auto_route/annotations.dart';
// ignore_for_file: constant_identifier_names
import 'package:flutter/material.dart';

//Screens
import '../views/screens/app_startup_screen.dart';
import '../views/screens/change_password_screen.dart';
import '../views/screens/confirmation_screen.dart';
import '../views/screens/forgot_password_screen.dart';
import '../views/screens/home_screen.dart';
import '../views/screens/login_screen.dart';
import '../views/screens/register_screen.dart';
import '../views/screens/movies_screen.dart';
import '../views/screens/movie_details_screen.dart';
import '../views/screens/trailer_screen.dart';
import '../views/screens/movies_screen.dart';
import '../views/screens/payment_screen.dart';
import '../views/screens/register_screen.dart';
import '../views/screens/shows_screen.dart';
import '../views/screens/theater_screen.dart';
import '../views/screens/ticket_summary_screen.dart';
import '../views/screens/payment_screen.dart';
import '../views/screens/confirmation_screen.dart';
import '../views/screens/trailer_screen.dart';
import '../views/screens/user_bookings_screen.dart';
import '../views/screens/change_password_screen.dart';
import '../views/screens/welcome_screen.dart';

//Routes
import 'routes.dart';

/// A utility class provides basic methods for navigation.
/// This class has no constructor and all variables are `static`.
@immutable
class AppRouter {
const AppRouter._();

/// The global key used to access navigator without context
static final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();

/// The name of the route that loads on app startup
static const String initialRoute = Routes.AppStartupScreenRoute;

/// This method is used when the app is navigating using named routes.
///
/// It maps each route name to a specific screen route.
///
/// In case of unknown route name, it returns a route indicating error.
static Route<dynamic>? generateRoute(RouteSettings settings) {
// final args = settings.arguments;
switch (settings.name) {
case Routes.AppStartupScreenRoute:
return MaterialPageRoute<dynamic>(
builder: (_) => const AppStartupScreen(),
settings: const RouteSettings(name: Routes.AppStartupScreenRoute),
);
case Routes.HomeScreenRoute:
return MaterialPageRoute<dynamic>(
builder: (_) => const HomeScreen(),
settings: const RouteSettings(name: Routes.HomeScreenRoute),
);
case Routes.LoginScreenRoute:
return MaterialPageRoute<dynamic>(
builder: (_) => const LoginScreen(),
settings: const RouteSettings(name: Routes.LoginScreenRoute),
);
case Routes.RegisterScreenRoute:
return MaterialPageRoute<dynamic>(
builder: (_) => const RegisterScreen(),
settings: const RouteSettings(name: Routes.RegisterScreenRoute),
);
case Routes.ForgotPasswordScreenRoute:
return MaterialPageRoute<dynamic>(
builder: (_) => const ForgotPasswordScreen(),
settings: const RouteSettings(name: Routes.ForgotPasswordScreenRoute),
);
case Routes.WelcomeScreenRoute:
return MaterialPageRoute<dynamic>(
builder: (_) => const WelcomeScreen(),
settings: const RouteSettings(name: Routes.WelcomeScreenRoute),
);
case Routes.ChangePasswordScreenRoute:
return MaterialPageRoute<dynamic>(
builder: (_) => const ChangePasswordScreen(),
settings: const RouteSettings(name: Routes.ChangePasswordScreenRoute),
);
case Routes.UserBookingsScreenRoute:
return MaterialPageRoute<dynamic>(
builder: (_) => const UserBookingsScreen(),
settings: const RouteSettings(name: Routes.UserBookingsScreenRoute),
);
case Routes.MoviesScreenRoute:
return MaterialPageRoute<dynamic>(
builder: (_) => const MoviesScreen(),
settings: const RouteSettings(name: Routes.MoviesScreenRoute),
);
case Routes.MovieDetailsScreenRoute:
return MaterialPageRoute<dynamic>(
builder: (_) => const MovieDetailsScreen(),
settings: const RouteSettings(name: Routes.MovieDetailsScreenRoute),
);
case Routes.TrailerScreenRoute:
return MaterialPageRoute<dynamic>(
builder: (_) => const TrailerScreen(),
settings: const RouteSettings(name: Routes.TrailerScreenRoute),
);
case Routes.ShowsScreenRoute:
return MaterialPageRoute<dynamic>(
builder: (_) => const ShowsScreen(),
settings: const RouteSettings(name: Routes.ShowsScreenRoute),
);
case Routes.TheaterScreenRoute:
return MaterialPageRoute<dynamic>(
builder: (_) => const TheaterScreen(),
settings: const RouteSettings(name: Routes.TheaterScreenRoute),
);
case Routes.TicketSummaryScreenRoute:
return MaterialPageRoute<dynamic>(
builder: (_) => const TicketSummaryScreen(),
settings: const RouteSettings(name: Routes.TicketSummaryScreenRoute),
);
case Routes.PaymentScreenRoute:
return MaterialPageRoute<dynamic>(
builder: (_) => const PaymentScreen(),
settings: const RouteSettings(name: Routes.PaymentScreenRoute),
);
case Routes.ConfirmationScreenRoute:
return MaterialPageRoute<dynamic>(
builder: (_) => const ConfirmationScreen(),
settings: const RouteSettings(name: Routes.ConfirmationScreenRoute),
);
default:
return _errorRoute();
}
}

/// This method returns an error page to indicate redirection to an
/// unknown route.
static Route<dynamic> _errorRoute() {
return MaterialPageRoute<dynamic>(
builder: (_) => Scaffold(
appBar: AppBar(
title: const Text('Unknown Route'),
),
body: const Center(
child: Text('Unknown Route'),
),
),
);
}

/// This method is used to navigate to a screen using it's name
static Future<dynamic> pushNamed(String routeName, {dynamic args}) {
return navigatorKey.currentState!.pushNamed(routeName, arguments: args);
}

/// This method is used to navigate back to the previous screen.
///
/// The [result] can contain any value that we want to return to the previous
/// screen.
static Future<void> pop([dynamic result]) async {
navigatorKey.currentState!.pop(result);
}

/// This method is used to navigate all the way back to a specific screen.
///
/// The [routeName] is the name of the screen we want to go back to.
static void popUntil(String routeName) {
navigatorKey.currentState!.popUntil(ModalRoute.withName(routeName));
}

@MaterialAutoRouter(
routes: <AutoRoute>[
AutoRoute<Widget>(page: AppStartupScreen, initial: true),
AutoRoute<Widget>(page: RegisterScreen),
AutoRoute<Widget>(page: LoginScreen),
AutoRoute<Widget>(page: MoviesScreen),
AutoRoute<Widget>(page: MovieDetailsScreen),
AutoRoute<Widget>(page: TrailerScreen),
AutoRoute<Widget>(page: ShowsScreen),
AutoRoute<Widget>(page: TheaterScreen),
AutoRoute<Widget>(page: TicketSummaryScreen),
AutoRoute<Widget>(page: PaymentScreen),
AutoRoute<Widget>(page: ConfirmationScreen),
AutoRoute<Widget>(page: UserBookingsScreen),
AutoRoute<Widget>(page: ChangePasswordScreen),
],
)
class $AppRouter{}
/// This method is used to navigate all the way back to the first screen
/// shown on startup i.e. the [initialRoute].
static void popUntilRoot() {
navigatorKey.currentState!.popUntil(ModalRoute.withName(initialRoute));
}
}
57 changes: 57 additions & 0 deletions lib/routes/routes.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
// ignore_for_file: constant_identifier_names
import 'package:flutter/material.dart';

/// A utility class that holds screen names for named navigation.
/// This class has no constructor and all variables are `static`.
@immutable
class Routes {
const Routes._();

/// The name of the route for app startup screen
static const String AppStartupScreenRoute = '/app-startup-screen';

/// The name of the route for home screen.
static const String HomeScreenRoute = '/home-screen';

/// The name of the route for login screen.
static const String LoginScreenRoute = '/login-screen';

/// The name of the route for home screen.
static const String RegisterScreenRoute = '/register-screen';

/// The name of the route for login screen.
static const String ForgotPasswordScreenRoute = '/forgot-password-screen';

/// The name of the route for home screen.
static const String WelcomeScreenRoute = '/welcome-screen';

/// The name of the route for login screen.
static const String ChangePasswordScreenRoute = '/change-password-screen';

/// The name of the route for user bookings screen.
static const String UserBookingsScreenRoute = '/user-bookings-screen';

/// The name of the route for movies screen.
static const String MoviesScreenRoute = '/movies-screen';

/// The name of the route for movie details screen.
static const String MovieDetailsScreenRoute = '/movie-details-screen';

/// The name of the route for trailer screen.
static const String TrailerScreenRoute = '/trailer-screen';

/// The name of the route for shows screen.
static const String ShowsScreenRoute = '/shows-screen';

/// The name of the route for theater map screen.
static const String TheaterScreenRoute = '/theater-screen';

/// The name of the route for ticket summary screen.
static const String TicketSummaryScreenRoute = '/ticket-summary-screen';

/// The name of the route for payment screen.
static const String PaymentScreenRoute = '/payment-screen';

/// The name of the route for payment/booking confirmation screen.
static const String ConfirmationScreenRoute = '/confirmation-screen';
}
4 changes: 3 additions & 1 deletion lib/services/networking/network_exception.dart
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
// 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';

//Helpers
import '../../helper/utils/exception_constants.dart';

part 'network_exception.freezed.dart';

@freezed
12 changes: 6 additions & 6 deletions lib/services/repositories/auth_repository.dart
Original file line number Diff line number Diff line change
@@ -55,14 +55,14 @@ class AuthRepository {
);
}

Future<bool> sendResetPasswordData({
Future<String> sendResetPasswordData({
required JSON data,
}) async {
return await _apiService.setData<bool>(
return await _apiService.setData<String>(
endpoint: ApiEndpoint.auth(AuthEndpoint.RESET_PASSWORD),
data: data,
requiresAuthToken: false,
converter: (response) => response['headers']['success'] == 1,
converter: (response) => response['headers']['message'] as String,
);
}

@@ -77,12 +77,12 @@ class AuthRepository {
);
}

Future<bool> sendOtpData({required JSON data}) async {
return await _apiService.setData<bool>(
Future<String> sendOtpData({required JSON data}) async {
return await _apiService.setData<String>(
endpoint: ApiEndpoint.auth(AuthEndpoint.VERIFY_OTP),
data: data,
requiresAuthToken: false,
converter: (response) => response['headers']['success'] == 1,
converter: (response) => response['headers']['message'] as String,
);
}
}
14 changes: 9 additions & 5 deletions lib/views/screens/change_password_screen.dart
Original file line number Diff line number Diff line change
@@ -5,11 +5,14 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
//Helpers
import '../../helper/extensions/context_extensions.dart';
import '../../helper/utils/constants.dart';
import '../../helper/typedefs.dart';

//Providers
import '../../providers/all_providers.dart';
import '../../providers/auth_provider.dart';
import '../../providers/states/future_state.dart';

//Routing
import '../../routes/app_router.dart';

//Widgets
import '../widgets/common/custom_dialog.dart';
@@ -28,10 +31,10 @@ class ChangePasswordScreen extends HookWidget {
final cNewPasswordController = useTextEditingController();
late final _formKey = useMemoized(() => GlobalKey<FormState>());
return Scaffold(
body: ProviderListener<StateController<FutureState<String>>>(
body: FutureStateListener<String>(
provider: changePasswordStateProvider,
onChange: (_, changePasswordStateController) async {
final changePasswordState = changePasswordStateController.state;
onChange: (_, controller) async {
final changePasswordState = controller.state;
changePasswordState.maybeWhen(
data: (message) async {
currentPasswordController.clear();
@@ -44,6 +47,7 @@ class ChangePasswordScreen extends HookWidget {
title: 'Change Password Success',
body: message,
buttonText: 'Okay',
onButtonPressed: () => AppRouter.pop(),
),
);
},
@@ -70,7 +74,7 @@ class ChangePasswordScreen extends HookWidget {
children: [
//Page name
Text(
'Your profile',
'Change password',
textAlign: TextAlign.center,
style: context.headline3.copyWith(fontSize: 22),
),
140 changes: 140 additions & 0 deletions lib/views/screens/forgot_password_screen.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';

//Helpers
import '../../helper/utils/constants.dart';

//Providers
import '../../providers/all_providers.dart';

//Routing
import '../../routes/app_router.dart';

//States
import '../../providers/states/forgot_password_state.dart';

//Widgets
import '../widgets/common/custom_dialog.dart';
import '../widgets/common/rounded_bottom_container.dart';
import '../widgets/common/scrollable_column.dart';
import '../widgets/forgot_password/forgot_button_widget.dart';
import '../widgets/forgot_password/forgot_message_widget.dart';
import '../widgets/forgot_password/forgot_name_widget.dart';
import '../widgets/forgot_password/forgot_resend_widget.dart';
import '../widgets/forgot_password/forgot_text_fields.dart';

class ForgotPasswordScreen extends HookWidget {
const ForgotPasswordScreen();

Future<bool> _showConfirmDialog(BuildContext context) async {
final doPop = await showDialog<bool>(
context: context,
barrierColor: Constants.barrierColor,
builder: (ctx) => const CustomDialog.confirm(
title: 'Are you sure?',
body: 'Do you want to go back without resetting your password?',
trueButtonText: 'Yes',
falseButtonText: 'No',
),
);
final popTheScreen = doPop != null && doPop;
return Future<bool>.value(popTheScreen);
}

@override
Widget build(BuildContext context) {
late final emailController = useTextEditingController();
late final newPasswordController = useTextEditingController();
late final cNewPasswordController = useTextEditingController();
late final _formKey = useMemoized(() => GlobalKey<FormState>());
return Scaffold(
body: ProviderListener<ForgotPasswordState>(
provider: forgotPasswordProvider,
onChange: (context, forgotPwState) async => forgotPwState.maybeWhen(
success: (_) async {
emailController.clear();
newPasswordController.clear();
cNewPasswordController.clear();
AppRouter.pop().then<bool?>((_) async {
return await showDialog<bool>(
context: context,
barrierColor: Constants.barrierColor.withOpacity(0.75),
builder: (ctx) => const CustomDialog.alert(
title: 'Password Reset Successful',
body: "In order to proceed, you'll have to login again with "
'your new password',
buttonText: 'Okay',
),
);
});
},
failed: (reason, lastState) async => await showDialog<bool>(
context: context,
barrierColor: Constants.barrierColor.withOpacity(0.75),
builder: (ctx) => CustomDialog.alert(
title: 'Password Reset Failure',
body: reason,
buttonText: 'Retry',
onButtonPressed: (){
final forgotProv = context.read(forgotPasswordProvider.notifier);
forgotProv.retryForgotPassword(lastState);
},
),
),
orElse: () {},
),
child: GestureDetector(
onTap: () => FocusScope.of(context).unfocus(),
child: ScrollableColumn(
children: [
//Input card
Form(
key: _formKey,
onWillPop: () => _showConfirmDialog(context),
child: RoundedBottomContainer(
padding: const EdgeInsets.fromLTRB(25.0, 28, 25.0, 20),
children: [
//Relevant Page Name
const ForgotNameWidget(),

const SizedBox(height: 20),

//Relevant Input Fields
ForgotTextFields(
emailController: emailController,
newPasswordController: newPasswordController,
cNewPasswordController: cNewPasswordController,
),

const SizedBox(height: 10),

//Resend message
const ForgotResendWidget(),
],
),
),

const SizedBox(height: 20),

//Relevant Response Message
const Padding(
padding: EdgeInsets.symmetric(horizontal: 25),
child: ForgotMessageWidget(),
),

const Spacer(),

//Reset Password Button
ForgotButtonWidget(
emailController: emailController,
newPasswordController: newPasswordController,
formKey: _formKey,
),
],
),
),
),
);
}
}
10 changes: 5 additions & 5 deletions lib/views/screens/home_screen.dart
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import 'package:auto_route/auto_route.dart';
import 'package:flutter/material.dart';

import '../../helper/utils/assets_helper.dart';
@@ -7,8 +6,9 @@ import '../../helper/utils/assets_helper.dart';
import '../../helper/utils/constants.dart';
import '../../helper/extensions/context_extensions.dart';

//Routes
import '../../routes/app_router.gr.dart';
//Routing
import '../../routes/routes.dart';
import '../../routes/app_router.dart';

//Widgets
import '../widgets/common/custom_text_button.dart';
@@ -62,7 +62,7 @@ class HomeScreen extends StatelessWidget {
child: CustomTextButton.gradient(
width: double.infinity,
onPressed: () {
context.router.push(const LoginScreenRoute());
AppRouter.pushNamed(Routes.LoginScreenRoute);
},
gradient: Constants.buttonGradientRed,
child: const Center(
@@ -100,7 +100,7 @@ class HomeScreen extends StatelessWidget {
CustomTextButton.outlined(
width: double.infinity,
onPressed: () {
context.router.push(const RegisterScreenRoute());
AppRouter.pushNamed(Routes.RegisterScreenRoute);
},
border: Border.all(color: Constants.primaryColor, width: 4),
child: const Center(
36 changes: 29 additions & 7 deletions lib/views/screens/login_screen.dart
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import 'package:auto_route/auto_route.dart';
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:flutter_spinkit/flutter_spinkit.dart';
@@ -12,6 +11,10 @@ import '../../helper/utils/form_validator.dart';
//Providers
import '../../providers/all_providers.dart';

//Routing
import '../../routes/routes.dart';
import '../../routes/app_router.dart';

//States
import '../../providers/states/auth_state.dart';

@@ -27,18 +30,17 @@ class LoginScreen extends HookWidget {

@override
Widget build(BuildContext context) {
final formKey = useMemoized(()=>GlobalKey<FormState>());
final formKey = useMemoized(() => GlobalKey<FormState>());
final emailController = useTextEditingController(text: '');
final passwordController = useTextEditingController(text: '');
return Scaffold(
body: ProviderListener(
body: ProviderListener<AuthState>(
provider: authProvider,
onChange: (context, authState) async =>
(authState as AuthState).maybeWhen(
onChange: (context, authState) async => authState.maybeWhen(
authenticated: (_) {
emailController.clear();
passwordController.clear();
context.router.popUntilRoot();
AppRouter.popUntilRoot();
},
failed: (reason) async {
await showDialog<bool>(
@@ -61,6 +63,7 @@ class LoginScreen extends HookWidget {
Form(
key: formKey,
child: RoundedBottomContainer(
padding: const EdgeInsets.fromLTRB(25.0, 28, 25.0, 20),
children: [
//Page name
Text(
@@ -76,7 +79,6 @@ class LoginScreen extends HookWidget {
//Email
CustomTextField(
controller: emailController,
autofocus: true,
floatingText: 'Email',
hintText: 'Type your email address',
keyboardType: TextInputType.emailAddress,
@@ -99,6 +101,26 @@ class LoginScreen extends HookWidget {
),
),

const SizedBox(height: 20),

Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
GestureDetector(
onTap: () {
AppRouter.pushNamed(Routes.ForgotPasswordScreenRoute);
},
child: Text(
'Forgot your password?',
style: context.headline3.copyWith(
fontSize: 17,
color: Constants.primaryColor,
),
),
),
],
),

const Spacer(),

//Login button
12 changes: 7 additions & 5 deletions lib/views/screens/movie_details_screen.dart
Original file line number Diff line number Diff line change
@@ -1,19 +1,21 @@
import 'package:flutter/material.dart';
import 'package:auto_route/auto_route.dart';

//Helper
import '../../helper/utils/constants.dart';
import '../../helper/extensions/context_extensions.dart';

//Routes
import '../../routes/app_router.gr.dart';
//Routing
import '../../routes/routes.dart';
import '../../routes/app_router.dart';

//Widgets
import '../widgets/movie_details/floating_movie_posters.dart';
import '../widgets/common/custom_text_button.dart';
import '../widgets/movie_details/movie_details_sheet.dart';

class MovieDetailsScreen extends StatelessWidget{
const MovieDetailsScreen({Key? key}) : super(key: key);

@override
Widget build(BuildContext context) {
return Scaffold(
@@ -45,7 +47,7 @@ class MovieDetailsScreen extends StatelessWidget{
),
),
onPressed: () {
context.router.push(const ShowsScreenRoute());
AppRouter.pushNamed(Routes.ShowsScreenRoute);
},
),
),
@@ -61,7 +63,7 @@ class MovieDetailsScreen extends StatelessWidget{
icon: const Icon(Icons.close_rounded, size: 25),
padding: const EdgeInsets.all(0),
onPressed: () {
context.router.pop();
AppRouter.pop();
},
),
),
6 changes: 4 additions & 2 deletions lib/views/screens/movies_screen.dart
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import 'package:auto_route/auto_route.dart';
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
@@ -11,6 +10,9 @@ import '../../helper/utils/constants.dart';
import '../../providers/all_providers.dart';
import '../../providers/movies_provider.dart';

//Routing
import '../../routes/app_router.dart';

//Skeletons
import '../skeletons/movies_skeleton_loader.dart';

@@ -100,7 +102,7 @@ class MoviesScreen extends HookWidget {
retryCallback: () => context.refresh(moviesFuture),
onError: () {
context.read(authProvider.notifier).logout();
context.router.popUntilRoot();
AppRouter.popUntilRoot();
},
),
),
6 changes: 4 additions & 2 deletions lib/views/screens/payment_screen.dart
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import 'package:auto_route/auto_route.dart';
import 'package:flutter/material.dart';

//Helpers
import '../../helper/utils/constants.dart';
import '../../helper/extensions/context_extensions.dart';

//Routing
import '../../routes/app_router.dart';

//Widgets
import '../widgets/common/scrollable_column.dart';
import '../widgets/payment/pay_button.dart';
@@ -90,7 +92,7 @@ class _BackIconRow extends StatelessWidget {
radius: 25,
child: const Icon(Icons.arrow_back_sharp, size: 26),
onTap: () {
context.router.pop();
AppRouter.pop();
},
),

11 changes: 6 additions & 5 deletions lib/views/screens/register_screen.dart
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import 'package:auto_route/auto_route.dart';
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:flutter_spinkit/flutter_spinkit.dart';
@@ -13,6 +12,9 @@ import '../../helper/utils/form_validator.dart';
//Providers
import '../../providers/all_providers.dart';

//Routing
import '../../routes/app_router.dart';

//States
import '../../providers/states/auth_state.dart';

@@ -178,13 +180,13 @@ class _RegisterScreenState extends State<RegisterScreen> {
cPasswordController.clear();
contactController.clear();
_formHasData = false;
context.router.popUntilRoot();
AppRouter.popUntilRoot();
}

return Scaffold(
body: ProviderListener(
body: ProviderListener<AuthState>(
provider: authProvider,
onChange: (_, authState) async => (authState as AuthState).maybeWhen(
onChange: (_, authState) async => authState.maybeWhen(
authenticated: onAuthStateAuthenticated,
failed: onAuthStateFailed,
orElse: () {},
@@ -280,7 +282,6 @@ class _UserDetailFields extends StatelessWidget {
//Full name
CustomTextField(
controller: fullNameController,
autofocus: true,
floatingText: 'Full name',
hintText: 'Type your full name',
keyboardType: TextInputType.name,
10 changes: 5 additions & 5 deletions lib/views/screens/shows_screen.dart
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import 'package:auto_route/auto_route.dart';
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
@@ -12,8 +11,9 @@ import '../../helper/utils/constants.dart';
import '../../providers/movies_provider.dart';
import '../../providers/shows_provider.dart';

//Routes
import '../../routes/app_router.gr.dart';
//Routing
import '../../routes/routes.dart';
import '../../routes/app_router.dart';

//Skeletons
import '../skeletons/shows_skeleton_loader.dart';
@@ -46,7 +46,7 @@ class ShowsScreen extends HookWidget {
radius: 25,
child: const Icon(Icons.arrow_back_sharp, size: 26),
onTap: () {
context.router.pop();
AppRouter.pop();
},
),

@@ -170,7 +170,7 @@ class ShowsScreen extends HookWidget {
width: double.infinity,
disabled: showStatus == ShowStatus.FULL,
onPressed: () {
context.router.push(const TheaterScreenRoute());
AppRouter.pushNamed(Routes.TheaterScreenRoute);
},
gradient: Constants.buttonGradientOrange,
child: const Center(
6 changes: 4 additions & 2 deletions lib/views/screens/theater_screen.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import 'dart:math';

import 'package:auto_route/auto_route.dart';
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
@@ -13,6 +12,9 @@ import '../../providers/all_providers.dart';
//Providers
import '../../providers/theaters_provider.dart';

//Routing
import '../../routes/app_router.dart';

//Skeletons
import '../skeletons/theater_skeleton_loader.dart';

@@ -160,7 +162,7 @@ class _BackIcon extends StatelessWidget {
radius: 25,
onTap: () {
context.read(theatersProvider).clearSelectedSeats();
context.router.pop();
AppRouter.pop();
},
child: const DecoratedBox(
decoration: BoxDecoration(
6 changes: 4 additions & 2 deletions lib/views/screens/ticket_summary_screen.dart
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import 'package:auto_route/auto_route.dart';
import 'package:flutter/material.dart';

//Helpers
import '../../helper/extensions/context_extensions.dart';

//Routing
import '../../routes/app_router.dart';

//Widgets
import '../widgets/ticket_summary/confirm_bookings_button.dart';
import '../widgets/ticket_summary/tickets_summary_box.dart';
@@ -52,7 +54,7 @@ class _BackIconRow extends StatelessWidget {
radius: 25,
child: const Icon(Icons.arrow_back_sharp, size: 26),
onTap: () {
context.router.pop();
AppRouter.pop();
},
),

6 changes: 4 additions & 2 deletions lib/views/screens/trailer_screen.dart
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import 'package:auto_route/auto_route.dart';
import 'package:better_player/better_player.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
@@ -8,6 +7,9 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../../helper/utils/constants.dart';
import '../../helper/extensions/context_extensions.dart';

//Routing
import '../../routes/app_router.dart';

//Providers
import '../../providers/movies_provider.dart';

@@ -79,7 +81,7 @@ class _TrailerScreenState extends State<TrailerScreen> {
GestureDetector(
child: const Icon(Icons.arrow_back_sharp, size: 26),
onTap: () {
context.router.pop();
AppRouter.pop();
},
),

6 changes: 4 additions & 2 deletions lib/views/screens/user_bookings_screen.dart
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import 'package:auto_route/auto_route.dart';
import 'package:flutter/material.dart';

//Helpers
import '../../helper/extensions/context_extensions.dart';

//Routing
import '../../routes/app_router.dart';

//Widgets
import '../widgets/user_bookings/user_bookings_history.dart';

@@ -27,7 +29,7 @@ class UserBookingsScreen extends StatelessWidget {
radius: 25,
child: const Icon(Icons.arrow_back_sharp, size: 26),
onTap: () {
context.router.pop();
AppRouter.pop();
},
),

10 changes: 5 additions & 5 deletions lib/views/screens/welcome_screen.dart
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import 'package:auto_route/auto_route.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
@@ -10,8 +9,9 @@ import '../../helper/utils/constants.dart';
//Providers
import '../../providers/all_providers.dart';

//Routes
import '../../routes/app_router.gr.dart';
//Routing
import '../../routes/routes.dart';
import '../../routes/app_router.dart';

//Widgets
import '../widgets/welcome/user_profile_details.dart';
@@ -48,7 +48,7 @@ class WelcomeScreen extends StatelessWidget {
),
onTap: () {
context.read(authProvider.notifier).logout();
context.router.popUntilRoot();
AppRouter.popUntilRoot();
},
),
),
@@ -62,7 +62,7 @@ class WelcomeScreen extends StatelessWidget {
size: 30,
),
onTap: () {
context.router.push(const ChangePasswordScreenRoute());
AppRouter.pushNamed(Routes.ChangePasswordScreenRoute);
},
)
],
25 changes: 21 additions & 4 deletions lib/views/widgets/common/custom_dialog.dart
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import 'package:flutter/material.dart';
import 'package:auto_route/auto_route.dart';

//Helpers
import '../../../helper/utils/constants.dart';
import '../../../helper/extensions/context_extensions.dart';

//Routing
import '../../../routes/app_router.dart';

//Widgets
import 'custom_text_button.dart';

@@ -15,11 +17,14 @@ class CustomDialog extends StatelessWidget {
final String title, body;
final String? buttonText, falseButtonText, trueButtonText;
final CustomDialogType _type;
final VoidCallback? falseButtonPressed, trueButtonPressed;

const CustomDialog._({
this.buttonText,
this.falseButtonText,
this.trueButtonText,
this.falseButtonPressed,
this.trueButtonPressed,
required this.title,
required this.body,
required CustomDialogType type,
@@ -29,13 +34,16 @@ class CustomDialog extends StatelessWidget {
required String title,
required String body,
required String buttonText,
VoidCallback? onButtonPressed,
}) = _CustomDialogWithAlert;

const factory CustomDialog.confirm({
required String title,
required String body,
required String falseButtonText,
required String trueButtonText,
VoidCallback? falseButtonPressed,
VoidCallback? trueButtonPressed,
}) = _CustomDialogWithConfirm;

@override
@@ -72,7 +80,8 @@ class CustomDialog extends StatelessWidget {
height: 40,
width: 60,
onPressed: () {
context.router.pop();
trueButtonPressed?.call();
AppRouter.pop();
},
)
else if (_type == CustomDialogType.CONFIRM) ...[
@@ -87,7 +96,8 @@ class CustomDialog extends StatelessWidget {
height: 40,
width: 60,
onPressed: () {
context.router.pop(true);
trueButtonPressed?.call();
AppRouter.pop(true);
},
),
CustomTextButton.gradient(
@@ -101,7 +111,8 @@ class CustomDialog extends StatelessWidget {
height: 40,
width: 60,
onPressed: () {
context.router.pop(false);
falseButtonPressed?.call();
AppRouter.pop(false);
},
),
]
@@ -115,10 +126,12 @@ class _CustomDialogWithAlert extends CustomDialog {
required String title,
required String body,
required String buttonText,
VoidCallback? onButtonPressed,
}) : super._(
title: title,
body: body,
buttonText: buttonText,
trueButtonPressed: onButtonPressed,
type: CustomDialogType.ALERT,
);
}
@@ -129,11 +142,15 @@ class _CustomDialogWithConfirm extends CustomDialog {
required String body,
required String falseButtonText,
required String trueButtonText,
VoidCallback? falseButtonPressed,
VoidCallback? trueButtonPressed,
}) : super._(
title: title,
body: body,
falseButtonText: falseButtonText,
trueButtonText: trueButtonText,
falseButtonPressed: falseButtonPressed,
trueButtonPressed: trueButtonPressed,
type: CustomDialogType.CONFIRM,
);
}
153 changes: 97 additions & 56 deletions lib/views/widgets/common/custom_textfield.dart
Original file line number Diff line number Diff line change
@@ -7,28 +7,44 @@ import '../../../helper/extensions/context_extensions.dart';
import '../../../helper/utils/constants.dart';

class CustomTextField extends StatefulWidget {
final String? floatingText, hintText;
final TextInputType keyboardType;
final TextInputAction textInputAction;
final TextEditingController controller;
final String? Function(String? value) validator;
final void Function(String? value)? onSaved;
final AlignmentGeometry errorTextAlign;
final Widget? prefix;
final bool autofocus;
final TextEditingController? controller;
final double? width, height;
final int? maxLength;
final String? floatingText, hintText;
final TextStyle hintStyle, errorStyle, inputStyle;
final TextStyle? floatingStyle;
final EdgeInsets? contentPadding;
final void Function(String? value)? onSaved, onChanged;
final Widget? prefix;
final bool showCursor;
final bool autofocus;
final bool showErrorBorder;
final TextAlign textAlign;
final Alignment errorAlign, floatingAlign;
final Color fillColor;
final TextInputType keyboardType;
final TextInputAction textInputAction;
final String? Function(String? value) validator;

const CustomTextField({
Key? key,
this.onSaved,
this.prefix,
this.controller,
this.width,
this.height = 47,
this.maxLength,
this.floatingText,
this.floatingStyle,
this.errorTextAlign = Alignment.centerRight,
this.onSaved,
this.onChanged,
this.prefix,
this.showCursor = true,
this.showErrorBorder = false,
this.autofocus = false,
this.textAlign = TextAlign.start,
this.errorAlign = Alignment.centerRight,
this.floatingAlign = Alignment.centerLeft,
this.fillColor = Constants.scaffoldColor,
this.hintText,
this.hintStyle = const TextStyle(
fontSize: 17,
color: Constants.textWhite80Color,
@@ -41,10 +57,7 @@ class CustomTextField extends StatefulWidget {
fontSize: 17,
color: Constants.textWhite80Color,
),
this.fillColor = Constants.scaffoldColor,
this.floatingText,
this.hintText,
required this.controller,
this.contentPadding = const EdgeInsets.fromLTRB(17, 10, 1, 10),
required this.keyboardType,
required this.textInputAction,
required this.validator,
@@ -58,32 +71,42 @@ class _CustomTextFieldState extends State<CustomTextField> {
String? errorText;
bool hidePassword = true;

bool get hasErrorText => errorText != null;
bool get hasError => errorText != null;

bool get showErrorBorder => widget.showErrorBorder && hasError;

bool get hasFloatingText => widget.floatingText != null;

bool get isPasswordField =>
widget.keyboardType == TextInputType.visiblePassword;

void _onSaved(String? value) {
value = value!.trim();
widget.controller.text = value;
if (widget.onSaved != null) widget.onSaved!(value);
widget.controller?.text = value;
widget.onSaved?.call(value);
}

void _onFieldSubmitted(String value) {
final error = widget.validator(value.trim());
setState(() {
errorText = error;
});
void _onChanged(String value) {
if(widget.onChanged != null){
_runValidator(value);
widget.onChanged!(value);
}
}

String? _onValidate(String? value) {
String? _runValidator(String? value) {
final error = widget.validator(value!.trim());
setState(() {
errorText = error;
});
return error;
}

void _togglePasswordVisibility() {
setState(() {
hidePassword = !hidePassword;
});
}

OutlineInputBorder _focusedBorder() {
return const OutlineInputBorder(
borderRadius: BorderRadius.all(Radius.circular(9)),
@@ -101,63 +124,79 @@ class _CustomTextFieldState extends State<CustomTextField> {
);
}

OutlineInputBorder _errorBorder() {
return const OutlineInputBorder(
borderRadius: BorderRadius.all(Radius.circular(9)),
borderSide: BorderSide(
color: Constants.redColor,
width: 1,
),
);
}

@override
Widget build(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
//Floating text
if (widget.floatingText != null)
Text(
widget.floatingText!,
style: widget.floatingStyle ??
context.bodyText1.copyWith(
color: Constants.textGreyColor,
fontSize: 17,
),
if (hasFloatingText) ...[
SizedBox(
width: widget.width,
child: Align(
alignment: widget.floatingAlign,
child: Text(
widget.floatingText!,
style: widget.floatingStyle ??
context.bodyText1.copyWith(
color: Constants.textGreyColor,
fontSize: 17,
),
),
),
),

if (widget.floatingText != null) const SizedBox(height: 2),
const SizedBox(height: 2),
],

//TextField
SizedBox(
height: 47,
height: widget.height,
width: widget.width,
child: TextFormField(
controller: widget.controller,
textAlign: widget.textAlign,
autofocus: widget.autofocus,
maxLength: widget.maxLength,
keyboardType: widget.keyboardType,
textInputAction: widget.textInputAction,
style: widget.inputStyle,
showCursor: widget.showCursor,
maxLengthEnforcement: MaxLengthEnforcement.enforced,
textAlignVertical: TextAlignVertical.center,
autovalidateMode: AutovalidateMode.disabled,
cursorColor: Colors.white,
obscureText: isPasswordField && hidePassword,
showCursor: true,
validator: _onValidate,
validator: _runValidator,
onFieldSubmitted: _runValidator,
onSaved: _onSaved,
onFieldSubmitted: _onFieldSubmitted,
onChanged: _onChanged,
decoration: InputDecoration(
hintText: widget.hintText,
hintStyle: widget.hintStyle,
errorStyle: widget.errorStyle,
fillColor: widget.fillColor,
prefixIcon: widget.prefix,
contentPadding: const EdgeInsets.fromLTRB(17, 10, 1, 10),
contentPadding: widget.contentPadding,
isDense: true,
filled: true,
counterText: '',
border: _normalBorder(),
focusedBorder: _focusedBorder(),
focusedErrorBorder: _focusedBorder(),
errorBorder: showErrorBorder ? _errorBorder() : null,
suffixIcon: isPasswordField
? InkWell(
onTap: () {
setState(() {
hidePassword = !hidePassword;
});
},
onTap: _togglePasswordVisibility,
child: const Icon(
Icons.remove_red_eye_sharp,
color: Constants.textGreyColor,
@@ -169,17 +208,19 @@ class _CustomTextFieldState extends State<CustomTextField> {
),
),

if (hasErrorText) ...[
//Error text
if (hasError) ...[
const SizedBox(height: 2),

//Error text
Align(
alignment: widget.errorTextAlign,
child: Text(
errorText!,
style: context.bodyText1.copyWith(
fontSize: 16,
color: Constants.primaryColor,
SizedBox(
width: widget.width,
child: Align(
alignment: widget.errorAlign,
child: Text(
errorText!,
style: context.bodyText1.copyWith(
fontSize: 16,
color: Constants.primaryColor,
),
),
),
),
4 changes: 2 additions & 2 deletions lib/views/widgets/common/error_response_handler.dart
Original file line number Diff line number Diff line change
@@ -40,7 +40,7 @@ class ErrorResponseHandler extends StatelessWidget {
height: context.screenHeight * 0.5,
);
}
if (onError != null) onError!();
onError?.call();
debugPrint(error.toString());
debugPrint(stackTrace?.toString());
return const SizedBox.shrink();
@@ -67,7 +67,7 @@ class _ErrorResponseHandlerWithBuilder extends ErrorResponseHandler {
@override
Widget build(BuildContext context) {
if (error is NetworkException) return builder(error as NetworkException);
if (onError != null) onError!();
onError?.call();
debugPrint(error.toString());
debugPrint(stackTrace?.toString());
return const SizedBox.shrink();
10 changes: 7 additions & 3 deletions lib/views/widgets/common/rounded_bottom_container.dart
Original file line number Diff line number Diff line change
@@ -1,17 +1,21 @@
import 'package:auto_route/auto_route.dart';
import 'package:flutter/material.dart';

//Routing
import '../../../routes/app_router.dart';

//Helpers
import '../../../helper/utils/constants.dart';

class RoundedBottomContainer extends StatelessWidget {
final List<Widget> children;
final VoidCallback? onBackTap;
final EdgeInsets? padding;

const RoundedBottomContainer({
Key? key,
required this.children,
this.onBackTap,
this.padding,
}) : super(key: key);

@override
@@ -39,11 +43,11 @@ class RoundedBottomContainer extends StatelessWidget {
color: Colors.white,
),
),
onTap: onBackTap ?? () => context.router.pop(),
onTap: onBackTap ?? () => AppRouter.pop(),
),

Padding(
padding: const EdgeInsets.fromLTRB(25.0, 28, 25.0, 27),
padding: padding ?? const EdgeInsets.fromLTRB(25.0, 28, 25.0, 27),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: children,
8 changes: 4 additions & 4 deletions lib/views/widgets/confirmation/more_bookings_button.dart
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import 'package:flutter/material.dart';
import 'package:auto_route/auto_route.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';

//Helpers
import '../../../helper/utils/constants.dart';

//Routes
import '../../../routes/app_router.gr.dart';
//Routing
import '../../../routes/routes.dart';
import '../../../routes/app_router.dart';

//Providers
import '../../../providers/all_providers.dart';
@@ -25,7 +25,7 @@ class MoreBookingsButton extends StatelessWidget {
width: double.infinity,
onPressed: () {
context.read(theatersProvider).clearSelectedSeats();
context.router.popUntilRouteWithName(MoviesScreenRoute.name);
AppRouter.popUntil(Routes.MoviesScreenRoute);
},
color: Constants.textWhite80Color,
child: const Center(
133 changes: 133 additions & 0 deletions lib/views/widgets/forgot_password/forgot_button_widget.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:flutter_spinkit/flutter_spinkit.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';

//Helpers
import '../../../helper/utils/constants.dart';

//Providers
import '../../../providers/all_providers.dart';

//Widgets
import '../common/custom_text_button.dart';

class ForgotButtonWidget extends StatefulHookWidget {
final TextEditingController emailController;
final TextEditingController newPasswordController;
final GlobalKey<FormState> formKey;

const ForgotButtonWidget({
Key? key,
required this.emailController,
required this.newPasswordController,
required this.formKey,
}) : super(key: key);

@override
_PageButtonWidgetState createState() => _PageButtonWidgetState();
}

class _PageButtonWidgetState extends State<ForgotButtonWidget> {
late Widget _currentPageButton;

void _onPressed({
bool isEmail = false,
bool isOtp = false,
bool isReset = false,
}) {
if (widget.formKey.currentState!.validate()) {
widget.formKey.currentState!.save();
final _forgotPasswordProv = context.read(forgotPasswordProvider.notifier);
if (isEmail) {
_forgotPasswordProv.requestOtpCode(widget.emailController.text);
} else if (isOtp) {
_forgotPasswordProv.verifyOtp();
} else if (isReset) {
_forgotPasswordProv.resetPassword(
password: widget.newPasswordController.text,
);
}
}
}

@override
Widget build(BuildContext context) {
final _forgotPasswordState = useProvider(forgotPasswordProvider);
return Padding(
padding: const EdgeInsets.fromLTRB(20, 40, 20, Constants.bottomInsets),
child: _forgotPasswordState.when(
email: () {
_currentPageButton = CustomTextButton.gradient(
width: double.infinity,
onPressed: () => _onPressed(isEmail: true),
gradient: Constants.buttonGradientOrange,
child: const Center(
child: Text(
'SEND OTP',
style: TextStyle(
color: Colors.white,
fontSize: 15,
letterSpacing: 0.7,
fontWeight: FontWeight.w600,
),
),
),
);
return _currentPageButton;
},
otp: (_) {
_currentPageButton = CustomTextButton.gradient(
width: double.infinity,
onPressed: () => _onPressed(isOtp: true),
gradient: Constants.buttonGradientOrange,
child: const Center(
child: Text('VERIFY OTP',
style: TextStyle(
color: Colors.white,
fontSize: 15,
letterSpacing: 0.7,
fontWeight: FontWeight.w600,
)),
),
);
return _currentPageButton;
},
resetPassword: (_) {
_currentPageButton = CustomTextButton.gradient(
width: double.infinity,
onPressed: () => _onPressed(isReset: true),
gradient: Constants.buttonGradientOrange,
child: const Center(
child: Text(
'RESET PASSWORD',
style: TextStyle(
color: Colors.white,
fontSize: 15,
letterSpacing: 0.7,
fontWeight: FontWeight.w600,
),
),
),
);
return _currentPageButton;
},
loading: (_) => CustomTextButton.gradient(
width: double.infinity,
onPressed: () {},
gradient: Constants.buttonGradientOrange,
child: const Center(
child: SpinKitRing(
color: Colors.white,
size: 30,
lineWidth: 4,
duration: Duration(milliseconds: 1100),
),
),
),
failed: (_, __) => _currentPageButton,
success: (_) => const SizedBox.shrink(),
),
);
}
}
42 changes: 42 additions & 0 deletions lib/views/widgets/forgot_password/forgot_message_widget.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';

//Helpers
import '../../../helper/extensions/context_extensions.dart';
import '../../../helper/utils/constants.dart';

//Providers
import '../../../providers/all_providers.dart';

class ForgotMessageWidget extends HookWidget {
const ForgotMessageWidget({
Key? key,
}) : super(key: key);

Text _buildMessageText(BuildContext ctx, String message) {
return Text(
message,
textAlign: TextAlign.center,
style: ctx.bodyText1.copyWith(
color: Constants.textGreyColor,
fontSize: 16,
),
);
}

@override
Widget build(BuildContext context) {
final _forgotPasswordState = useProvider(forgotPasswordProvider);
return _forgotPasswordState.maybeWhen(
email: () => _buildMessageText(
context,
'A 4 digit OTP will be sent to this email once verified',
),
otp: (message) => _buildMessageText(context, message),
resetPassword: (message) => _buildMessageText(context, message),
loading: (message) => _buildMessageText(context, message),
orElse: () => const SizedBox.shrink(),
);
}
}
50 changes: 50 additions & 0 deletions lib/views/widgets/forgot_password/forgot_name_widget.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';

//Helpers
import '../../../helper/extensions/context_extensions.dart';

//Providers
import '../../../providers/all_providers.dart';

class ForgotNameWidget extends StatefulHookWidget {
const ForgotNameWidget({
Key? key,
}) : super(key: key);

@override
_PageNameWidgetState createState() => _PageNameWidgetState();
}

class _PageNameWidgetState extends State<ForgotNameWidget> {
late Text currentPageText;

Text _buildText(String pageName) {
return Text(
pageName,
style: context.headline3.copyWith(fontSize: 22),
);
}

@override
Widget build(BuildContext context) {
final _forgotPasswordState = useProvider(forgotPasswordProvider);
return _forgotPasswordState.maybeWhen(
email: () {
currentPageText = _buildText('Forgot Password');
return currentPageText;
},
otp: (_) {
currentPageText = _buildText('Verify Otp');
return currentPageText;
},
resetPassword: (_) {
currentPageText = _buildText('Reset Password');
return currentPageText;
},
success: (_) => const SizedBox.shrink(),
orElse: () => currentPageText,
);
}
}
41 changes: 41 additions & 0 deletions lib/views/widgets/forgot_password/forgot_resend_widget.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';

//Providers
import '../../../providers/all_providers.dart';

//Helpers
import '../../../helper/extensions/context_extensions.dart';
import '../../../helper/utils/constants.dart';

class ForgotResendWidget extends HookWidget {
const ForgotResendWidget({
Key? key,
}) : super(key: key);

@override
Widget build(BuildContext context) {
final _forgotPasswordState = useProvider(forgotPasswordProvider);
return _forgotPasswordState.maybeWhen(
otp: (_) => Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
GestureDetector(
onTap: () {
context.read(forgotPasswordProvider.notifier).resendOtpCode();
},
child: Text(
'Resend OTP',
style: context.headline3.copyWith(
fontSize: 17,
color: Constants.primaryColor
),
),
),
],
),
orElse: () => const SizedBox.shrink(),
);
}
}
65 changes: 65 additions & 0 deletions lib/views/widgets/forgot_password/forgot_text_fields.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';

//Helpers
import '../../../helper/utils/form_validator.dart';

//Providers
import '../../../providers/all_providers.dart';

//Widgets
import '../common/custom_textfield.dart';
import 'reset_password_fields.dart';
import 'otp_code_fields.dart';

class ForgotTextFields extends StatefulHookWidget {
const ForgotTextFields({
Key? key,
required this.emailController,
required this.newPasswordController,
required this.cNewPasswordController,
}) : super(key: key);

final TextEditingController emailController;
final TextEditingController newPasswordController;
final TextEditingController cNewPasswordController;

@override
_ForgotPasswordFieldsState createState() => _ForgotPasswordFieldsState();
}

class _ForgotPasswordFieldsState extends State<ForgotTextFields> {
late Widget currentTextFields;

@override
Widget build(BuildContext context) {
final _forgotPasswordState = useProvider(forgotPasswordProvider);
return _forgotPasswordState.maybeWhen(
email: () {
currentTextFields = CustomTextField(
controller: widget.emailController,
floatingText: 'Email',
hintText: 'Type your email address',
keyboardType: TextInputType.emailAddress,
textInputAction: TextInputAction.next,
validator: FormValidator.emailValidator,
);
return currentTextFields;
},
otp: (_) {
currentTextFields = const OtpCodeFields();
return currentTextFields;
},
resetPassword: (_) {
currentTextFields = ResetPasswordFields(
newPasswordController: widget.newPasswordController,
cNewPasswordController: widget.cNewPasswordController,
);
return currentTextFields;
},
success: (_) => const SizedBox.shrink(),
orElse: () => currentTextFields,
);
}
}
60 changes: 60 additions & 0 deletions lib/views/widgets/forgot_password/otp_code_fields.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';

//Helpers
import '../../../helper/utils/constants.dart';
import '../../../helper/utils/form_validator.dart';

//Providers
import '../../../providers/all_providers.dart';

//Widgets
import '../common/custom_textfield.dart';

class OtpCodeFields extends StatelessWidget {
const OtpCodeFields({
Key? key,
}) : super(key: key);

@override
Widget build(BuildContext context) {
return Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
//Digit 1-4
for (int i = 0; i < 4; i++)
CustomTextField(
maxLength: 1,
width: 60,
height: 60,
contentPadding: const EdgeInsets.only(bottom: 10),
inputStyle: const TextStyle(
fontSize: 35,
color: Constants.textWhite80Color,
),
showErrorBorder: true,
textAlign: TextAlign.center,
errorAlign: Alignment.topCenter,
keyboardType: TextInputType.phone,
textInputAction: TextInputAction.next,
validator: FormValidator.otpDigitValidator,
onSaved: (digit) {
final forgotProv = context.read(forgotPasswordProvider.notifier);
forgotProv.setOtpDigit(i, digit!);
},
onChanged: (digit) {
if (digit!.length == 1) {
FocusScope.of(context).nextFocus();
} else if (digit.isEmpty) {
FocusScope.of(context).previousFocus();
}
},
),
],
),
],
);
}
}
50 changes: 50 additions & 0 deletions lib/views/widgets/forgot_password/reset_password_fields.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import 'package:flutter/material.dart';

//Helper
import '../../../helper/utils/form_validator.dart';

//Widget
import '../common/custom_textfield.dart';

class ResetPasswordFields extends StatelessWidget {
final TextEditingController newPasswordController;
final TextEditingController cNewPasswordController;

const ResetPasswordFields({
Key? key,
required this.newPasswordController,
required this.cNewPasswordController,
}) : super(key: key);

@override
Widget build(BuildContext context) {
return Column(
children: [
//New Password Field
CustomTextField(
hintText: 'Type your password',
floatingText: 'New Password',
controller: newPasswordController,
keyboardType: TextInputType.visiblePassword,
textInputAction: TextInputAction.next,
validator: FormValidator.passwordValidator,
),

const SizedBox(height: 25),

//Confirm New Password Field
CustomTextField(
hintText: 'Retype your password',
floatingText: 'Confirm Password',
controller: cNewPasswordController,
keyboardType: TextInputType.visiblePassword,
textInputAction: TextInputAction.done,
validator: (confirmPw) => FormValidator.confirmPasswordValidator(
confirmPw,
newPasswordController.text,
),
),
],
);
}
}
6 changes: 3 additions & 3 deletions lib/views/widgets/movie_details/floating_movie_posters.dart
Original file line number Diff line number Diff line change
@@ -43,7 +43,7 @@ class _FloatingMoviePostersState extends State<FloatingMoviePosters> {
left: 0,
height: 250,
width: 150,
duration: const Duration(milliseconds: 650),
duration: const Duration(milliseconds: 850),
curve: Curves.easeInOutBack,
child: const _LeftMoviePoster(),
),
@@ -54,7 +54,7 @@ class _FloatingMoviePostersState extends State<FloatingMoviePosters> {
right: 0,
height: 250,
width: 150,
duration: const Duration(milliseconds: 650),
duration: const Duration(milliseconds: 850),
curve: Curves.easeInOutBack,
child: const _RightMoviePoster(),
),
@@ -75,7 +75,7 @@ class _FloatingMoviePostersState extends State<FloatingMoviePosters> {
top: topGap - 65,
height: 250,
width: 190,
duration: const Duration(milliseconds: 500),
duration: const Duration(milliseconds: 650),
curve: Curves.easeInOutBack,
child: const _MainMoviePoster(),
),
8 changes: 4 additions & 4 deletions lib/views/widgets/movie_details/play_button_widget.dart
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:auto_route/auto_route.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';

//Providers
import 'movie_details_sheet.dart' show btnScaleRatioProvider;

//Routes
import '../../../routes/app_router.gr.dart';
//Routing
import '../../../routes/routes.dart';
import '../../../routes/app_router.dart';

class PlayButtonWidget extends HookWidget {
const PlayButtonWidget({
@@ -19,7 +19,7 @@ class PlayButtonWidget extends HookWidget {
final btnScaleRatio = useProvider(btnScaleRatioProvider).state;
return ElevatedButton(
onPressed: () {
context.router.push(const TrailerScreenRoute());
AppRouter.pushNamed(Routes.TrailerScreenRoute);
},
style: ElevatedButton.styleFrom(
elevation: 5,
8 changes: 4 additions & 4 deletions lib/views/widgets/movies/movie_carousel.dart
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import 'package:auto_route/auto_route.dart';
import 'package:carousel_slider/carousel_slider.dart';
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
@@ -13,8 +12,9 @@ import '../../../models/movie_model.dart';
//Providers
import '../../../providers/movies_provider.dart';

//Router
import '../../../routes/app_router.gr.dart';
//Routing
import '../../../routes/routes.dart';
import '../../../routes/app_router.dart';

//Widgets
import 'white_movie_container.dart';
@@ -55,7 +55,7 @@ class __MoviesCarouselState extends State<MoviesCarousel> {
context.read(selectedMovieProvider).state = movies[i];
context.read(leftMovieProvider).state = movies[leftIndex];
context.read(rightMovieProvider).state = movies[rightIndex];
context.router.push(const MovieDetailsScreenRoute());
AppRouter.pushNamed(Routes.MovieDetailsScreenRoute);
},
),
);
6 changes: 4 additions & 2 deletions lib/views/widgets/movies/movie_icons_row.dart
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import 'package:auto_route/auto_route.dart';
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';

//Routing
import '../../../routes/app_router.dart';

//Widgets
import 'movie_type_popup_menu.dart';

@@ -21,7 +23,7 @@ class MoviesIconsRow extends HookWidget {
IconButton(
icon: const Icon(Icons.arrow_back_rounded),
padding: const EdgeInsets.all(0),
onPressed: () => context.router.pop(),
onPressed: () => AppRouter.pop(),
),

//Filter
8 changes: 4 additions & 4 deletions lib/views/widgets/payment/pay_button.dart
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:auto_route/auto_route.dart';

//Helpers
import '../../../helper/utils/constants.dart';

//Routes
import '../../../routes/app_router.gr.dart';
//Routing
import '../../../routes/routes.dart';
import '../../../routes/app_router.dart';

//Providers
import '../../../providers/all_providers.dart';
@@ -25,7 +25,7 @@ class PayButton extends StatelessWidget {
width: double.infinity,
onPressed: () {
context.read(paymentsProvider).makePayment();
context.router.push(const ConfirmationScreenRoute());
AppRouter.pushNamed(Routes.ConfirmationScreenRoute);
},
gradient: Constants.buttonGradientOrange,
child: const Center(
8 changes: 4 additions & 4 deletions lib/views/widgets/theater/purchase_seats_button.dart
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import 'package:auto_route/auto_route.dart';
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';

@@ -8,8 +7,9 @@ import '../../../helper/utils/constants.dart';
//Providers
import '../../../providers/all_providers.dart';

//Routes
import '../../../routes/app_router.gr.dart';
//Routing
import '../../../routes/routes.dart';
import '../../../routes/app_router.dart';

//Widgets
import '../common/custom_text_button.dart';
@@ -27,7 +27,7 @@ class PurchaseSeatsButton extends StatelessWidget {
return CustomTextButton.gradient(
width: double.infinity,
onPressed: () {
context.router.push(const TicketSummaryScreenRoute());
AppRouter.pushNamed(Routes.TicketSummaryScreenRoute);
},
disabled: theaterSeats == 0,
gradient: Constants.buttonGradientOrange,
8 changes: 4 additions & 4 deletions lib/views/widgets/ticket_summary/confirm_bookings_button.dart
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import 'package:flutter/material.dart';
import 'package:auto_route/auto_route.dart';

//Helpers
import '../../../helper/utils/constants.dart';

//Routes
import '../../../routes/app_router.gr.dart';
//Routing
import '../../../routes/routes.dart';
import '../../../routes/app_router.dart';

//Widgets
import '../common/custom_text_button.dart';
@@ -20,7 +20,7 @@ class ConfirmBookingsButton extends StatelessWidget {
child: CustomTextButton.gradient(
width: double.infinity,
onPressed: () {
context.router.push(const PaymentScreenRoute());
AppRouter.pushNamed(Routes.PaymentScreenRoute);
},
gradient: Constants.buttonGradientOrange,
child: const Center(
8 changes: 4 additions & 4 deletions lib/views/widgets/welcome/browse_movies_button.dart
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import 'package:flutter/material.dart';
import 'package:auto_route/auto_route.dart';

//Helpers
import '../../../helper/utils/constants.dart';

//Routes
import '../../../../routes/app_router.gr.dart';
//Routing
import '../../../routes/routes.dart';
import '../../../routes/app_router.dart';

//Widgets
import '../common/custom_text_button.dart';
@@ -18,7 +18,7 @@ class BrowseMoviesButton extends StatelessWidget {
return CustomTextButton.gradient(
width: double.infinity,
onPressed: () {
context.router.push(const MoviesScreenRoute());
AppRouter.pushNamed(Routes.MoviesScreenRoute);
},
gradient: Constants.buttonGradientOrange,
child: const Center(
8 changes: 4 additions & 4 deletions lib/views/widgets/welcome/view_bookings_button.dart
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import 'package:flutter/material.dart';
import 'package:auto_route/auto_route.dart';

//Helpers
import '../../../helper/utils/constants.dart';

//Routes
import '../../../routes/app_router.gr.dart';
//Routing
import '../../../routes/routes.dart';
import '../../../routes/app_router.dart';

//Widgets
import '../common/custom_text_button.dart';
@@ -17,7 +17,7 @@ class ViewBookingsButton extends StatelessWidget {
Widget build(BuildContext context) {
return CustomTextButton.outlined(
width: double.infinity,
onPressed: () => context.router.push(const UserBookingsScreenRoute()),
onPressed: () => AppRouter.pushNamed(Routes.UserBookingsScreenRoute),
border: Border.all(color: Constants.primaryColor,width: 4),
child: const Center(
child: Text(
60 changes: 30 additions & 30 deletions pubspec.lock
Original file line number Diff line number Diff line change
@@ -7,14 +7,14 @@ packages:
name: _fe_analyzer_shared
url: "https://pub.dartlang.org"
source: hosted
version: "22.0.0"
version: "23.0.0"
analyzer:
dependency: transitive
dependency: "direct overridden"
description:
name: analyzer
url: "https://pub.dartlang.org"
source: hosted
version: "1.7.1"
version: "2.0.0"
archive:
dependency: transitive
description:
@@ -36,20 +36,6 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "2.6.1"
auto_route:
dependency: "direct main"
description:
name: auto_route
url: "https://pub.dartlang.org"
source: hosted
version: "2.2.0"
auto_route_generator:
dependency: "direct dev"
description:
name: auto_route_generator
url: "https://pub.dartlang.org"
source: hosted
version: "2.1.0"
better_player:
dependency: "direct main"
description:
@@ -70,7 +56,7 @@ packages:
name: build
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.3"
version: "2.1.0"
build_config:
dependency: transitive
description:
@@ -98,14 +84,14 @@ packages:
name: build_runner
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.6"
version: "2.1.1"
build_runner_core:
dependency: transitive
description:
name: build_runner_core
url: "https://pub.dartlang.org"
source: hosted
version: "7.0.1"
version: "7.1.0"
built_collection:
dependency: transitive
description:
@@ -238,7 +224,7 @@ packages:
name: dartdoc
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.0"
version: "1.0.2"
dio:
dependency: "direct main"
description:
@@ -320,7 +306,7 @@ packages:
name: flutter_native_splash
url: "https://pub.dartlang.org"
source: hosted
version: "1.2.0"
version: "1.2.1"
flutter_riverpod:
dependency: transitive
description:
@@ -365,14 +351,14 @@ packages:
name: freezed
url: "https://pub.dartlang.org"
source: hosted
version: "0.14.2"
version: "0.14.5"
freezed_annotation:
dependency: "direct main"
description:
name: freezed_annotation
url: "https://pub.dartlang.org"
source: hosted
version: "0.14.2"
version: "0.14.3"
frontend_server_client:
dependency: transitive
description:
@@ -387,6 +373,13 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.1"
golden_toolkit:
dependency: "direct main"
description:
name: golden_toolkit
url: "https://pub.dartlang.org"
source: hosted
version: "0.9.0"
google_fonts:
dependency: "direct main"
description:
@@ -470,14 +463,14 @@ packages:
name: json_annotation
url: "https://pub.dartlang.org"
source: hosted
version: "4.0.1"
version: "4.1.0"
json_serializable:
dependency: "direct dev"
description:
name: json_serializable
url: "https://pub.dartlang.org"
source: hosted
version: "4.1.4"
version: "5.0.0"
lints:
dependency: transitive
description:
@@ -507,12 +500,12 @@ packages:
source: hosted
version: "0.12.10"
meta:
dependency: transitive
dependency: "direct overridden"
description:
name: meta
url: "https://pub.dartlang.org"
source: hosted
version: "1.3.0"
version: "1.7.0"
mime:
dependency: transitive
description:
@@ -526,7 +519,7 @@ packages:
name: mockito
url: "https://pub.dartlang.org"
source: hosted
version: "5.0.13"
version: "5.0.14"
octo_image:
dependency: transitive
description:
@@ -727,7 +720,14 @@ packages:
name: source_gen
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.3"
version: "1.0.5"
source_helper:
dependency: transitive
description:
name: source_helper
url: "https://pub.dartlang.org"
source: hosted
version: "1.2.1"
source_span:
dependency: transitive
description:
19 changes: 11 additions & 8 deletions pubspec.yaml
Original file line number Diff line number Diff line change
@@ -8,11 +8,10 @@ environment:
dependencies:
flutter:
sdk: flutter
auto_route: ^2.2.0
cupertino_icons: ^1.0.3
time: ^2.0.0
cached_network_image: ^3.1.0
freezed_annotation: ^0.14.2
freezed_annotation: ^0.14.3
google_fonts: ^2.1.0
flutter_hooks: ^0.17.0
hooks_riverpod: ^0.14.0+4
@@ -22,25 +21,29 @@ dependencies:
shared_preferences: ^2.0.6
sliding_up_panel: ^2.0.0+1
intl: ^0.17.0
dartdoc: ^1.0.0
dartdoc: ^1.0.2
better_player: ^0.0.72
flutter_spinkit: 5.0.0
flutter_secure_storage: ^4.2.1
clock: ^1.1.0
mockito: ^5.0.13
mockito: ^5.0.14
golden_toolkit: ^0.9.0
dev_dependencies:
flutter_test:
sdk: flutter
auto_route_generator: ^2.1.0
freezed: ^0.14.2
json_serializable: ^4.1.3
freezed: ^0.14.4
json_serializable: ^5.0.0
build_runner: null
flutter_native_splash: ^1.2.0
flutter_native_splash: ^1.2.1
flutter_lints: ^1.0.4
dependency_overrides:
analyzer: '2.0.0'
meta: '1.7.0'
flutter:
uses-material-design: true
assets:
- assets/
- google_fonts/
flutter_icons:
android: true
ios: false
37 changes: 37 additions & 0 deletions test/flutter_test_config.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import 'dart:async';
import 'dart:io';

import 'package:golden_toolkit/golden_toolkit.dart';

//Theme
import 'package:ez_ticketz_app/helper/utils/custom_theme.dart';

Future<void> testExecutable(FutureOr<void> Function() testMain) async {
return GoldenToolkit.runWithConfiguration(
() async {
await loadAppFonts();
await testMain();
},
config: GoldenToolkitConfiguration(
defaultDevices: const [GoldensGlobalConfig.defaultDevice],
fileNameFactory: (name) {
if(Platform.isWindows) {
return 'goldens_local/$name.png';
}
else {
return 'goldens/$name.png';
}
}
),
);
}

abstract class GoldensGlobalConfig {
static final globalAppWrapper = materialAppWrapper(
theme: CustomTheme.mainTheme,
);

static const defaultDevice = Device.iphone11;

static final defaultSurfaceSize = defaultDevice.size;
}
39 changes: 39 additions & 0 deletions test/golden_tests/change_password_screen_golden_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import 'package:flutter_test/flutter_test.dart';
import 'package:golden_toolkit/golden_toolkit.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';

//Providers
import 'package:ez_ticketz_app/providers/all_providers.dart';
import 'common_mocked_providers.dart';

//Screens
import 'package:ez_ticketz_app/views/screens/change_password_screen.dart';

//Config
import '../flutter_test_config.dart';

void main() {
group('ChangePasswordScreen', () {
testGoldens(
'GIVEN the change password icon is pressed '
'WHEN the change password screen is shown '
'THEN it looks like change_password_screen_golden.png',
(tester) async {
//when
await tester.pumpWidgetBuilder(
ProviderScope(
overrides: [
authProvider.overrideWithProvider(mockAuthProvider)
],
child: const ChangePasswordScreen(),
),
surfaceSize: GoldensGlobalConfig.defaultSurfaceSize,
wrapper: GoldensGlobalConfig.globalAppWrapper,
);

//then
await screenMatchesGolden(tester, 'change_password_screen_golden');
},
);
});
}
44 changes: 44 additions & 0 deletions test/golden_tests/common_mocked_providers.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import 'package:flutter/foundation.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:mockito/mockito.dart';

//Models
import 'package:ez_ticketz_app/models/user_model.dart';

//Providers
import 'package:ez_ticketz_app/providers/auth_provider.dart';

//States
import 'package:ez_ticketz_app/providers/states/auth_state.dart';

//Services
import 'package:ez_ticketz_app/services/local_storage/key_value_storage_service.dart';
import 'package:ez_ticketz_app/services/repositories/auth_repository.dart';

//Mocks
class _MockKVStorageService extends Mock implements KeyValueStorageService {
@override
bool getAuthState() => false;

@override
UserModel? getAuthUser() => null;

@override
Future<String> getAuthPassword() => SynchronousFuture('');

@override
void resetKeys() {}
}

//Fakes
class MockAuthRepository extends Fake implements AuthRepository {}

//Providers
final mockAuthProvider = StateNotifierProvider<AuthProvider, AuthState>((ref) {
return AuthProvider(
reader: ref.read,
authRepository: MockAuthRepository(),
keyValueStorageService: _MockKVStorageService(),
);
});
118 changes: 118 additions & 0 deletions test/golden_tests/forgot_password_screen_golden_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
import 'package:flutter_test/flutter_test.dart';
import 'package:golden_toolkit/golden_toolkit.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';

//Providers
import 'package:ez_ticketz_app/providers/all_providers.dart';
import 'package:ez_ticketz_app/providers/forgot_password_provider.dart';
import 'package:ez_ticketz_app/providers/states/forgot_password_state.dart';
import 'common_mocked_providers.dart';

//Screens
import 'package:ez_ticketz_app/views/screens/forgot_password_screen.dart';

//Config
import '../flutter_test_config.dart';

void main() {
group(
'ForgotPasswordScreen',
() {
const forgotPasswordScreen = ForgotPasswordScreen();

testGoldens(
'GIVEN the forgot password link is pressed '
'WHEN the forgot password screen is shown '
'THEN it looks like forgot_password_screen_email_golden.png',
(tester) async {
//when
await tester.pumpWidgetBuilder(
const ProviderScope(
child: forgotPasswordScreen,
),
surfaceSize: GoldensGlobalConfig.defaultSurfaceSize,
wrapper: GoldensGlobalConfig.globalAppWrapper,
);

//then
await screenMatchesGolden(
tester,
'forgot_password_screen_email_golden',
);
},
);

testGoldens(
'GIVEN we are on forgot password screen '
'AND the otp has been sent '
'WHEN the screen is rebuilt '
'THEN it looks like forgot_password_screen_otp_golden.png',
(tester) async {
//given
await tester.pumpWidgetBuilder(
ProviderScope(
overrides: [
forgotPasswordProvider.overrideWithValue(
ForgotPasswordProvider(
authRepository: MockAuthRepository(),
initialState: const ForgotPasswordState.otp(
otpSentMessage: 'otpSentMessage',
),
),
)
],
child: forgotPasswordScreen,
),
surfaceSize: GoldensGlobalConfig.defaultSurfaceSize,
wrapper: GoldensGlobalConfig.globalAppWrapper,
);

//rebuild screen
await tester.pump();

//then
await screenMatchesGolden(
tester,
'forgot_password_screen_otp_golden',
);
},
);

testGoldens(
'GIVEN we are on forgot password screen '
'AND the otp has been verified '
'WHEN the screen is rebuilt '
'THEN it looks like forgot_password_screen_resetPw_golden.png',
(tester) async {
//given
await tester.pumpWidgetBuilder(
ProviderScope(
overrides: [
forgotPasswordProvider.overrideWithValue(
ForgotPasswordProvider(
authRepository: MockAuthRepository(),
initialState: const ForgotPasswordState.resetPassword(
otpVerifiedMessage: 'otpVerifiedMessage',
),
),
)
],
child: forgotPasswordScreen,
),
surfaceSize: GoldensGlobalConfig.defaultSurfaceSize,
wrapper: GoldensGlobalConfig.globalAppWrapper,
);

//rebuild screen
await tester.pump();

//then
await screenMatchesGolden(
tester,
'forgot_password_screen_resetPw_golden',
);
},
);
},
);
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
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 test/golden_tests/goldens/home_screen_golden.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 test/golden_tests/goldens/login_screen_golden.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
29 changes: 29 additions & 0 deletions test/golden_tests/home_screen_golden_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import 'package:flutter_test/flutter_test.dart';
import 'package:golden_toolkit/golden_toolkit.dart';

//Screens
import 'package:ez_ticketz_app/views/screens/home_screen.dart';

//Config
import '../flutter_test_config.dart';

void main() {
group('HomeScreen', () {
testGoldens(
'GIVEN the app is started '
'WHEN the home screen is shown '
'THEN it looks like home_screen_golden.png',
(tester) async {
//when
await tester.pumpWidgetBuilder(
const HomeScreen(),
surfaceSize: GoldensGlobalConfig.defaultSurfaceSize,
wrapper: GoldensGlobalConfig.globalAppWrapper,
);

//then
await screenMatchesGolden(tester, 'home_screen_golden');
},
);
});
}
39 changes: 39 additions & 0 deletions test/golden_tests/login_screen_golden_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import 'package:flutter_test/flutter_test.dart';
import 'package:golden_toolkit/golden_toolkit.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';

//Providers
import 'package:ez_ticketz_app/providers/all_providers.dart';
import 'common_mocked_providers.dart';

//Screens
import 'package:ez_ticketz_app/views/screens/login_screen.dart';

//Config
import '../flutter_test_config.dart';

void main() {
group('LoginScreen', () {
testGoldens(
'GIVEN the login button is pressed '
'WHEN the login screen is shown '
'THEN it looks like login_screen_golden.png',
(tester) async {
//when
await tester.pumpWidgetBuilder(
ProviderScope(
overrides: [
authProvider.overrideWithProvider(mockAuthProvider),
],
child: const LoginScreen(),
),
surfaceSize: GoldensGlobalConfig.defaultSurfaceSize,
wrapper: GoldensGlobalConfig.globalAppWrapper,
);

//then
await screenMatchesGolden(tester, 'login_screen_golden');
},
);
});
}
Loading

0 comments on commit a6a4245

Please sign in to comment.