feat: Refactor code structure and optimize performance across multiple modules
This commit is contained in:
@@ -0,0 +1,87 @@
|
||||
import 'package:firebase_auth/firebase_auth.dart';
|
||||
import 'package:flutter_dotenv/flutter_dotenv.dart';
|
||||
import 'package:graphql_flutter/graphql_flutter.dart';
|
||||
import 'package:injectable/injectable.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
class GraphQLConfig {
|
||||
static HttpLink httpLink = HttpLink(
|
||||
dotenv.env['BASE_URL']!,
|
||||
defaultHeaders: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
);
|
||||
static AuthLink authLink = AuthLink(
|
||||
getToken: () async {
|
||||
User? user = FirebaseAuth.instance.currentUser;
|
||||
if (user != null) {
|
||||
return 'Bearer ${await user.getIdToken()}';
|
||||
}
|
||||
return null;
|
||||
},
|
||||
);
|
||||
|
||||
static Link link = authLink.concat(httpLink);
|
||||
|
||||
Future<GraphQLClient> configClient() async {
|
||||
var path = await getApplicationDocumentsDirectory();
|
||||
final store = await HiveStore.open(path: '${path.path}/gql_cache');
|
||||
|
||||
return GraphQLClient(
|
||||
link: link,
|
||||
cache: GraphQLCache(store: store, ),
|
||||
defaultPolicies: DefaultPolicies(
|
||||
mutate: Policies(
|
||||
cacheReread: CacheRereadPolicy.ignoreOptimisitic,
|
||||
error: ErrorPolicy.all,
|
||||
fetch: FetchPolicy.networkOnly,
|
||||
),
|
||||
query: Policies(
|
||||
cacheReread: CacheRereadPolicy.ignoreOptimisitic,
|
||||
error: ErrorPolicy.all,
|
||||
fetch: FetchPolicy.networkOnly,
|
||||
),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@Singleton()
|
||||
class ApiClient {
|
||||
static final GraphQLConfig _graphQLConfig = GraphQLConfig();
|
||||
final Future<GraphQLClient> _client = _graphQLConfig.configClient();
|
||||
|
||||
Future<QueryResult> query(
|
||||
{required String schema, Map<String, dynamic>? body}) async {
|
||||
final QueryOptions options = QueryOptions(
|
||||
document: gql(schema),
|
||||
variables: body ?? {},
|
||||
);
|
||||
return (await _client).query(options).timeout(Duration(seconds: 30));
|
||||
}
|
||||
|
||||
Stream<QueryResult?> queryWithCache({
|
||||
required String schema,
|
||||
Map<String, dynamic>? body,
|
||||
}) async* {
|
||||
final WatchQueryOptions options = WatchQueryOptions(
|
||||
document: gql(schema),
|
||||
variables: body ?? {},
|
||||
fetchPolicy: FetchPolicy.cacheAndNetwork);
|
||||
|
||||
var result = (await _client).watchQuery(options).fetchResults();
|
||||
yield result.eagerResult;
|
||||
yield await result.networkResult;
|
||||
}
|
||||
|
||||
Future<QueryResult> mutate(
|
||||
{required String schema, Map<String, dynamic>? body}) async {
|
||||
final MutationOptions options = MutationOptions(
|
||||
document: gql(schema),
|
||||
variables: body ?? {},
|
||||
);
|
||||
return (await _client).mutate(options);
|
||||
}
|
||||
|
||||
void dropCache() async {
|
||||
(await _client).cache.store.reset();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
import 'package:graphql_flutter/graphql_flutter.dart';
|
||||
|
||||
interface class DisplayableException {
|
||||
final String message;
|
||||
|
||||
DisplayableException(this.message);
|
||||
}
|
||||
|
||||
class NetworkException implements Exception, DisplayableException {
|
||||
@override
|
||||
final String message;
|
||||
|
||||
NetworkException([this.message = 'No internet connection']);
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return message;
|
||||
}
|
||||
}
|
||||
|
||||
class GraphQLException implements Exception, DisplayableException {
|
||||
@override
|
||||
final String message;
|
||||
|
||||
GraphQLException(this.message);
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return message;
|
||||
}
|
||||
}
|
||||
|
||||
class UnknownException implements Exception, DisplayableException {
|
||||
@override
|
||||
final String message;
|
||||
|
||||
UnknownException([this.message = 'Something went wrong']);
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return message;
|
||||
}
|
||||
}
|
||||
|
||||
Exception parseBackendError(dynamic error) {
|
||||
if (error is OperationException) {
|
||||
if (error.graphqlErrors.isNotEmpty) {
|
||||
return GraphQLException(
|
||||
error.graphqlErrors.firstOrNull?.message ?? 'GraphQL error occurred');
|
||||
}
|
||||
return NetworkException('Network error occurred');
|
||||
}
|
||||
return UnknownException();
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
extension IntExtensions on int {
|
||||
String toOrdinal() {
|
||||
if (this >= 11 && this <= 13) {
|
||||
return '${this}th';
|
||||
}
|
||||
switch (this % 10) {
|
||||
case 1:
|
||||
return '${this}st';
|
||||
case 2:
|
||||
return '${this}nd';
|
||||
case 3:
|
||||
return '${this}rd';
|
||||
default:
|
||||
return '${this}th';
|
||||
}
|
||||
}
|
||||
|
||||
String getWeekdayId() => switch (this) {
|
||||
0 => 'Monday',
|
||||
1 => 'Tuesday',
|
||||
2 => 'Wednesday',
|
||||
3 => 'Thursday',
|
||||
4 => 'Friday',
|
||||
5 => 'Saturday',
|
||||
_ => 'Sunday',
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
import 'dart:developer';
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
|
||||
class Logger {
|
||||
static void error(e, {String? message}) {
|
||||
log(
|
||||
e.toString(),
|
||||
error: message ?? e,
|
||||
level: 2000,
|
||||
stackTrace: StackTrace.current,
|
||||
name: 'ERROR',
|
||||
);
|
||||
}
|
||||
|
||||
static debug(dynamic msg, {String? message}) {
|
||||
if (kDebugMode) {
|
||||
log(
|
||||
msg.toString(),
|
||||
name: message ?? msg.runtimeType.toString(),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const error = Logger.error;
|
||||
const debug = Logger.debug;
|
||||
@@ -0,0 +1,6 @@
|
||||
extension StringExtensions on String {
|
||||
String capitalize() {
|
||||
if (isEmpty) return this;
|
||||
return this[0].toUpperCase() + substring(1).toLowerCase();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
class DateTextFormatter extends TextInputFormatter {
|
||||
@override
|
||||
TextEditingValue formatEditUpdate(
|
||||
TextEditingValue oldValue, TextEditingValue newValue) {
|
||||
if (oldValue.text.length >= newValue.text.length) {
|
||||
return newValue;
|
||||
}
|
||||
|
||||
String filteredText = newValue.text.replaceAll(RegExp(r'[^0-9.]'), '');
|
||||
|
||||
var dateText = _addSeparators(filteredText, '.');
|
||||
return newValue.copyWith(
|
||||
text: dateText, selection: updateCursorPosition(dateText));
|
||||
}
|
||||
|
||||
String _addSeparators(String value, String separator) {
|
||||
value = value.replaceAll('.', '');
|
||||
var newString = '';
|
||||
for (int i = 0; i < value.length; i++) {
|
||||
newString += value[i];
|
||||
if (i == 1) {
|
||||
newString += separator;
|
||||
}
|
||||
if (i == 3) {
|
||||
newString += separator;
|
||||
}
|
||||
}
|
||||
return newString;
|
||||
}
|
||||
|
||||
TextSelection updateCursorPosition(String text) {
|
||||
return TextSelection.fromPosition(TextPosition(offset: text.length));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
abstract class EmailValidator {
|
||||
static String? validate(String? email, {bool isRequired = true}) {
|
||||
// Regular expression for validating emails
|
||||
final RegExp phoneRegExp = RegExp(
|
||||
r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$',
|
||||
);
|
||||
|
||||
if (isRequired && (email == null || email.isEmpty)) {
|
||||
return 'Email is required';
|
||||
} else if (email != null && !phoneRegExp.hasMatch(email)) {
|
||||
return 'Invalid email';
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
abstract class PhoneValidator {
|
||||
static String? validate(String? phoneNumber, {bool isRequired = true}) {
|
||||
// Regular expression for validating phone numbers
|
||||
final RegExp phoneRegExp = RegExp(r'^\+?[1-9]\d{1,14}$');
|
||||
|
||||
if (isRequired && (phoneNumber==null || phoneNumber.isEmpty)) {
|
||||
return 'Phone number is required';
|
||||
} else if (phoneNumber !=null && !phoneRegExp.hasMatch(phoneNumber)) {
|
||||
return 'Invalid phone number';
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
class SkillExpValidator {
|
||||
SkillExpValidator._();
|
||||
|
||||
static String? validate(String value) {
|
||||
if (value.isEmpty) {
|
||||
return 'Experience is required';
|
||||
}
|
||||
var parsed = int.tryParse(value);
|
||||
if (value.isNotEmpty && parsed == null) {
|
||||
return 'Experience must be a number';
|
||||
}
|
||||
|
||||
if (value.isNotEmpty && parsed! > 20 || parsed! < 1) {
|
||||
return 'Experience must be between 1 and 20';
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
import 'package:get_it/get_it.dart';
|
||||
import 'package:injectable/injectable.dart';
|
||||
|
||||
import 'injectable.config.dart';
|
||||
|
||||
final getIt = GetIt.instance;
|
||||
|
||||
@InjectableInit()
|
||||
void configureDependencies(String env) => GetIt.instance.init(environment: env);
|
||||
@@ -0,0 +1,41 @@
|
||||
import 'package:auto_route/auto_route.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:krow/core/application/routing/routes.gr.dart';
|
||||
import 'package:krow/core/sevices/auth_state_service/auth_service.dart';
|
||||
|
||||
class SplashRedirectGuard extends AutoRouteGuard {
|
||||
final AuthService authService;
|
||||
|
||||
SplashRedirectGuard(this.authService);
|
||||
|
||||
@override
|
||||
Future<void> onNavigation(
|
||||
NavigationResolver resolver, StackRouter router) async {
|
||||
final status = await authService.getAuthStatus();
|
||||
switch (status) {
|
||||
case AuthStatus.authenticated:
|
||||
router.replace(const HomeRoute());
|
||||
break;
|
||||
case AuthStatus.adminValidation:
|
||||
// router.replace(const WaitingValidationRoute());
|
||||
break;
|
||||
case AuthStatus.prepareProfile:
|
||||
if(kDebugMode) {
|
||||
// router.replace(const HomeRoute());
|
||||
}else {
|
||||
// router.replace(const CheckListFlowRoute());
|
||||
}
|
||||
break;
|
||||
case AuthStatus.unauthenticated:
|
||||
// router.replace(const HomeRoute());
|
||||
router.replace( const SignInFlowRoute());
|
||||
break;
|
||||
case AuthStatus.error:
|
||||
//todo(Artem):show error screen
|
||||
router.replace( const SignInFlowRoute());
|
||||
}
|
||||
|
||||
// resolver.next(false);
|
||||
// resolver.next(true);
|
||||
}
|
||||
}
|
||||
122
mobile-apps/client-app/lib/core/application/routing/routes.dart
Normal file
122
mobile-apps/client-app/lib/core/application/routing/routes.dart
Normal file
@@ -0,0 +1,122 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:auto_route/auto_route.dart';
|
||||
import 'package:krow/core/application/di/injectable.dart';
|
||||
import 'package:krow/core/application/routing/guards.dart';
|
||||
import 'package:krow/core/application/routing/routes.gr.dart';
|
||||
import 'package:krow/core/sevices/auth_state_service/auth_service.dart';
|
||||
|
||||
@AutoRouterConfig(
|
||||
replaceInRouteName: 'Screen|Page,Route',
|
||||
)
|
||||
class AppRouter extends RootStackRouter {
|
||||
@override
|
||||
RouteType get defaultRouteType => const RouteType.material();
|
||||
|
||||
Future<DeepLink> deepLinkBuilder(PlatformDeepLink deepLink) async {
|
||||
final uri = deepLink.uri;
|
||||
if (uri.queryParameters.containsKey('oobCode')) {
|
||||
final code = uri.queryParameters['oobCode'];
|
||||
popUntil((route) => false);
|
||||
return DeepLink([
|
||||
const WelcomeRoute(),
|
||||
const SignInRoute(),
|
||||
EnterNewPassRoute(code: code),
|
||||
]);
|
||||
}
|
||||
return deepLink;
|
||||
}
|
||||
|
||||
@override
|
||||
List<AutoRoute> get routes => [
|
||||
AdaptiveRoute(
|
||||
path: '/',
|
||||
page: SplashRoute.page,
|
||||
initial: true,
|
||||
guards: [SplashRedirectGuard(getIt<AuthService>())],
|
||||
),
|
||||
createEventFlow,
|
||||
authFlow,
|
||||
homeFlow,
|
||||
];
|
||||
|
||||
get authFlow => AdaptiveRoute(
|
||||
path: '/auth',
|
||||
page: SignInFlowRoute.page,
|
||||
children: [
|
||||
AutoRoute(path: 'welcome', page: WelcomeRoute.page, initial: true),
|
||||
AutoRoute(
|
||||
path: 'sign_in',
|
||||
page: SignInRoute.page,
|
||||
),
|
||||
AutoRoute(
|
||||
path: 'reset_pass',
|
||||
page: ResetPassRoute.page,
|
||||
),
|
||||
AutoRoute(
|
||||
path: 'pass',
|
||||
page: EnterNewPassRoute.page,
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
get createEventFlow =>
|
||||
AutoRoute(path: '/create', page: CreateEventFlowRoute.page, children: [
|
||||
AutoRoute(path: 'edit', page: CreateEventRoute.page, initial: true),
|
||||
AutoRoute(
|
||||
path: 'preview',
|
||||
page: EventDetailsRoute.page,
|
||||
),
|
||||
]);
|
||||
|
||||
get homeFlow => AdaptiveRoute(
|
||||
path: '/home',
|
||||
page: HomeRoute.page,
|
||||
children: [eventsFlow, invoiceFlow, notificationsFlow, profileFlow]);
|
||||
|
||||
get eventsFlow =>
|
||||
AdaptiveRoute(path: 'events', page: EventsFlowRoute.page, children: [
|
||||
AutoRoute(path: 'list', page: EventsListMainRoute.page, initial: true),
|
||||
AutoRoute(path: 'details', page: EventDetailsRoute.page),
|
||||
AutoRoute(path: 'assigned', page: AssignedStaffRoute.page),
|
||||
AutoRoute(path: 'assigned_manual', page: ClockManualRoute.page),
|
||||
AutoRoute(path: 'rate_staff', page: RateStaffRoute.page),
|
||||
]);
|
||||
|
||||
get invoiceFlow =>
|
||||
AdaptiveRoute(path: 'invoice', page: InvoiceFlowRoute.page, children: [
|
||||
AutoRoute(
|
||||
path: 'list', page: InvoicesListMainRoute.page, initial: true),
|
||||
AutoRoute(
|
||||
path: 'details', page: InvoiceDetailsRoute.page),
|
||||
AutoRoute(
|
||||
path: 'event',
|
||||
page: EventDetailsRoute.page,
|
||||
),
|
||||
]);
|
||||
|
||||
get notificationsFlow => AdaptiveRoute(
|
||||
path: 'notifications',
|
||||
page: NotificationFlowRoute.page,
|
||||
children: [
|
||||
AutoRoute(
|
||||
path: 'list', page: NotificationsListRoute.page, initial: true),
|
||||
AutoRoute(
|
||||
path: 'details',
|
||||
page: NotificationDetailsRoute.page,
|
||||
),
|
||||
]);
|
||||
|
||||
get profileFlow =>
|
||||
AdaptiveRoute(path: 'profile', page: ProfileFlowRoute.page, children: [
|
||||
AutoRoute(
|
||||
path: 'preview', page: ProfilePreviewRoute.page, initial: true),
|
||||
AutoRoute(
|
||||
path: 'edit',
|
||||
page: PersonalInfoRoute.page,
|
||||
),
|
||||
]);
|
||||
|
||||
@override
|
||||
List<AutoRouteGuard> get guards => [];
|
||||
}
|
||||
Reference in New Issue
Block a user