Merge branch 'master' of https://git.delta.iq/dania/finger_print_app
This commit is contained in:
3
devtools_options.yaml
Normal file
3
devtools_options.yaml
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
description: This file stores settings for Dart & Flutter DevTools.
|
||||||
|
documentation: https://docs.flutter.dev/tools/devtools/extensions#configure-extension-enablement-states
|
||||||
|
extensions:
|
||||||
@@ -4,11 +4,15 @@ PODS:
|
|||||||
- Flutter (1.0.0)
|
- Flutter (1.0.0)
|
||||||
- flutter_native_splash (2.4.3):
|
- flutter_native_splash (2.4.3):
|
||||||
- Flutter
|
- Flutter
|
||||||
|
- shared_preferences_foundation (0.0.1):
|
||||||
|
- Flutter
|
||||||
|
- FlutterMacOS
|
||||||
|
|
||||||
DEPENDENCIES:
|
DEPENDENCIES:
|
||||||
- camera_avfoundation (from `.symlinks/plugins/camera_avfoundation/ios`)
|
- camera_avfoundation (from `.symlinks/plugins/camera_avfoundation/ios`)
|
||||||
- Flutter (from `Flutter`)
|
- Flutter (from `Flutter`)
|
||||||
- flutter_native_splash (from `.symlinks/plugins/flutter_native_splash/ios`)
|
- flutter_native_splash (from `.symlinks/plugins/flutter_native_splash/ios`)
|
||||||
|
- shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`)
|
||||||
|
|
||||||
EXTERNAL SOURCES:
|
EXTERNAL SOURCES:
|
||||||
camera_avfoundation:
|
camera_avfoundation:
|
||||||
@@ -17,11 +21,14 @@ EXTERNAL SOURCES:
|
|||||||
:path: Flutter
|
:path: Flutter
|
||||||
flutter_native_splash:
|
flutter_native_splash:
|
||||||
:path: ".symlinks/plugins/flutter_native_splash/ios"
|
:path: ".symlinks/plugins/flutter_native_splash/ios"
|
||||||
|
shared_preferences_foundation:
|
||||||
|
:path: ".symlinks/plugins/shared_preferences_foundation/darwin"
|
||||||
|
|
||||||
SPEC CHECKSUMS:
|
SPEC CHECKSUMS:
|
||||||
camera_avfoundation: be3be85408cd4126f250386828e9b1dfa40ab436
|
camera_avfoundation: be3be85408cd4126f250386828e9b1dfa40ab436
|
||||||
Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7
|
Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7
|
||||||
flutter_native_splash: c32d145d68aeda5502d5f543ee38c192065986cf
|
flutter_native_splash: c32d145d68aeda5502d5f543ee38c192065986cf
|
||||||
|
shared_preferences_foundation: 9e1978ff2562383bd5676f64ec4e9aa8fa06a6f7
|
||||||
|
|
||||||
PODFILE CHECKSUM: 4305caec6b40dde0ae97be1573c53de1882a07e5
|
PODFILE CHECKSUM: 4305caec6b40dde0ae97be1573c53de1882a07e5
|
||||||
|
|
||||||
|
|||||||
@@ -161,7 +161,6 @@
|
|||||||
C9C24CB30CC2EF64A5028A78 /* Pods-RunnerTests.release.xcconfig */,
|
C9C24CB30CC2EF64A5028A78 /* Pods-RunnerTests.release.xcconfig */,
|
||||||
C8EAEACEAC43BB801D9EFF4B /* Pods-RunnerTests.profile.xcconfig */,
|
C8EAEACEAC43BB801D9EFF4B /* Pods-RunnerTests.profile.xcconfig */,
|
||||||
);
|
);
|
||||||
name = Pods;
|
|
||||||
path = Pods;
|
path = Pods;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
@@ -471,6 +470,7 @@
|
|||||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
CLANG_ENABLE_MODULES = YES;
|
CLANG_ENABLE_MODULES = YES;
|
||||||
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
||||||
|
DEVELOPMENT_TEAM = 6YV3J6426X;
|
||||||
ENABLE_BITCODE = NO;
|
ENABLE_BITCODE = NO;
|
||||||
INFOPLIST_FILE = Runner/Info.plist;
|
INFOPLIST_FILE = Runner/Info.plist;
|
||||||
LD_RUNPATH_SEARCH_PATHS = (
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
@@ -653,6 +653,7 @@
|
|||||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
CLANG_ENABLE_MODULES = YES;
|
CLANG_ENABLE_MODULES = YES;
|
||||||
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
||||||
|
DEVELOPMENT_TEAM = 6YV3J6426X;
|
||||||
ENABLE_BITCODE = NO;
|
ENABLE_BITCODE = NO;
|
||||||
INFOPLIST_FILE = Runner/Info.plist;
|
INFOPLIST_FILE = Runner/Info.plist;
|
||||||
LD_RUNPATH_SEARCH_PATHS = (
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
@@ -675,6 +676,7 @@
|
|||||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
CLANG_ENABLE_MODULES = YES;
|
CLANG_ENABLE_MODULES = YES;
|
||||||
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
||||||
|
DEVELOPMENT_TEAM = 6YV3J6426X;
|
||||||
ENABLE_BITCODE = NO;
|
ENABLE_BITCODE = NO;
|
||||||
INFOPLIST_FILE = Runner/Info.plist;
|
INFOPLIST_FILE = Runner/Info.plist;
|
||||||
LD_RUNPATH_SEARCH_PATHS = (
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
|
|||||||
173
lib/ARCHITECTURE.md
Normal file
173
lib/ARCHITECTURE.md
Normal file
@@ -0,0 +1,173 @@
|
|||||||
|
# هيكلية المشروع (Project Architecture)
|
||||||
|
|
||||||
|
هذا المشروع يتبع نمط Clean Architecture مع فصل واضح بين الطبقات.
|
||||||
|
|
||||||
|
## الهيكلية العامة
|
||||||
|
|
||||||
|
```
|
||||||
|
lib/
|
||||||
|
├── core/ # المكونات الأساسية المشتركة
|
||||||
|
│ ├── constants/ # الثوابت (مثل الأبعاد)
|
||||||
|
│ ├── di/ # Dependency Injection (GetIt)
|
||||||
|
│ ├── enums/ # التعدادات
|
||||||
|
│ ├── error/ # معالجة الأخطاء (Exceptions & Failures)
|
||||||
|
│ ├── network/ # عميل API (ApiClient)
|
||||||
|
│ └── utils/ # الأدوات المساعدة
|
||||||
|
│
|
||||||
|
├── data/ # طبقة البيانات
|
||||||
|
│ ├── datasources/ # مصادر البيانات (Remote & Local)
|
||||||
|
│ ├── dto/ # Data Transfer Objects (للاتصال مع API)
|
||||||
|
│ └── repositories/ # تطبيقات الـ Repositories
|
||||||
|
│
|
||||||
|
├── domain/ # طبقة الأعمال (Business Logic)
|
||||||
|
│ ├── models/ # نماذج الأعمال
|
||||||
|
│ ├── repositories/ # واجهات الـ Repositories
|
||||||
|
│ └── usecases/ # حالات الاستخدام (Use Cases)
|
||||||
|
│
|
||||||
|
├── presentation/ # طبقة العرض
|
||||||
|
│ ├── blocs/ # State Management (BLoC)
|
||||||
|
│ ├── screens/ # الشاشات
|
||||||
|
│ └── widgets/ # الويدجتات القابلة لإعادة الاستخدام
|
||||||
|
│
|
||||||
|
├── models/ # النماذج القديمة (يمكن نقلها لـ domain/models)
|
||||||
|
├── screens/ # الشاشات القديمة (يمكن نقلها لـ presentation/screens)
|
||||||
|
├── services/ # الخدمات القديمة
|
||||||
|
└── widgets/ # الويدجتات القديمة (يمكن نقلها لـ presentation/widgets)
|
||||||
|
```
|
||||||
|
|
||||||
|
## كيفية الاستخدام
|
||||||
|
|
||||||
|
### 1. إضافة API جديد
|
||||||
|
|
||||||
|
#### أ) إنشاء DTO في `data/dto/`
|
||||||
|
```dart
|
||||||
|
class LoginDto {
|
||||||
|
final String phoneNumber;
|
||||||
|
final String password;
|
||||||
|
|
||||||
|
LoginDto({required this.phoneNumber, required this.password});
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() => {
|
||||||
|
'phoneNumber': phoneNumber,
|
||||||
|
'password': password,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### ب) إنشاء Remote Data Source في `data/datasources/`
|
||||||
|
```dart
|
||||||
|
abstract class AuthRemoteDataSource {
|
||||||
|
Future<LoginResponseDto> login(LoginDto dto);
|
||||||
|
}
|
||||||
|
|
||||||
|
class AuthRemoteDataSourceImpl implements AuthRemoteDataSource {
|
||||||
|
final ApiClient apiClient;
|
||||||
|
|
||||||
|
AuthRemoteDataSourceImpl({required this.apiClient});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<LoginResponseDto> login(LoginDto dto) async {
|
||||||
|
try {
|
||||||
|
final response = await apiClient.post('/Auth/login', data: dto.toJson());
|
||||||
|
// Handle response
|
||||||
|
} on DioException catch (e) {
|
||||||
|
// Handle errors
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### ج) إنشاء Repository Interface في `domain/repositories/`
|
||||||
|
```dart
|
||||||
|
abstract class AuthRepository {
|
||||||
|
Future<Either<Failure, LoginResponseModel>> login(LoginRequest request);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### د) إنشاء Repository Implementation في `data/repositories/`
|
||||||
|
```dart
|
||||||
|
class AuthRepositoryImpl implements AuthRepository {
|
||||||
|
final AuthRemoteDataSource remoteDataSource;
|
||||||
|
|
||||||
|
AuthRepositoryImpl({required this.remoteDataSource});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<Either<Failure, LoginResponseModel>> login(LoginRequest request) async {
|
||||||
|
try {
|
||||||
|
final dto = LoginDto(...);
|
||||||
|
final responseDto = await remoteDataSource.login(dto);
|
||||||
|
final model = _convertDtoToModel(responseDto);
|
||||||
|
return Right(model);
|
||||||
|
} on ServerException catch (e) {
|
||||||
|
return Left(ServerFailure(e.message));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### هـ) إنشاء Use Case في `domain/usecases/`
|
||||||
|
```dart
|
||||||
|
class LoginUseCase {
|
||||||
|
final AuthRepository repository;
|
||||||
|
|
||||||
|
LoginUseCase({required this.repository});
|
||||||
|
|
||||||
|
Future<Either<Failure, LoginResponseModel>> call(LoginRequest request) {
|
||||||
|
return repository.login(request);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### و) تسجيل في `core/di/injection_container.dart`
|
||||||
|
```dart
|
||||||
|
// Data source
|
||||||
|
sl.registerLazySingleton<AuthRemoteDataSource>(
|
||||||
|
() => AuthRemoteDataSourceImpl(apiClient: sl()),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Repository
|
||||||
|
sl.registerLazySingleton<AuthRepository>(
|
||||||
|
() => AuthRepositoryImpl(remoteDataSource: sl()),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Use case
|
||||||
|
sl.registerLazySingleton(() => LoginUseCase(repository: sl()));
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. استخدام في BLoC
|
||||||
|
```dart
|
||||||
|
class LoginBloc extends Bloc<LoginEvent, LoginState> {
|
||||||
|
final LoginUseCase loginUseCase;
|
||||||
|
|
||||||
|
LoginBloc({required this.loginUseCase}) : super(LoginInitial()) {
|
||||||
|
on<LoginSubmitted>(_onLoginSubmitted);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _onLoginSubmitted(
|
||||||
|
LoginSubmitted event,
|
||||||
|
Emitter<LoginState> emit,
|
||||||
|
) async {
|
||||||
|
emit(LoginLoading());
|
||||||
|
final result = await loginUseCase(event.request);
|
||||||
|
result.fold(
|
||||||
|
(failure) => emit(LoginError(failure.message)),
|
||||||
|
(response) => emit(LoginSuccess(response)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## الحزم المستخدمة
|
||||||
|
|
||||||
|
- `dio`: للاتصال بالـ API
|
||||||
|
- `get_it`: لإدارة Dependency Injection
|
||||||
|
- `dartz`: لاستخدام `Either` للتعامل مع الأخطاء
|
||||||
|
- `equatable`: للمساواة بين الكائنات
|
||||||
|
- `shared_preferences`: للتخزين المحلي
|
||||||
|
|
||||||
|
## ملاحظات مهمة
|
||||||
|
|
||||||
|
1. **تحديث baseUrl**: قم بتحديث `baseUrl` في `core/network/api_client.dart`
|
||||||
|
2. **إضافة Token**: يمكن إضافة interceptor في `ApiClient` لإضافة token تلقائياً
|
||||||
|
3. **معالجة الأخطاء**: جميع الأخطاء تمر عبر `Exceptions` ثم `Failures`
|
||||||
|
4. **التحويل**: DTOs للـ API، Models للـ Domain
|
||||||
113
lib/LOGIN_SETUP.md
Normal file
113
lib/LOGIN_SETUP.md
Normal file
@@ -0,0 +1,113 @@
|
|||||||
|
# إعداد تسجيل الدخول (Login Setup)
|
||||||
|
|
||||||
|
تم ربط عملية تسجيل الدخول مع السيرفر بنجاح.
|
||||||
|
|
||||||
|
## الملفات المُنشأة
|
||||||
|
|
||||||
|
### 1. Data Layer
|
||||||
|
- `data/dto/login_dto.dart` - DTO لإرسال بيانات تسجيل الدخول
|
||||||
|
- `data/dto/login_response_dto.dart` - DTO لاستقبال استجابة تسجيل الدخول
|
||||||
|
- `data/datasources/auth_remote_data_source.dart` - مصدر البيانات البعيدة لتسجيل الدخول
|
||||||
|
|
||||||
|
### 2. Domain Layer
|
||||||
|
- `domain/models/login_request.dart` - نموذج طلب تسجيل الدخول
|
||||||
|
- `domain/models/login_response_model.dart` - نموذج استجابة تسجيل الدخول
|
||||||
|
- `domain/repositories/auth_repository.dart` - واجهة Repository
|
||||||
|
- `domain/usecases/login_usecase.dart` - Use Case لتسجيل الدخول
|
||||||
|
|
||||||
|
### 3. Data Implementation
|
||||||
|
- `data/repositories/auth_repository_impl.dart` - تطبيق Repository
|
||||||
|
|
||||||
|
### 4. Core Updates
|
||||||
|
- `core/network/api_client.dart` - تم تحديثه لإضافة token تلقائياً في الطلبات
|
||||||
|
- `core/di/injection_container.dart` - تم تسجيل جميع التبعيات
|
||||||
|
|
||||||
|
### 5. UI Updates
|
||||||
|
- `widgets/auth_form.dart` - تم تحديثه لاستخدام LoginUseCase
|
||||||
|
|
||||||
|
## كيفية الاستخدام
|
||||||
|
|
||||||
|
### في الكود:
|
||||||
|
```dart
|
||||||
|
// الحصول على LoginUseCase من dependency injection
|
||||||
|
final loginUseCase = sl<LoginUseCase>();
|
||||||
|
|
||||||
|
// إنشاء طلب تسجيل الدخول
|
||||||
|
final request = LoginRequest(
|
||||||
|
phoneNumber: '7856121557',
|
||||||
|
password: 'qaqaqa',
|
||||||
|
);
|
||||||
|
|
||||||
|
// استدعاء UseCase
|
||||||
|
final result = await loginUseCase(request);
|
||||||
|
|
||||||
|
// التعامل مع النتيجة
|
||||||
|
result.fold(
|
||||||
|
(failure) {
|
||||||
|
// معالجة الخطأ
|
||||||
|
print('خطأ: ${failure.message}');
|
||||||
|
},
|
||||||
|
(response) {
|
||||||
|
// معالجة النجاح
|
||||||
|
if (response.isSuccess) {
|
||||||
|
print('تم تسجيل الدخول بنجاح');
|
||||||
|
print('Token: ${response.data?.token}');
|
||||||
|
print('اسم المستخدم: ${response.data?.fullName}');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
## API Endpoint
|
||||||
|
|
||||||
|
- **URL**: `https://hrm.go.iq/api/Auth/login`
|
||||||
|
- **Method**: POST
|
||||||
|
- **Headers**:
|
||||||
|
- `Content-Type: application/json`
|
||||||
|
- `accept: text/plain`
|
||||||
|
|
||||||
|
### Request Body:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"phoneNumber": "7856121557",
|
||||||
|
"password": "qaqaqa"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Response:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"statusCode": 200,
|
||||||
|
"isSuccess": true,
|
||||||
|
"message": "Login Successful",
|
||||||
|
"data": {
|
||||||
|
"token": "...",
|
||||||
|
"id": "...",
|
||||||
|
"username": "...",
|
||||||
|
"fullName": "...",
|
||||||
|
"role": "...",
|
||||||
|
"email": "...",
|
||||||
|
"phoneNumber": "...",
|
||||||
|
"permissions": [...]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## المميزات
|
||||||
|
|
||||||
|
1. ✅ حفظ Token تلقائياً في SharedPreferences
|
||||||
|
2. ✅ إضافة Token تلقائياً في جميع الطلبات عبر ApiClient interceptor
|
||||||
|
3. ✅ معالجة الأخطاء بشكل شامل (Network, Server, Validation)
|
||||||
|
4. ✅ رسائل خطأ بالعربية
|
||||||
|
5. ✅ Loading state في واجهة المستخدم
|
||||||
|
6. ✅ التحقق من صحة المدخلات
|
||||||
|
|
||||||
|
## الخطوات التالية
|
||||||
|
|
||||||
|
1. قم بتشغيل `flutter pub get` لتثبيت الحزم
|
||||||
|
2. اختبر تسجيل الدخول باستخدام البيانات الصحيحة
|
||||||
|
3. يمكنك إضافة المزيد من الميزات مثل:
|
||||||
|
- حفظ بيانات المستخدم الكاملة
|
||||||
|
- تذكر المستخدم (Remember Me)
|
||||||
|
- تسجيل الخروج (Logout)
|
||||||
|
- تحديث Token تلقائياً عند انتهاء الصلاحية
|
||||||
146
lib/core/constants/dimensions.dart
Normal file
146
lib/core/constants/dimensions.dart
Normal file
@@ -0,0 +1,146 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
class AppDimensions {
|
||||||
|
// Private constructor to prevent instantiation
|
||||||
|
AppDimensions._();
|
||||||
|
|
||||||
|
// Screen breakpoints
|
||||||
|
static const double smallScreenWidth = 460;
|
||||||
|
static const double tabletWidth = 600;
|
||||||
|
static const double largeScreenWidth = 1200;
|
||||||
|
|
||||||
|
// Padding values
|
||||||
|
static const double paddingXS = 4.0;
|
||||||
|
static const double paddingS = 8.0;
|
||||||
|
static const double paddingM = 12.0;
|
||||||
|
static const double paddingL = 16.0;
|
||||||
|
static const double paddingXL = 25.0;
|
||||||
|
static const double paddingXXL = 30.0;
|
||||||
|
static const double paddingXXXL = 30.0;
|
||||||
|
static const double paddingHuge = 40.0;
|
||||||
|
|
||||||
|
// Spacing values
|
||||||
|
static const double spacingXS = 4.0;
|
||||||
|
static const double spacingS = 8.0;
|
||||||
|
static const double spacingM = 12.0;
|
||||||
|
static const double spacingL = 16.0;
|
||||||
|
static const double spacingXL = 20.0;
|
||||||
|
static const double spacingXXL = 23.0;
|
||||||
|
static const double spacingXXXL = 30.0;
|
||||||
|
static const double spacingHuge = 40.0;
|
||||||
|
|
||||||
|
// Font sizes
|
||||||
|
static const double fontSizeXS = 10.0;
|
||||||
|
static const double fontSizeS = 12.0;
|
||||||
|
static const double fontSizeM = 14.0;
|
||||||
|
static const double fontSizeL = 16.0;
|
||||||
|
static const double fontSizeXL = 18.0;
|
||||||
|
static const double fontSizeXXL = 20.0;
|
||||||
|
static const double fontSizeXXXL = 22.0;
|
||||||
|
static const double fontSizeHuge = 24.0;
|
||||||
|
static const double fontSizeTitle = 36.0;
|
||||||
|
static const double fontSizeTitleLarge = 42.0;
|
||||||
|
|
||||||
|
// Icon sizes
|
||||||
|
static const double iconSizeXS = 16.0;
|
||||||
|
static const double iconSizeS = 20.0;
|
||||||
|
static const double iconSizeM = 24.0;
|
||||||
|
static const double iconSizeL = 30.0;
|
||||||
|
static const double iconSizeXL = 33.0;
|
||||||
|
static const double iconSizeXXL = 40.0;
|
||||||
|
static const double iconSizeHuge = 60.0;
|
||||||
|
|
||||||
|
// Border radius
|
||||||
|
static const double radiusXS = 3.0;
|
||||||
|
static const double radiusS = 5.0;
|
||||||
|
static const double radiusM = 8.0;
|
||||||
|
static const double radiusL = 10.0;
|
||||||
|
static const double radiusXL = 15.0;
|
||||||
|
static const double radiusXXL = 20.0;
|
||||||
|
static const double radiusCircle = 50.0;
|
||||||
|
|
||||||
|
// Button heights
|
||||||
|
static const double buttonHeightS = 40.0;
|
||||||
|
static const double buttonHeightM = 48.0;
|
||||||
|
static const double buttonHeightL = 56.0;
|
||||||
|
|
||||||
|
// Image heights
|
||||||
|
static const double imageHeightS = 150.0;
|
||||||
|
static const double imageHeightM = 200.0;
|
||||||
|
static const double imageHeightL = 250.0;
|
||||||
|
|
||||||
|
// Page indicator sizes
|
||||||
|
static const double pageIndicatorSize = 10.0;
|
||||||
|
static const double pageIndicatorSizeActive = 16.0;
|
||||||
|
|
||||||
|
// Responsive methods
|
||||||
|
static bool isSmallScreen(BuildContext context) {
|
||||||
|
return MediaQuery.of(context).size.width < smallScreenWidth;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool isTablet(BuildContext context) {
|
||||||
|
final width = MediaQuery.of(context).size.width;
|
||||||
|
return width >= tabletWidth && width < largeScreenWidth;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool isLargeScreen(BuildContext context) {
|
||||||
|
return MediaQuery.of(context).size.width >= largeScreenWidth;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Responsive padding
|
||||||
|
static double getHorizontalPadding(BuildContext context) {
|
||||||
|
return isSmallScreen(context) ? paddingXL : paddingXXL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static double getVerticalPadding(BuildContext context) {
|
||||||
|
return isSmallScreen(context) ? paddingL : paddingXXL;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Responsive font size
|
||||||
|
static double getTitleFontSize(BuildContext context) {
|
||||||
|
final screenWidth = MediaQuery.of(context).size.width;
|
||||||
|
if (isSmallScreen(context)) {
|
||||||
|
return screenWidth * 0.07;
|
||||||
|
} else if (isTablet(context)) {
|
||||||
|
return fontSizeTitleLarge;
|
||||||
|
} else {
|
||||||
|
return screenWidth * 0.08;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static double getSubtitleFontSize(BuildContext context) {
|
||||||
|
return isSmallScreen(context) ? fontSizeM : fontSizeL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static double getBodyFontSize(BuildContext context) {
|
||||||
|
return isSmallScreen(context) ? fontSizeS : fontSizeM;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Responsive spacing
|
||||||
|
static double getVerticalSpacing(BuildContext context) {
|
||||||
|
return isSmallScreen(context) ? spacingL : spacingXXL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static double getSmallVerticalSpacing(BuildContext context) {
|
||||||
|
return isSmallScreen(context) ? spacingM : spacingL;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Responsive icon size
|
||||||
|
static double getIconSize(BuildContext context) {
|
||||||
|
return isSmallScreen(context) ? iconSizeM : iconSizeXL;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Screen dimensions
|
||||||
|
static double screenWidth(BuildContext context) {
|
||||||
|
return MediaQuery.of(context).size.width;
|
||||||
|
}
|
||||||
|
|
||||||
|
static double screenHeight(BuildContext context) {
|
||||||
|
return MediaQuery.of(context).size.height;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Safe area padding
|
||||||
|
static EdgeInsets getSafeAreaPadding(BuildContext context) {
|
||||||
|
return MediaQuery.of(context).padding;
|
||||||
|
}
|
||||||
|
}
|
||||||
49
lib/core/di/injection_container.dart
Normal file
49
lib/core/di/injection_container.dart
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
import 'package:dio/dio.dart';
|
||||||
|
import 'package:get_it/get_it.dart';
|
||||||
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
|
import '../network/api_client.dart';
|
||||||
|
import '../../data/datasources/auth_remote_data_source.dart';
|
||||||
|
import '../../data/datasources/user_local_data_source.dart';
|
||||||
|
import '../../data/repositories/auth_repository_impl.dart';
|
||||||
|
import '../../domain/repositories/auth_repository.dart';
|
||||||
|
import '../../domain/usecases/login_usecase.dart';
|
||||||
|
|
||||||
|
final sl = GetIt.instance;
|
||||||
|
|
||||||
|
Future<void> initializeDependencies() async {
|
||||||
|
// External
|
||||||
|
sl.registerLazySingleton<Dio>(() => Dio());
|
||||||
|
|
||||||
|
// SharedPreferences
|
||||||
|
final sharedPreferences = await SharedPreferences.getInstance();
|
||||||
|
sl.registerLazySingleton<SharedPreferences>(() => sharedPreferences);
|
||||||
|
|
||||||
|
// Core
|
||||||
|
sl.registerLazySingleton<ApiClient>(
|
||||||
|
() => ApiClient(dio: sl(), sharedPreferences: sl()),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Data sources
|
||||||
|
sl.registerLazySingleton<AuthRemoteDataSource>(
|
||||||
|
() => AuthRemoteDataSourceImpl(apiClient: sl()),
|
||||||
|
);
|
||||||
|
|
||||||
|
sl.registerLazySingleton<UserLocalDataSource>(
|
||||||
|
() => UserLocalDataSourceImpl(sharedPreferences: sl()),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Repositories
|
||||||
|
sl.registerLazySingleton<AuthRepository>(
|
||||||
|
() => AuthRepositoryImpl(
|
||||||
|
remoteDataSource: sl(),
|
||||||
|
localDataSource: sl(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Use cases
|
||||||
|
sl.registerLazySingleton(() => LoginUseCase(repository: sl()));
|
||||||
|
|
||||||
|
// Blocs will be registered here
|
||||||
|
// Example:
|
||||||
|
// sl.registerFactory(() => LoginBloc(loginUseCase: sl()));
|
||||||
|
}
|
||||||
7
lib/core/enums/app_enums.dart
Normal file
7
lib/core/enums/app_enums.dart
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
// Add your app-specific enums here
|
||||||
|
// Example:
|
||||||
|
// enum UserRole {
|
||||||
|
// admin,
|
||||||
|
// user,
|
||||||
|
// guest,
|
||||||
|
// }
|
||||||
18
lib/core/error/exceptions.dart
Normal file
18
lib/core/error/exceptions.dart
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
class ServerException implements Exception {
|
||||||
|
final String message;
|
||||||
|
final int? statusCode;
|
||||||
|
|
||||||
|
ServerException({required this.message, this.statusCode});
|
||||||
|
}
|
||||||
|
|
||||||
|
class NetworkException implements Exception {
|
||||||
|
final String message;
|
||||||
|
|
||||||
|
NetworkException({required this.message});
|
||||||
|
}
|
||||||
|
|
||||||
|
class ValidationException implements Exception {
|
||||||
|
final String message;
|
||||||
|
|
||||||
|
ValidationException({required this.message});
|
||||||
|
}
|
||||||
22
lib/core/error/failures.dart
Normal file
22
lib/core/error/failures.dart
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
import 'package:equatable/equatable.dart';
|
||||||
|
|
||||||
|
abstract class Failure extends Equatable {
|
||||||
|
final String message;
|
||||||
|
|
||||||
|
const Failure(this.message);
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object> get props => [message];
|
||||||
|
}
|
||||||
|
|
||||||
|
class ServerFailure extends Failure {
|
||||||
|
const ServerFailure(super.message);
|
||||||
|
}
|
||||||
|
|
||||||
|
class NetworkFailure extends Failure {
|
||||||
|
const NetworkFailure(super.message);
|
||||||
|
}
|
||||||
|
|
||||||
|
class ValidationFailure extends Failure {
|
||||||
|
const ValidationFailure(super.message);
|
||||||
|
}
|
||||||
120
lib/core/network/api_client.dart
Normal file
120
lib/core/network/api_client.dart
Normal file
@@ -0,0 +1,120 @@
|
|||||||
|
import 'package:dio/dio.dart';
|
||||||
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
|
|
||||||
|
class ApiClient {
|
||||||
|
final Dio dio;
|
||||||
|
final SharedPreferences? sharedPreferences;
|
||||||
|
static const String baseUrl = 'https://hrm.go.iq/api';
|
||||||
|
static const String _tokenKey = 'user_token';
|
||||||
|
|
||||||
|
ApiClient({required this.dio, this.sharedPreferences}) {
|
||||||
|
dio.options = BaseOptions(
|
||||||
|
baseUrl: baseUrl,
|
||||||
|
connectTimeout: const Duration(seconds: 30),
|
||||||
|
receiveTimeout: const Duration(seconds: 30),
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'accept': 'text/plain',
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
// Add interceptor to add token to requests
|
||||||
|
dio.interceptors.add(
|
||||||
|
InterceptorsWrapper(
|
||||||
|
onRequest: (options, handler) async {
|
||||||
|
// Get token from SharedPreferences
|
||||||
|
final token = sharedPreferences?.getString(_tokenKey);
|
||||||
|
if (token != null && token.isNotEmpty) {
|
||||||
|
options.headers['Authorization'] = 'Bearer $token';
|
||||||
|
}
|
||||||
|
return handler.next(options);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Add interceptors for logging and error handling
|
||||||
|
dio.interceptors.add(
|
||||||
|
LogInterceptor(
|
||||||
|
requestBody: true,
|
||||||
|
responseBody: true,
|
||||||
|
error: true,
|
||||||
|
requestHeader: true,
|
||||||
|
responseHeader: false,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<Response> post(
|
||||||
|
String path, {
|
||||||
|
dynamic data,
|
||||||
|
Map<String, dynamic>? queryParameters,
|
||||||
|
Options? options,
|
||||||
|
}) async {
|
||||||
|
try {
|
||||||
|
final response = await dio.post(
|
||||||
|
path,
|
||||||
|
data: data,
|
||||||
|
queryParameters: queryParameters,
|
||||||
|
options: options,
|
||||||
|
);
|
||||||
|
return response;
|
||||||
|
} catch (e) {
|
||||||
|
rethrow;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<Response> get(
|
||||||
|
String path, {
|
||||||
|
Map<String, dynamic>? queryParameters,
|
||||||
|
Options? options,
|
||||||
|
}) async {
|
||||||
|
try {
|
||||||
|
final response = await dio.get(
|
||||||
|
path,
|
||||||
|
queryParameters: queryParameters,
|
||||||
|
options: options,
|
||||||
|
);
|
||||||
|
return response;
|
||||||
|
} catch (e) {
|
||||||
|
rethrow;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<Response> put(
|
||||||
|
String path, {
|
||||||
|
dynamic data,
|
||||||
|
Map<String, dynamic>? queryParameters,
|
||||||
|
Options? options,
|
||||||
|
}) async {
|
||||||
|
try {
|
||||||
|
final response = await dio.put(
|
||||||
|
path,
|
||||||
|
data: data,
|
||||||
|
queryParameters: queryParameters,
|
||||||
|
options: options,
|
||||||
|
);
|
||||||
|
return response;
|
||||||
|
} catch (e) {
|
||||||
|
rethrow;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<Response> delete(
|
||||||
|
String path, {
|
||||||
|
dynamic data,
|
||||||
|
Map<String, dynamic>? queryParameters,
|
||||||
|
Options? options,
|
||||||
|
}) async {
|
||||||
|
try {
|
||||||
|
final response = await dio.delete(
|
||||||
|
path,
|
||||||
|
data: data,
|
||||||
|
queryParameters: queryParameters,
|
||||||
|
options: options,
|
||||||
|
);
|
||||||
|
return response;
|
||||||
|
} catch (e) {
|
||||||
|
rethrow;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
37
lib/core/utils/validators.dart
Normal file
37
lib/core/utils/validators.dart
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
class Validators {
|
||||||
|
static String? validateEmail(String? value) {
|
||||||
|
if (value == null || value.isEmpty) {
|
||||||
|
return 'البريد الإلكتروني مطلوب';
|
||||||
|
}
|
||||||
|
final emailRegex = RegExp(r'^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$');
|
||||||
|
if (!emailRegex.hasMatch(value)) {
|
||||||
|
return 'البريد الإلكتروني غير صحيح';
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
static String? validatePhone(String? value) {
|
||||||
|
if (value == null || value.isEmpty) {
|
||||||
|
return 'رقم الهاتف مطلوب';
|
||||||
|
}
|
||||||
|
// Add your phone validation logic here
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
static String? validateRequired(String? value, String fieldName) {
|
||||||
|
if (value == null || value.isEmpty) {
|
||||||
|
return '$fieldName مطلوب';
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
static String? validatePassword(String? value) {
|
||||||
|
if (value == null || value.isEmpty) {
|
||||||
|
return 'كلمة المرور مطلوبة';
|
||||||
|
}
|
||||||
|
if (value.length < 6) {
|
||||||
|
return 'كلمة المرور يجب أن تكون 6 أحرف على الأقل';
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
25
lib/data/datasources/.gitkeep
Normal file
25
lib/data/datasources/.gitkeep
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
# Data sources directory
|
||||||
|
# Create your remote data sources here following this pattern:
|
||||||
|
#
|
||||||
|
# abstract class YourRemoteDataSource {
|
||||||
|
# Future<YourDto> yourMethod(YourRequest request);
|
||||||
|
# }
|
||||||
|
#
|
||||||
|
# class YourRemoteDataSourceImpl implements YourRemoteDataSource {
|
||||||
|
# final ApiClient apiClient;
|
||||||
|
#
|
||||||
|
# YourRemoteDataSourceImpl({required this.apiClient});
|
||||||
|
#
|
||||||
|
# @override
|
||||||
|
# Future<YourDto> yourMethod(YourRequest request) async {
|
||||||
|
# try {
|
||||||
|
# final response = await apiClient.post(
|
||||||
|
# '/your-endpoint',
|
||||||
|
# data: request.toJson(),
|
||||||
|
# );
|
||||||
|
# // Handle response and return DTO
|
||||||
|
# } on DioException catch (e) {
|
||||||
|
# // Handle errors
|
||||||
|
# }
|
||||||
|
# }
|
||||||
|
# }
|
||||||
77
lib/data/datasources/auth_remote_data_source.dart
Normal file
77
lib/data/datasources/auth_remote_data_source.dart
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
import 'package:dio/dio.dart';
|
||||||
|
import '../../core/error/exceptions.dart';
|
||||||
|
import '../../core/network/api_client.dart';
|
||||||
|
import '../dto/login_dto.dart';
|
||||||
|
import '../dto/login_response_dto.dart';
|
||||||
|
|
||||||
|
abstract class AuthRemoteDataSource {
|
||||||
|
Future<LoginResponseDto> login(LoginDto dto);
|
||||||
|
}
|
||||||
|
|
||||||
|
class AuthRemoteDataSourceImpl implements AuthRemoteDataSource {
|
||||||
|
final ApiClient apiClient;
|
||||||
|
|
||||||
|
AuthRemoteDataSourceImpl({required this.apiClient});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<LoginResponseDto> login(LoginDto dto) async {
|
||||||
|
try {
|
||||||
|
final response = await apiClient.post(
|
||||||
|
'/Auth/login',
|
||||||
|
data: dto.toJson(),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (response.statusCode == 200 || response.statusCode == 201) {
|
||||||
|
final responseData = response.data;
|
||||||
|
|
||||||
|
if (responseData is Map<String, dynamic>) {
|
||||||
|
return LoginResponseDto.fromJson(responseData);
|
||||||
|
} else {
|
||||||
|
throw ServerException(
|
||||||
|
message: 'استجابة غير صحيحة من الخادم',
|
||||||
|
statusCode: response.statusCode,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw ServerException(
|
||||||
|
message: 'فشل تسجيل الدخول',
|
||||||
|
statusCode: response.statusCode,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} on DioException catch (e) {
|
||||||
|
if (e.type == DioExceptionType.connectionTimeout ||
|
||||||
|
e.type == DioExceptionType.receiveTimeout) {
|
||||||
|
throw NetworkException(message: 'انتهت مهلة الاتصال');
|
||||||
|
} else if (e.type == DioExceptionType.connectionError) {
|
||||||
|
throw NetworkException(message: 'لا يوجد اتصال بالانترنيت');
|
||||||
|
} else if (e.response?.statusCode == 500) {
|
||||||
|
throw ServerException(message: 'خطأ في الخادم يرجى المحاولة لاحقا');
|
||||||
|
} else if (e.response != null) {
|
||||||
|
final message = e.response?.data?['message'] ??
|
||||||
|
e.response?.data?['error'] ??
|
||||||
|
'فشل تسجيل الدخول';
|
||||||
|
|
||||||
|
// Check for invalid credentials
|
||||||
|
final customMessage =
|
||||||
|
message.toString().toLowerCase().contains('invalid') ||
|
||||||
|
message.toString().toLowerCase().contains('incorrect')
|
||||||
|
? 'رقم الهاتف أو كلمة المرور غير صحيحة'
|
||||||
|
: message.toString().toLowerCase().contains('not found')
|
||||||
|
? 'المستخدم غير موجود'
|
||||||
|
: message;
|
||||||
|
|
||||||
|
throw ServerException(
|
||||||
|
message: customMessage,
|
||||||
|
statusCode: e.response?.statusCode,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
throw NetworkException(message: 'خطأ في الانترنيت يرجى المحاولة لاحقا');
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
if (e is ServerException || e is NetworkException) {
|
||||||
|
rethrow;
|
||||||
|
}
|
||||||
|
throw ServerException(message: 'خطأ غير متوقع');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
29
lib/data/datasources/user_local_data_source.dart
Normal file
29
lib/data/datasources/user_local_data_source.dart
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
|
|
||||||
|
abstract class UserLocalDataSource {
|
||||||
|
Future<void> cacheUserToken(String token);
|
||||||
|
Future<String?> getCachedUserToken();
|
||||||
|
Future<void> clearCache();
|
||||||
|
}
|
||||||
|
|
||||||
|
class UserLocalDataSourceImpl implements UserLocalDataSource {
|
||||||
|
final SharedPreferences sharedPreferences;
|
||||||
|
static const String _tokenKey = 'user_token';
|
||||||
|
|
||||||
|
UserLocalDataSourceImpl({required this.sharedPreferences});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> cacheUserToken(String token) async {
|
||||||
|
await sharedPreferences.setString(_tokenKey, token);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<String?> getCachedUserToken() async {
|
||||||
|
return sharedPreferences.getString(_tokenKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> clearCache() async {
|
||||||
|
await sharedPreferences.remove(_tokenKey);
|
||||||
|
}
|
||||||
|
}
|
||||||
24
lib/data/dto/.gitkeep
Normal file
24
lib/data/dto/.gitkeep
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
# DTO (Data Transfer Objects) directory
|
||||||
|
# Create your DTOs here for API request/response mapping
|
||||||
|
# Example:
|
||||||
|
#
|
||||||
|
# class LoginDto {
|
||||||
|
# final String phoneNumber;
|
||||||
|
# final String password;
|
||||||
|
#
|
||||||
|
# LoginDto({required this.phoneNumber, required this.password});
|
||||||
|
#
|
||||||
|
# Map<String, dynamic> toJson() {
|
||||||
|
# return {
|
||||||
|
# 'phoneNumber': phoneNumber,
|
||||||
|
# 'password': password,
|
||||||
|
# };
|
||||||
|
# }
|
||||||
|
#
|
||||||
|
# factory LoginDto.fromJson(Map<String, dynamic> json) {
|
||||||
|
# return LoginDto(
|
||||||
|
# phoneNumber: json['phoneNumber'],
|
||||||
|
# password: json['password'],
|
||||||
|
# );
|
||||||
|
# }
|
||||||
|
# }
|
||||||
16
lib/data/dto/login_dto.dart
Normal file
16
lib/data/dto/login_dto.dart
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
class LoginDto {
|
||||||
|
final String phoneNumber;
|
||||||
|
final String password;
|
||||||
|
|
||||||
|
LoginDto({
|
||||||
|
required this.phoneNumber,
|
||||||
|
required this.password,
|
||||||
|
});
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
return {
|
||||||
|
'phoneNumber': phoneNumber,
|
||||||
|
'password': password,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
85
lib/data/dto/login_response_dto.dart
Normal file
85
lib/data/dto/login_response_dto.dart
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
class LoginResponseDto {
|
||||||
|
final int statusCode;
|
||||||
|
final bool isSuccess;
|
||||||
|
final String message;
|
||||||
|
final LoginDataDto? data;
|
||||||
|
|
||||||
|
LoginResponseDto({
|
||||||
|
required this.statusCode,
|
||||||
|
required this.isSuccess,
|
||||||
|
required this.message,
|
||||||
|
this.data,
|
||||||
|
});
|
||||||
|
|
||||||
|
factory LoginResponseDto.fromJson(Map<String, dynamic> json) {
|
||||||
|
return LoginResponseDto(
|
||||||
|
statusCode: json['statusCode'] ?? 0,
|
||||||
|
isSuccess: json['isSuccess'] ?? false,
|
||||||
|
message: json['message'] ?? '',
|
||||||
|
data: json['data'] != null ? LoginDataDto.fromJson(json['data']) : null,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
return {
|
||||||
|
'statusCode': statusCode,
|
||||||
|
'isSuccess': isSuccess,
|
||||||
|
'message': message,
|
||||||
|
'data': data?.toJson(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class LoginDataDto {
|
||||||
|
final String? token;
|
||||||
|
final String? id;
|
||||||
|
final String? employeeId;
|
||||||
|
final String? username;
|
||||||
|
final String? fullName;
|
||||||
|
final String? role;
|
||||||
|
final String? email;
|
||||||
|
final String? phoneNumber;
|
||||||
|
final List<String>? permissions;
|
||||||
|
|
||||||
|
LoginDataDto({
|
||||||
|
this.token,
|
||||||
|
this.id,
|
||||||
|
this.employeeId,
|
||||||
|
this.username,
|
||||||
|
this.fullName,
|
||||||
|
this.role,
|
||||||
|
this.email,
|
||||||
|
this.phoneNumber,
|
||||||
|
this.permissions,
|
||||||
|
});
|
||||||
|
|
||||||
|
factory LoginDataDto.fromJson(Map<String, dynamic> json) {
|
||||||
|
return LoginDataDto(
|
||||||
|
token: json['token'],
|
||||||
|
id: json['id'],
|
||||||
|
employeeId: json['employeeId'],
|
||||||
|
username: json['username'],
|
||||||
|
fullName: json['fullName'],
|
||||||
|
role: json['role'],
|
||||||
|
email: json['email'],
|
||||||
|
phoneNumber: json['phoneNumber'],
|
||||||
|
permissions: json['permissions'] != null
|
||||||
|
? List<String>.from(json['permissions'])
|
||||||
|
: null,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
return {
|
||||||
|
'token': token,
|
||||||
|
'id': id,
|
||||||
|
'employeeId': employeeId,
|
||||||
|
'username': username,
|
||||||
|
'fullName': fullName,
|
||||||
|
'role': role,
|
||||||
|
'email': email,
|
||||||
|
'phoneNumber': phoneNumber,
|
||||||
|
'permissions': permissions,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
27
lib/data/repositories/.gitkeep
Normal file
27
lib/data/repositories/.gitkeep
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
# Repository implementations directory
|
||||||
|
# Create your repository implementations here
|
||||||
|
# Example:
|
||||||
|
#
|
||||||
|
# class AuthRepositoryImpl implements AuthRepository {
|
||||||
|
# final AuthRemoteDataSource remoteDataSource;
|
||||||
|
# final UserLocalDataSource localDataSource;
|
||||||
|
#
|
||||||
|
# AuthRepositoryImpl({
|
||||||
|
# required this.remoteDataSource,
|
||||||
|
# required this.localDataSource,
|
||||||
|
# });
|
||||||
|
#
|
||||||
|
# @override
|
||||||
|
# Future<Either<Failure, LoginResponseModel>> login(LoginRequest request) async {
|
||||||
|
# try {
|
||||||
|
# final dto = LoginDto(...);
|
||||||
|
# final responseDto = await remoteDataSource.login(dto);
|
||||||
|
# final responseModel = _convertDtoToModel(responseDto);
|
||||||
|
# return Right(responseModel);
|
||||||
|
# } on ServerException catch (e) {
|
||||||
|
# return Left(ServerFailure(e.message));
|
||||||
|
# } on NetworkException catch (e) {
|
||||||
|
# return Left(NetworkFailure(e.message));
|
||||||
|
# }
|
||||||
|
# }
|
||||||
|
# }
|
||||||
65
lib/data/repositories/auth_repository_impl.dart
Normal file
65
lib/data/repositories/auth_repository_impl.dart
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
import 'package:dartz/dartz.dart';
|
||||||
|
import '../../core/error/exceptions.dart';
|
||||||
|
import '../../core/error/failures.dart';
|
||||||
|
import '../datasources/auth_remote_data_source.dart';
|
||||||
|
import '../datasources/user_local_data_source.dart';
|
||||||
|
import '../dto/login_dto.dart';
|
||||||
|
import '../dto/login_response_dto.dart';
|
||||||
|
import '../../domain/models/login_request.dart';
|
||||||
|
import '../../domain/models/login_response_model.dart';
|
||||||
|
import '../../domain/repositories/auth_repository.dart';
|
||||||
|
|
||||||
|
class AuthRepositoryImpl implements AuthRepository {
|
||||||
|
final AuthRemoteDataSource remoteDataSource;
|
||||||
|
final UserLocalDataSource localDataSource;
|
||||||
|
|
||||||
|
AuthRepositoryImpl({
|
||||||
|
required this.remoteDataSource,
|
||||||
|
required this.localDataSource,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<Either<Failure, LoginResponseModel>> login(LoginRequest request) async {
|
||||||
|
try {
|
||||||
|
final dto = LoginDto(
|
||||||
|
phoneNumber: request.phoneNumber,
|
||||||
|
password: request.password,
|
||||||
|
);
|
||||||
|
|
||||||
|
final responseDto = await remoteDataSource.login(dto);
|
||||||
|
|
||||||
|
// Cache the token locally
|
||||||
|
if (responseDto.data?.token != null) {
|
||||||
|
await localDataSource.cacheUserToken(responseDto.data!.token!);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert DTO to Model
|
||||||
|
final responseModel = LoginResponseModel(
|
||||||
|
statusCode: responseDto.statusCode,
|
||||||
|
isSuccess: responseDto.isSuccess,
|
||||||
|
message: responseDto.message,
|
||||||
|
data: responseDto.data != null
|
||||||
|
? LoginDataModel(
|
||||||
|
token: responseDto.data!.token,
|
||||||
|
id: responseDto.data!.id,
|
||||||
|
employeeId: responseDto.data!.employeeId,
|
||||||
|
username: responseDto.data!.username,
|
||||||
|
fullName: responseDto.data!.fullName,
|
||||||
|
role: responseDto.data!.role,
|
||||||
|
email: responseDto.data!.email,
|
||||||
|
phoneNumber: responseDto.data!.phoneNumber,
|
||||||
|
permissions: responseDto.data!.permissions,
|
||||||
|
)
|
||||||
|
: null,
|
||||||
|
);
|
||||||
|
|
||||||
|
return Right(responseModel);
|
||||||
|
} on ServerException catch (e) {
|
||||||
|
return Left(ServerFailure(e.message));
|
||||||
|
} on NetworkException catch (e) {
|
||||||
|
return Left(NetworkFailure(e.message));
|
||||||
|
} catch (e) {
|
||||||
|
return Left(ServerFailure('خطأ غير متوقع: $e'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
3
lib/domain/models/.gitkeep
Normal file
3
lib/domain/models/.gitkeep
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
# Domain models directory
|
||||||
|
# Create your domain models here (business logic models)
|
||||||
|
# These are different from DTOs - they represent your app's domain entities
|
||||||
9
lib/domain/models/login_request.dart
Normal file
9
lib/domain/models/login_request.dart
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
class LoginRequest {
|
||||||
|
final String phoneNumber;
|
||||||
|
final String password;
|
||||||
|
|
||||||
|
LoginRequest({
|
||||||
|
required this.phoneNumber,
|
||||||
|
required this.password,
|
||||||
|
});
|
||||||
|
}
|
||||||
37
lib/domain/models/login_response_model.dart
Normal file
37
lib/domain/models/login_response_model.dart
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
class LoginResponseModel {
|
||||||
|
final int statusCode;
|
||||||
|
final bool isSuccess;
|
||||||
|
final String message;
|
||||||
|
final LoginDataModel? data;
|
||||||
|
|
||||||
|
LoginResponseModel({
|
||||||
|
required this.statusCode,
|
||||||
|
required this.isSuccess,
|
||||||
|
required this.message,
|
||||||
|
this.data,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
class LoginDataModel {
|
||||||
|
final String? token;
|
||||||
|
final String? id;
|
||||||
|
final String? employeeId;
|
||||||
|
final String? username;
|
||||||
|
final String? fullName;
|
||||||
|
final String? role;
|
||||||
|
final String? email;
|
||||||
|
final String? phoneNumber;
|
||||||
|
final List<String>? permissions;
|
||||||
|
|
||||||
|
LoginDataModel({
|
||||||
|
this.token,
|
||||||
|
this.id,
|
||||||
|
this.employeeId,
|
||||||
|
this.username,
|
||||||
|
this.fullName,
|
||||||
|
this.role,
|
||||||
|
this.email,
|
||||||
|
this.phoneNumber,
|
||||||
|
this.permissions,
|
||||||
|
});
|
||||||
|
}
|
||||||
8
lib/domain/repositories/.gitkeep
Normal file
8
lib/domain/repositories/.gitkeep
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
# Repository interfaces directory
|
||||||
|
# Create your repository interfaces here
|
||||||
|
# Example:
|
||||||
|
#
|
||||||
|
# abstract class AuthRepository {
|
||||||
|
# Future<Either<Failure, LoginResponseModel>> login(LoginRequest request);
|
||||||
|
# Future<Either<Failure, RegisterResponseModel>> register(RegisterRequest request);
|
||||||
|
# }
|
||||||
8
lib/domain/repositories/auth_repository.dart
Normal file
8
lib/domain/repositories/auth_repository.dart
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
import 'package:dartz/dartz.dart';
|
||||||
|
import '../../core/error/failures.dart';
|
||||||
|
import '../models/login_request.dart';
|
||||||
|
import '../models/login_response_model.dart';
|
||||||
|
|
||||||
|
abstract class AuthRepository {
|
||||||
|
Future<Either<Failure, LoginResponseModel>> login(LoginRequest request);
|
||||||
|
}
|
||||||
13
lib/domain/usecases/.gitkeep
Normal file
13
lib/domain/usecases/.gitkeep
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
# Use cases directory
|
||||||
|
# Create your use cases here (business logic)
|
||||||
|
# Example:
|
||||||
|
#
|
||||||
|
# class LoginUseCase {
|
||||||
|
# final AuthRepository repository;
|
||||||
|
#
|
||||||
|
# LoginUseCase({required this.repository});
|
||||||
|
#
|
||||||
|
# Future<Either<Failure, LoginResponseModel>> call(LoginRequest request) {
|
||||||
|
# return repository.login(request);
|
||||||
|
# }
|
||||||
|
# }
|
||||||
15
lib/domain/usecases/login_usecase.dart
Normal file
15
lib/domain/usecases/login_usecase.dart
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
import 'package:dartz/dartz.dart';
|
||||||
|
import '../../core/error/failures.dart';
|
||||||
|
import '../models/login_request.dart';
|
||||||
|
import '../models/login_response_model.dart';
|
||||||
|
import '../repositories/auth_repository.dart';
|
||||||
|
|
||||||
|
class LoginUseCase {
|
||||||
|
final AuthRepository repository;
|
||||||
|
|
||||||
|
LoginUseCase({required this.repository});
|
||||||
|
|
||||||
|
Future<Either<Failure, LoginResponseModel>> call(LoginRequest request) {
|
||||||
|
return repository.login(request);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,11 +1,16 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_native_splash/flutter_native_splash.dart';
|
import 'package:flutter_native_splash/flutter_native_splash.dart';
|
||||||
|
|
||||||
|
import 'core/di/injection_container.dart';
|
||||||
import 'screens/splash_screen.dart';
|
import 'screens/splash_screen.dart';
|
||||||
|
|
||||||
void main() {
|
void main() async {
|
||||||
WidgetsBinding widgetsBinding = WidgetsFlutterBinding.ensureInitialized();
|
WidgetsBinding widgetsBinding = WidgetsFlutterBinding.ensureInitialized();
|
||||||
FlutterNativeSplash.preserve(widgetsBinding: widgetsBinding);
|
FlutterNativeSplash.preserve(widgetsBinding: widgetsBinding);
|
||||||
|
|
||||||
|
// Initialize dependency injection
|
||||||
|
await initializeDependencies();
|
||||||
|
|
||||||
runApp(const CodaApp());
|
runApp(const CodaApp());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
6
lib/presentation/blocs/.gitkeep
Normal file
6
lib/presentation/blocs/.gitkeep
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
# BLoC directory
|
||||||
|
# Create your BLoCs here for state management
|
||||||
|
# Each BLoC should have its own folder with:
|
||||||
|
# - bloc_name_bloc.dart
|
||||||
|
# - bloc_name_event.dart
|
||||||
|
# - bloc_name_state.dart
|
||||||
@@ -1,5 +1,8 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import '../screens/main_screen.dart';
|
import '../screens/main_screen.dart';
|
||||||
|
import '../core/di/injection_container.dart';
|
||||||
|
import '../domain/usecases/login_usecase.dart';
|
||||||
|
import '../domain/models/login_request.dart';
|
||||||
import 'onboarding_button.dart';
|
import 'onboarding_button.dart';
|
||||||
|
|
||||||
class AuthForm extends StatefulWidget {
|
class AuthForm extends StatefulWidget {
|
||||||
@@ -13,40 +16,103 @@ class AuthForm extends StatefulWidget {
|
|||||||
|
|
||||||
class _AuthFormState extends State<AuthForm> {
|
class _AuthFormState extends State<AuthForm> {
|
||||||
bool _obscure = true;
|
bool _obscure = true;
|
||||||
|
bool _isLoading = false;
|
||||||
|
|
||||||
|
// Text controllers
|
||||||
|
final TextEditingController _phoneNumberController = TextEditingController();
|
||||||
|
final TextEditingController _passwordController = TextEditingController();
|
||||||
|
|
||||||
// Focus nodes for text fields
|
// Focus nodes for text fields
|
||||||
late FocusNode _usernameFocusNode;
|
late FocusNode _phoneNumberFocusNode;
|
||||||
late FocusNode _passwordFocusNode;
|
late FocusNode _passwordFocusNode;
|
||||||
|
|
||||||
void _handleLogin() {
|
// Get LoginUseCase from dependency injection
|
||||||
|
final LoginUseCase _loginUseCase = sl<LoginUseCase>();
|
||||||
|
|
||||||
|
Future<void> _handleLogin() async {
|
||||||
|
// Validate inputs
|
||||||
|
if (_phoneNumberController.text.trim().isEmpty) {
|
||||||
|
_showError('الرجاء إدخال رقم الهاتف');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_passwordController.text.trim().isEmpty) {
|
||||||
|
_showError('الرجاء إدخال كلمة المرور');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Unfocus any focused text field
|
// Unfocus any focused text field
|
||||||
_usernameFocusNode.unfocus();
|
_phoneNumberFocusNode.unfocus();
|
||||||
_passwordFocusNode.unfocus();
|
_passwordFocusNode.unfocus();
|
||||||
|
|
||||||
// Call the onSubmit callback if provided (for any other logic you might have)
|
setState(() {
|
||||||
|
_isLoading = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
final request = LoginRequest(
|
||||||
|
phoneNumber: _phoneNumberController.text.trim(),
|
||||||
|
password: _passwordController.text.trim(),
|
||||||
|
);
|
||||||
|
|
||||||
|
final result = await _loginUseCase(request);
|
||||||
|
|
||||||
|
result.fold(
|
||||||
|
(failure) {
|
||||||
|
_showError(failure.message);
|
||||||
|
},
|
||||||
|
(response) {
|
||||||
|
if (response.isSuccess) {
|
||||||
|
// Call the onSubmit callback if provided
|
||||||
if (widget.onSubmit != null) {
|
if (widget.onSubmit != null) {
|
||||||
widget.onSubmit!();
|
widget.onSubmit!();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Navigate to the AttendancePage
|
// Navigate to the MainPage
|
||||||
Navigator.pushReplacement(
|
Navigator.pushReplacement(
|
||||||
context,
|
context,
|
||||||
MaterialPageRoute(builder: (context) => const MainPage()),
|
MaterialPageRoute(builder: (context) => const MainPage()),
|
||||||
);
|
);
|
||||||
|
} else {
|
||||||
|
_showError(response.message);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
_showError('حدث خطأ غير متوقع: $e');
|
||||||
|
} finally {
|
||||||
|
if (mounted) {
|
||||||
|
setState(() {
|
||||||
|
_isLoading = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _showError(String message) {
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
SnackBar(
|
||||||
|
content: Text(message),
|
||||||
|
backgroundColor: Colors.red,
|
||||||
|
behavior: SnackBarBehavior.floating,
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
// Initialize focus nodes
|
// Initialize focus nodes
|
||||||
_usernameFocusNode = FocusNode();
|
_phoneNumberFocusNode = FocusNode();
|
||||||
_passwordFocusNode = FocusNode();
|
_passwordFocusNode = FocusNode();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
// Clean up focus nodes when widget is disposed
|
// Clean up controllers and focus nodes
|
||||||
_usernameFocusNode.dispose();
|
_phoneNumberController.dispose();
|
||||||
|
_passwordController.dispose();
|
||||||
|
_phoneNumberFocusNode.dispose();
|
||||||
_passwordFocusNode.dispose();
|
_passwordFocusNode.dispose();
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
@@ -130,11 +196,11 @@ class _AuthFormState extends State<AuthForm> {
|
|||||||
|
|
||||||
SizedBox(height: verticalSpacing),
|
SizedBox(height: verticalSpacing),
|
||||||
|
|
||||||
/// Username Label
|
/// Phone Number Label
|
||||||
Align(
|
Align(
|
||||||
alignment: Alignment.centerRight,
|
alignment: Alignment.centerRight,
|
||||||
child: Text(
|
child: Text(
|
||||||
"اسم المستخدم",
|
"رقم الهاتف",
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: labelFontSize,
|
fontSize: labelFontSize,
|
||||||
color: Colors.black87,
|
color: Colors.black87,
|
||||||
@@ -144,9 +210,11 @@ class _AuthFormState extends State<AuthForm> {
|
|||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
|
|
||||||
_buildField(
|
_buildField(
|
||||||
hint: "اسم المستخدم",
|
controller: _phoneNumberController,
|
||||||
|
hint: "رقم الهاتف",
|
||||||
obscure: false,
|
obscure: false,
|
||||||
focusNode: _usernameFocusNode,
|
keyboardType: TextInputType.phone,
|
||||||
|
focusNode: _phoneNumberFocusNode,
|
||||||
textInputAction: TextInputAction.next,
|
textInputAction: TextInputAction.next,
|
||||||
onSubmitted: (_) {
|
onSubmitted: (_) {
|
||||||
// Move focus to password field when next is pressed
|
// Move focus to password field when next is pressed
|
||||||
@@ -171,14 +239,13 @@ class _AuthFormState extends State<AuthForm> {
|
|||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
|
|
||||||
_buildField(
|
_buildField(
|
||||||
|
controller: _passwordController,
|
||||||
hint: "كلمة المرور",
|
hint: "كلمة المرور",
|
||||||
obscure: _obscure,
|
obscure: _obscure,
|
||||||
hasEye: true,
|
hasEye: true,
|
||||||
focusNode: _passwordFocusNode,
|
focusNode: _passwordFocusNode,
|
||||||
textInputAction: TextInputAction.done,
|
textInputAction: TextInputAction.done,
|
||||||
onSubmitted:
|
onSubmitted: (_) => _handleLogin(),
|
||||||
(_) =>
|
|
||||||
_handleLogin(), // Added parentheses to call the method
|
|
||||||
fontSize: fieldFontSize,
|
fontSize: fieldFontSize,
|
||||||
),
|
),
|
||||||
|
|
||||||
@@ -186,9 +253,9 @@ class _AuthFormState extends State<AuthForm> {
|
|||||||
|
|
||||||
Center(
|
Center(
|
||||||
child: OnboardingButton(
|
child: OnboardingButton(
|
||||||
text: "تسجيل دخول",
|
text: _isLoading ? "جاري تسجيل الدخول..." : "تسجيل دخول",
|
||||||
backgroundColor: const Color.fromARGB(239, 35, 87, 74),
|
backgroundColor: const Color.fromARGB(239, 35, 87, 74),
|
||||||
onPressed: _handleLogin,
|
onPressed: _isLoading ? null : _handleLogin,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
||||||
@@ -203,9 +270,11 @@ class _AuthFormState extends State<AuthForm> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildField({
|
Widget _buildField({
|
||||||
|
TextEditingController? controller,
|
||||||
required String hint,
|
required String hint,
|
||||||
required bool obscure,
|
required bool obscure,
|
||||||
bool hasEye = false,
|
bool hasEye = false,
|
||||||
|
TextInputType? keyboardType,
|
||||||
FocusNode? focusNode,
|
FocusNode? focusNode,
|
||||||
TextInputAction? textInputAction,
|
TextInputAction? textInputAction,
|
||||||
Function(String)? onSubmitted,
|
Function(String)? onSubmitted,
|
||||||
@@ -220,8 +289,10 @@ class _AuthFormState extends State<AuthForm> {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
child: TextField(
|
child: TextField(
|
||||||
|
controller: controller,
|
||||||
focusNode: focusNode,
|
focusNode: focusNode,
|
||||||
obscureText: obscure,
|
obscureText: obscure,
|
||||||
|
keyboardType: keyboardType,
|
||||||
textAlign: TextAlign.right,
|
textAlign: TextAlign.right,
|
||||||
textInputAction: textInputAction,
|
textInputAction: textInputAction,
|
||||||
onSubmitted: onSubmitted,
|
onSubmitted: onSubmitted,
|
||||||
|
|||||||
@@ -5,6 +5,8 @@
|
|||||||
import FlutterMacOS
|
import FlutterMacOS
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
|
import shared_preferences_foundation
|
||||||
|
|
||||||
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
||||||
|
SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
|
||||||
}
|
}
|
||||||
|
|||||||
16
macos/Podfile.lock
Normal file
16
macos/Podfile.lock
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
PODS:
|
||||||
|
- FlutterMacOS (1.0.0)
|
||||||
|
|
||||||
|
DEPENDENCIES:
|
||||||
|
- FlutterMacOS (from `Flutter/ephemeral`)
|
||||||
|
|
||||||
|
EXTERNAL SOURCES:
|
||||||
|
FlutterMacOS:
|
||||||
|
:path: Flutter/ephemeral
|
||||||
|
|
||||||
|
SPEC CHECKSUMS:
|
||||||
|
FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24
|
||||||
|
|
||||||
|
PODFILE CHECKSUM: 7eb978b976557c8c1cd717d8185ec483fd090a82
|
||||||
|
|
||||||
|
COCOAPODS: 1.16.2
|
||||||
@@ -27,6 +27,8 @@
|
|||||||
33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F22044A3C60003C045 /* Assets.xcassets */; };
|
33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F22044A3C60003C045 /* Assets.xcassets */; };
|
||||||
33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F42044A3C60003C045 /* MainMenu.xib */; };
|
33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F42044A3C60003C045 /* MainMenu.xib */; };
|
||||||
33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */; };
|
33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */; };
|
||||||
|
426126D67DBD3142EF2DC173 /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 59E8D21861D6167BC86AD219 /* Pods_RunnerTests.framework */; };
|
||||||
|
A34F225E7247409903A905E4 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FAE82037DE8C61D063D492A5 /* Pods_Runner.framework */; };
|
||||||
/* End PBXBuildFile section */
|
/* End PBXBuildFile section */
|
||||||
|
|
||||||
/* Begin PBXContainerItemProxy section */
|
/* Begin PBXContainerItemProxy section */
|
||||||
@@ -60,11 +62,13 @@
|
|||||||
/* End PBXCopyFilesBuildPhase section */
|
/* End PBXCopyFilesBuildPhase section */
|
||||||
|
|
||||||
/* Begin PBXFileReference section */
|
/* Begin PBXFileReference section */
|
||||||
|
0CB18BBF478F344C686F435E /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = "<group>"; };
|
||||||
|
1947B83DB3093FD23FE0D59E /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = "<group>"; };
|
||||||
331C80D5294CF71000263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
|
331C80D5294CF71000263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
331C80D7294CF71000263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = "<group>"; };
|
331C80D7294CF71000263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = "<group>"; };
|
||||||
333000ED22D3DE5D00554162 /* Warnings.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Warnings.xcconfig; sourceTree = "<group>"; };
|
333000ED22D3DE5D00554162 /* Warnings.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Warnings.xcconfig; sourceTree = "<group>"; };
|
||||||
335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneratedPluginRegistrant.swift; sourceTree = "<group>"; };
|
335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneratedPluginRegistrant.swift; sourceTree = "<group>"; };
|
||||||
33CC10ED2044A3C60003C045 /* coda_project.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "coda_project.app"; sourceTree = BUILT_PRODUCTS_DIR; };
|
33CC10ED2044A3C60003C045 /* coda_project.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = coda_project.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
33CC10F02044A3C60003C045 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
|
33CC10F02044A3C60003C045 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
|
||||||
33CC10F22044A3C60003C045 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = Runner/Assets.xcassets; sourceTree = "<group>"; };
|
33CC10F22044A3C60003C045 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = Runner/Assets.xcassets; sourceTree = "<group>"; };
|
||||||
33CC10F52044A3C60003C045 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = "<group>"; };
|
33CC10F52044A3C60003C045 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = "<group>"; };
|
||||||
@@ -76,8 +80,14 @@
|
|||||||
33E51913231747F40026EE4D /* DebugProfile.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = DebugProfile.entitlements; sourceTree = "<group>"; };
|
33E51913231747F40026EE4D /* DebugProfile.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = DebugProfile.entitlements; sourceTree = "<group>"; };
|
||||||
33E51914231749380026EE4D /* Release.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = Release.entitlements; sourceTree = "<group>"; };
|
33E51914231749380026EE4D /* Release.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = Release.entitlements; sourceTree = "<group>"; };
|
||||||
33E5194F232828860026EE4D /* AppInfo.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = AppInfo.xcconfig; sourceTree = "<group>"; };
|
33E5194F232828860026EE4D /* AppInfo.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = AppInfo.xcconfig; sourceTree = "<group>"; };
|
||||||
|
43EA9FAFB40865A263C1DE07 /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = "<group>"; };
|
||||||
|
59E8D21861D6167BC86AD219 /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = "<group>"; };
|
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = "<group>"; };
|
||||||
9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = "<group>"; };
|
9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = "<group>"; };
|
||||||
|
DAADAB5CEB9A1FD331BC72CA /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = "<group>"; };
|
||||||
|
F95332891AC6CB1046F7CE26 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = "<group>"; };
|
||||||
|
FAE82037DE8C61D063D492A5 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
|
FDD61E81F4A4D672FA555791 /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = "<group>"; };
|
||||||
/* End PBXFileReference section */
|
/* End PBXFileReference section */
|
||||||
|
|
||||||
/* Begin PBXFrameworksBuildPhase section */
|
/* Begin PBXFrameworksBuildPhase section */
|
||||||
@@ -85,6 +95,7 @@
|
|||||||
isa = PBXFrameworksBuildPhase;
|
isa = PBXFrameworksBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
|
426126D67DBD3142EF2DC173 /* Pods_RunnerTests.framework in Frameworks */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
@@ -92,6 +103,7 @@
|
|||||||
isa = PBXFrameworksBuildPhase;
|
isa = PBXFrameworksBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
|
A34F225E7247409903A905E4 /* Pods_Runner.framework in Frameworks */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
@@ -125,6 +137,7 @@
|
|||||||
331C80D6294CF71000263BE5 /* RunnerTests */,
|
331C80D6294CF71000263BE5 /* RunnerTests */,
|
||||||
33CC10EE2044A3C60003C045 /* Products */,
|
33CC10EE2044A3C60003C045 /* Products */,
|
||||||
D73912EC22F37F3D000D13A0 /* Frameworks */,
|
D73912EC22F37F3D000D13A0 /* Frameworks */,
|
||||||
|
90884EA0D598E32A2E874D7C /* Pods */,
|
||||||
);
|
);
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
@@ -172,9 +185,25 @@
|
|||||||
path = Runner;
|
path = Runner;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
|
90884EA0D598E32A2E874D7C /* Pods */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
DAADAB5CEB9A1FD331BC72CA /* Pods-Runner.debug.xcconfig */,
|
||||||
|
0CB18BBF478F344C686F435E /* Pods-Runner.release.xcconfig */,
|
||||||
|
F95332891AC6CB1046F7CE26 /* Pods-Runner.profile.xcconfig */,
|
||||||
|
43EA9FAFB40865A263C1DE07 /* Pods-RunnerTests.debug.xcconfig */,
|
||||||
|
FDD61E81F4A4D672FA555791 /* Pods-RunnerTests.release.xcconfig */,
|
||||||
|
1947B83DB3093FD23FE0D59E /* Pods-RunnerTests.profile.xcconfig */,
|
||||||
|
);
|
||||||
|
name = Pods;
|
||||||
|
path = Pods;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
D73912EC22F37F3D000D13A0 /* Frameworks */ = {
|
D73912EC22F37F3D000D13A0 /* Frameworks */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
FAE82037DE8C61D063D492A5 /* Pods_Runner.framework */,
|
||||||
|
59E8D21861D6167BC86AD219 /* Pods_RunnerTests.framework */,
|
||||||
);
|
);
|
||||||
name = Frameworks;
|
name = Frameworks;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@@ -186,6 +215,7 @@
|
|||||||
isa = PBXNativeTarget;
|
isa = PBXNativeTarget;
|
||||||
buildConfigurationList = 331C80DE294CF71000263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */;
|
buildConfigurationList = 331C80DE294CF71000263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */;
|
||||||
buildPhases = (
|
buildPhases = (
|
||||||
|
B5815815AFBCD5EB4EA925DE /* [CP] Check Pods Manifest.lock */,
|
||||||
331C80D1294CF70F00263BE5 /* Sources */,
|
331C80D1294CF70F00263BE5 /* Sources */,
|
||||||
331C80D2294CF70F00263BE5 /* Frameworks */,
|
331C80D2294CF70F00263BE5 /* Frameworks */,
|
||||||
331C80D3294CF70F00263BE5 /* Resources */,
|
331C80D3294CF70F00263BE5 /* Resources */,
|
||||||
@@ -204,6 +234,7 @@
|
|||||||
isa = PBXNativeTarget;
|
isa = PBXNativeTarget;
|
||||||
buildConfigurationList = 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */;
|
buildConfigurationList = 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */;
|
||||||
buildPhases = (
|
buildPhases = (
|
||||||
|
18A4EC124D3C458317CBA9CD /* [CP] Check Pods Manifest.lock */,
|
||||||
33CC10E92044A3C60003C045 /* Sources */,
|
33CC10E92044A3C60003C045 /* Sources */,
|
||||||
33CC10EA2044A3C60003C045 /* Frameworks */,
|
33CC10EA2044A3C60003C045 /* Frameworks */,
|
||||||
33CC10EB2044A3C60003C045 /* Resources */,
|
33CC10EB2044A3C60003C045 /* Resources */,
|
||||||
@@ -291,6 +322,28 @@
|
|||||||
/* End PBXResourcesBuildPhase section */
|
/* End PBXResourcesBuildPhase section */
|
||||||
|
|
||||||
/* Begin PBXShellScriptBuildPhase section */
|
/* Begin PBXShellScriptBuildPhase section */
|
||||||
|
18A4EC124D3C458317CBA9CD /* [CP] Check Pods Manifest.lock */ = {
|
||||||
|
isa = PBXShellScriptBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
);
|
||||||
|
inputFileListPaths = (
|
||||||
|
);
|
||||||
|
inputPaths = (
|
||||||
|
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
|
||||||
|
"${PODS_ROOT}/Manifest.lock",
|
||||||
|
);
|
||||||
|
name = "[CP] Check Pods Manifest.lock";
|
||||||
|
outputFileListPaths = (
|
||||||
|
);
|
||||||
|
outputPaths = (
|
||||||
|
"$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt",
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
shellPath = /bin/sh;
|
||||||
|
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
|
||||||
|
showEnvVarsInLog = 0;
|
||||||
|
};
|
||||||
3399D490228B24CF009A79C7 /* ShellScript */ = {
|
3399D490228B24CF009A79C7 /* ShellScript */ = {
|
||||||
isa = PBXShellScriptBuildPhase;
|
isa = PBXShellScriptBuildPhase;
|
||||||
alwaysOutOfDate = 1;
|
alwaysOutOfDate = 1;
|
||||||
@@ -329,6 +382,28 @@
|
|||||||
shellPath = /bin/sh;
|
shellPath = /bin/sh;
|
||||||
shellScript = "\"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh && touch Flutter/ephemeral/tripwire";
|
shellScript = "\"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh && touch Flutter/ephemeral/tripwire";
|
||||||
};
|
};
|
||||||
|
B5815815AFBCD5EB4EA925DE /* [CP] Check Pods Manifest.lock */ = {
|
||||||
|
isa = PBXShellScriptBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
);
|
||||||
|
inputFileListPaths = (
|
||||||
|
);
|
||||||
|
inputPaths = (
|
||||||
|
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
|
||||||
|
"${PODS_ROOT}/Manifest.lock",
|
||||||
|
);
|
||||||
|
name = "[CP] Check Pods Manifest.lock";
|
||||||
|
outputFileListPaths = (
|
||||||
|
);
|
||||||
|
outputPaths = (
|
||||||
|
"$(DERIVED_FILE_DIR)/Pods-RunnerTests-checkManifestLockResult.txt",
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
shellPath = /bin/sh;
|
||||||
|
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
|
||||||
|
showEnvVarsInLog = 0;
|
||||||
|
};
|
||||||
/* End PBXShellScriptBuildPhase section */
|
/* End PBXShellScriptBuildPhase section */
|
||||||
|
|
||||||
/* Begin PBXSourcesBuildPhase section */
|
/* Begin PBXSourcesBuildPhase section */
|
||||||
@@ -380,6 +455,7 @@
|
|||||||
/* Begin XCBuildConfiguration section */
|
/* Begin XCBuildConfiguration section */
|
||||||
331C80DB294CF71000263BE5 /* Debug */ = {
|
331C80DB294CF71000263BE5 /* Debug */ = {
|
||||||
isa = XCBuildConfiguration;
|
isa = XCBuildConfiguration;
|
||||||
|
baseConfigurationReference = 43EA9FAFB40865A263C1DE07 /* Pods-RunnerTests.debug.xcconfig */;
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
BUNDLE_LOADER = "$(TEST_HOST)";
|
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||||
CURRENT_PROJECT_VERSION = 1;
|
CURRENT_PROJECT_VERSION = 1;
|
||||||
@@ -394,6 +470,7 @@
|
|||||||
};
|
};
|
||||||
331C80DC294CF71000263BE5 /* Release */ = {
|
331C80DC294CF71000263BE5 /* Release */ = {
|
||||||
isa = XCBuildConfiguration;
|
isa = XCBuildConfiguration;
|
||||||
|
baseConfigurationReference = FDD61E81F4A4D672FA555791 /* Pods-RunnerTests.release.xcconfig */;
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
BUNDLE_LOADER = "$(TEST_HOST)";
|
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||||
CURRENT_PROJECT_VERSION = 1;
|
CURRENT_PROJECT_VERSION = 1;
|
||||||
@@ -408,6 +485,7 @@
|
|||||||
};
|
};
|
||||||
331C80DD294CF71000263BE5 /* Profile */ = {
|
331C80DD294CF71000263BE5 /* Profile */ = {
|
||||||
isa = XCBuildConfiguration;
|
isa = XCBuildConfiguration;
|
||||||
|
baseConfigurationReference = 1947B83DB3093FD23FE0D59E /* Pods-RunnerTests.profile.xcconfig */;
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
BUNDLE_LOADER = "$(TEST_HOST)";
|
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||||
CURRENT_PROJECT_VERSION = 1;
|
CURRENT_PROJECT_VERSION = 1;
|
||||||
|
|||||||
@@ -4,4 +4,7 @@
|
|||||||
<FileRef
|
<FileRef
|
||||||
location = "group:Runner.xcodeproj">
|
location = "group:Runner.xcodeproj">
|
||||||
</FileRef>
|
</FileRef>
|
||||||
|
<FileRef
|
||||||
|
location = "group:Pods/Pods.xcodeproj">
|
||||||
|
</FileRef>
|
||||||
</Workspace>
|
</Workspace>
|
||||||
|
|||||||
152
pubspec.lock
152
pubspec.lock
@@ -145,6 +145,38 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.0.2"
|
version: "1.0.2"
|
||||||
|
dartz:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: dartz
|
||||||
|
sha256: e6acf34ad2e31b1eb00948692468c30ab48ac8250e0f0df661e29f12dd252168
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.10.1"
|
||||||
|
dio:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: dio
|
||||||
|
sha256: d90ee57923d1828ac14e492ca49440f65477f4bb1263575900be731a3dac66a9
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "5.9.0"
|
||||||
|
dio_web_adapter:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: dio_web_adapter
|
||||||
|
sha256: "7586e476d70caecaf1686d21eee7247ea43ef5c345eab9e0cc3583ff13378d78"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.1.1"
|
||||||
|
equatable:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: equatable
|
||||||
|
sha256: "3e0141505477fd8ad55d6eb4e7776d3fe8430be8e497ccb1521370c3f21a3e2b"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.0.8"
|
||||||
fake_async:
|
fake_async:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -161,6 +193,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.4"
|
version: "2.1.4"
|
||||||
|
file:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: file
|
||||||
|
sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "7.0.1"
|
||||||
flutter:
|
flutter:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description: flutter
|
description: flutter
|
||||||
@@ -216,6 +256,14 @@ packages:
|
|||||||
description: flutter
|
description: flutter
|
||||||
source: sdk
|
source: sdk
|
||||||
version: "0.0.0"
|
version: "0.0.0"
|
||||||
|
get_it:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: get_it
|
||||||
|
sha256: d85128a5dae4ea777324730dc65edd9c9f43155c109d5cc0a69cab74139fbac1
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "7.7.0"
|
||||||
html:
|
html:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -312,6 +360,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.16.0"
|
version: "1.16.0"
|
||||||
|
mime:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: mime
|
||||||
|
sha256: "41a20518f0cb1256669420fdba0cd90d21561e560ac240f26ef8322e45bb7ed6"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.0.0"
|
||||||
path:
|
path:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -328,6 +384,30 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.1.0"
|
version: "1.1.0"
|
||||||
|
path_provider_linux:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: path_provider_linux
|
||||||
|
sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.2.1"
|
||||||
|
path_provider_platform_interface:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: path_provider_platform_interface
|
||||||
|
sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.1.2"
|
||||||
|
path_provider_windows:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: path_provider_windows
|
||||||
|
sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.3.0"
|
||||||
petitparser:
|
petitparser:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -336,6 +416,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "6.1.0"
|
version: "6.1.0"
|
||||||
|
platform:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: platform
|
||||||
|
sha256: "5d6b1b0036a5f331ebc77c850ebc8506cbc1e9416c27e59b439f917a902a4984"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "3.1.6"
|
||||||
plugin_platform_interface:
|
plugin_platform_interface:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -352,6 +440,62 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "6.0.3"
|
version: "6.0.3"
|
||||||
|
shared_preferences:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: shared_preferences
|
||||||
|
sha256: "6e8bf70b7fef813df4e9a36f658ac46d107db4b4cfe1048b477d4e453a8159f5"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.5.3"
|
||||||
|
shared_preferences_android:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: shared_preferences_android
|
||||||
|
sha256: bd14436108211b0d4ee5038689a56d4ae3620fd72fd6036e113bf1345bc74d9e
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.4.13"
|
||||||
|
shared_preferences_foundation:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: shared_preferences_foundation
|
||||||
|
sha256: "6a52cfcdaeac77cad8c97b539ff688ccfc458c007b4db12be584fbe5c0e49e03"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.5.4"
|
||||||
|
shared_preferences_linux:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: shared_preferences_linux
|
||||||
|
sha256: "580abfd40f415611503cae30adf626e6656dfb2f0cee8f465ece7b6defb40f2f"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.4.1"
|
||||||
|
shared_preferences_platform_interface:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: shared_preferences_platform_interface
|
||||||
|
sha256: "57cbf196c486bc2cf1f02b85784932c6094376284b3ad5779d1b1c6c6a816b80"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.4.1"
|
||||||
|
shared_preferences_web:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: shared_preferences_web
|
||||||
|
sha256: c49bd060261c9a3f0ff445892695d6212ff603ef3115edbb448509d407600019
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.4.3"
|
||||||
|
shared_preferences_windows:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: shared_preferences_windows
|
||||||
|
sha256: "94ef0f72b2d71bc3e700e025db3710911bd51a71cefb65cc609dd0d9a982e3c1"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.4.1"
|
||||||
sky_engine:
|
sky_engine:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description: flutter
|
description: flutter
|
||||||
@@ -477,6 +621,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.1.1"
|
version: "1.1.1"
|
||||||
|
xdg_directories:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: xdg_directories
|
||||||
|
sha256: "7a3f37b05d989967cdddcbb571f1ea834867ae2faa29725fd085180e0883aa15"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.1.0"
|
||||||
xml:
|
xml:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|||||||
@@ -12,6 +12,11 @@ dependencies:
|
|||||||
flutter:
|
flutter:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
flutter_svg: ^2.0.9
|
flutter_svg: ^2.0.9
|
||||||
|
dio: ^5.4.0
|
||||||
|
get_it: ^7.6.4
|
||||||
|
dartz: ^0.10.1
|
||||||
|
equatable: ^2.0.5
|
||||||
|
shared_preferences: ^2.2.2
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
|
|||||||
Reference in New Issue
Block a user