This commit is contained in:
Mohammed Al-Samarraie
2026-01-13 12:43:43 +03:00
parent 4df24f5d8d
commit ac8a769ff0
23 changed files with 945 additions and 2 deletions

173
lib/ARCHITECTURE.md Normal file
View 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

View 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;
}
}

View File

@@ -0,0 +1,41 @@
import 'package:dio/dio.dart';
import 'package:get_it/get_it.dart';
import 'package:shared_preferences/shared_preferences.dart';
import '../network/api_client.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()));
// Data sources will be registered here
// Example:
// sl.registerLazySingleton<AuthRemoteDataSource>(
// () => AuthRemoteDataSourceImpl(apiClient: sl()),
// );
// Repositories will be registered here
// Example:
// sl.registerLazySingleton<AuthRepository>(
// () => AuthRepositoryImpl(
// remoteDataSource: sl(),
// localDataSource: sl(),
// ),
// );
// Use cases will be registered here
// Example:
// sl.registerLazySingleton(() => LoginUseCase(repository: sl()));
// Blocs will be registered here
// Example:
// sl.registerFactory(() => LoginBloc(loginUseCase: sl()));
}

View File

@@ -0,0 +1,7 @@
// Add your app-specific enums here
// Example:
// enum UserRole {
// admin,
// user,
// guest,
// }

View 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});
}

View 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);
}

View File

@@ -0,0 +1,103 @@
import 'package:dio/dio.dart';
class ApiClient {
final Dio dio;
static const String baseUrl = 'YOUR_API_BASE_URL_HERE';
ApiClient({required this.dio}) {
dio.options = BaseOptions(
baseUrl: baseUrl,
connectTimeout: const Duration(seconds: 30),
receiveTimeout: const Duration(seconds: 30),
headers: {
'Content-Type': 'application/json',
'accept': 'text/plain',
},
);
// 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;
}
}
}

View 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;
}
}

View 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
# }
# }
# }

View 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
View 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'],
# );
# }
# }

View 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));
# }
# }
# }

View 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

View 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);
# }

View 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);
# }
# }

View File

@@ -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());
} }

View 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

View File

@@ -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
View 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

View File

@@ -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;

View File

@@ -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>

View File

@@ -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:

View File

@@ -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: