diff --git a/lib/ARCHITECTURE.md b/lib/ARCHITECTURE.md new file mode 100644 index 0000000..4b9601e --- /dev/null +++ b/lib/ARCHITECTURE.md @@ -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 toJson() => { + 'phoneNumber': phoneNumber, + 'password': password, + }; +} +``` + +#### ب) إنشاء Remote Data Source في `data/datasources/` +```dart +abstract class AuthRemoteDataSource { + Future login(LoginDto dto); +} + +class AuthRemoteDataSourceImpl implements AuthRemoteDataSource { + final ApiClient apiClient; + + AuthRemoteDataSourceImpl({required this.apiClient}); + + @override + Future 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> login(LoginRequest request); +} +``` + +#### د) إنشاء Repository Implementation في `data/repositories/` +```dart +class AuthRepositoryImpl implements AuthRepository { + final AuthRemoteDataSource remoteDataSource; + + AuthRepositoryImpl({required this.remoteDataSource}); + + @override + Future> 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> call(LoginRequest request) { + return repository.login(request); + } +} +``` + +#### و) تسجيل في `core/di/injection_container.dart` +```dart +// Data source +sl.registerLazySingleton( + () => AuthRemoteDataSourceImpl(apiClient: sl()), +); + +// Repository +sl.registerLazySingleton( + () => AuthRepositoryImpl(remoteDataSource: sl()), +); + +// Use case +sl.registerLazySingleton(() => LoginUseCase(repository: sl())); +``` + +### 2. استخدام في BLoC +```dart +class LoginBloc extends Bloc { + final LoginUseCase loginUseCase; + + LoginBloc({required this.loginUseCase}) : super(LoginInitial()) { + on(_onLoginSubmitted); + } + + Future _onLoginSubmitted( + LoginSubmitted event, + Emitter 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 diff --git a/lib/core/constants/dimensions.dart b/lib/core/constants/dimensions.dart new file mode 100644 index 0000000..b5d9612 --- /dev/null +++ b/lib/core/constants/dimensions.dart @@ -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; + } +} diff --git a/lib/core/di/injection_container.dart b/lib/core/di/injection_container.dart new file mode 100644 index 0000000..7055227 --- /dev/null +++ b/lib/core/di/injection_container.dart @@ -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 initializeDependencies() async { + // External + sl.registerLazySingleton(() => Dio()); + + // SharedPreferences + final sharedPreferences = await SharedPreferences.getInstance(); + sl.registerLazySingleton(() => sharedPreferences); + + // Core + sl.registerLazySingleton(() => ApiClient(dio: sl())); + + // Data sources will be registered here + // Example: + // sl.registerLazySingleton( + // () => AuthRemoteDataSourceImpl(apiClient: sl()), + // ); + + // Repositories will be registered here + // Example: + // sl.registerLazySingleton( + // () => 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())); +} diff --git a/lib/core/enums/app_enums.dart b/lib/core/enums/app_enums.dart new file mode 100644 index 0000000..32d9dec --- /dev/null +++ b/lib/core/enums/app_enums.dart @@ -0,0 +1,7 @@ +// Add your app-specific enums here +// Example: +// enum UserRole { +// admin, +// user, +// guest, +// } diff --git a/lib/core/error/exceptions.dart b/lib/core/error/exceptions.dart new file mode 100644 index 0000000..2ced2ac --- /dev/null +++ b/lib/core/error/exceptions.dart @@ -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}); +} diff --git a/lib/core/error/failures.dart b/lib/core/error/failures.dart new file mode 100644 index 0000000..2af3aa1 --- /dev/null +++ b/lib/core/error/failures.dart @@ -0,0 +1,22 @@ +import 'package:equatable/equatable.dart'; + +abstract class Failure extends Equatable { + final String message; + + const Failure(this.message); + + @override + List 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); +} diff --git a/lib/core/network/api_client.dart b/lib/core/network/api_client.dart new file mode 100644 index 0000000..3372c87 --- /dev/null +++ b/lib/core/network/api_client.dart @@ -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 post( + String path, { + dynamic data, + Map? queryParameters, + Options? options, + }) async { + try { + final response = await dio.post( + path, + data: data, + queryParameters: queryParameters, + options: options, + ); + return response; + } catch (e) { + rethrow; + } + } + + Future get( + String path, { + Map? queryParameters, + Options? options, + }) async { + try { + final response = await dio.get( + path, + queryParameters: queryParameters, + options: options, + ); + return response; + } catch (e) { + rethrow; + } + } + + Future put( + String path, { + dynamic data, + Map? queryParameters, + Options? options, + }) async { + try { + final response = await dio.put( + path, + data: data, + queryParameters: queryParameters, + options: options, + ); + return response; + } catch (e) { + rethrow; + } + } + + Future delete( + String path, { + dynamic data, + Map? queryParameters, + Options? options, + }) async { + try { + final response = await dio.delete( + path, + data: data, + queryParameters: queryParameters, + options: options, + ); + return response; + } catch (e) { + rethrow; + } + } +} diff --git a/lib/core/utils/validators.dart b/lib/core/utils/validators.dart new file mode 100644 index 0000000..f818fa4 --- /dev/null +++ b/lib/core/utils/validators.dart @@ -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; + } +} diff --git a/lib/data/datasources/.gitkeep b/lib/data/datasources/.gitkeep new file mode 100644 index 0000000..e709089 --- /dev/null +++ b/lib/data/datasources/.gitkeep @@ -0,0 +1,25 @@ +# Data sources directory +# Create your remote data sources here following this pattern: +# +# abstract class YourRemoteDataSource { +# Future yourMethod(YourRequest request); +# } +# +# class YourRemoteDataSourceImpl implements YourRemoteDataSource { +# final ApiClient apiClient; +# +# YourRemoteDataSourceImpl({required this.apiClient}); +# +# @override +# Future 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 +# } +# } +# } diff --git a/lib/data/datasources/user_local_data_source.dart b/lib/data/datasources/user_local_data_source.dart new file mode 100644 index 0000000..dacf105 --- /dev/null +++ b/lib/data/datasources/user_local_data_source.dart @@ -0,0 +1,29 @@ +import 'package:shared_preferences/shared_preferences.dart'; + +abstract class UserLocalDataSource { + Future cacheUserToken(String token); + Future getCachedUserToken(); + Future clearCache(); +} + +class UserLocalDataSourceImpl implements UserLocalDataSource { + final SharedPreferences sharedPreferences; + static const String _tokenKey = 'user_token'; + + UserLocalDataSourceImpl({required this.sharedPreferences}); + + @override + Future cacheUserToken(String token) async { + await sharedPreferences.setString(_tokenKey, token); + } + + @override + Future getCachedUserToken() async { + return sharedPreferences.getString(_tokenKey); + } + + @override + Future clearCache() async { + await sharedPreferences.remove(_tokenKey); + } +} diff --git a/lib/data/dto/.gitkeep b/lib/data/dto/.gitkeep new file mode 100644 index 0000000..e38ca9a --- /dev/null +++ b/lib/data/dto/.gitkeep @@ -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 toJson() { +# return { +# 'phoneNumber': phoneNumber, +# 'password': password, +# }; +# } +# +# factory LoginDto.fromJson(Map json) { +# return LoginDto( +# phoneNumber: json['phoneNumber'], +# password: json['password'], +# ); +# } +# } diff --git a/lib/data/repositories/.gitkeep b/lib/data/repositories/.gitkeep new file mode 100644 index 0000000..5fa8e5f --- /dev/null +++ b/lib/data/repositories/.gitkeep @@ -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> 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)); +# } +# } +# } diff --git a/lib/domain/models/.gitkeep b/lib/domain/models/.gitkeep new file mode 100644 index 0000000..afab31e --- /dev/null +++ b/lib/domain/models/.gitkeep @@ -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 diff --git a/lib/domain/repositories/.gitkeep b/lib/domain/repositories/.gitkeep new file mode 100644 index 0000000..d44dfb0 --- /dev/null +++ b/lib/domain/repositories/.gitkeep @@ -0,0 +1,8 @@ +# Repository interfaces directory +# Create your repository interfaces here +# Example: +# +# abstract class AuthRepository { +# Future> login(LoginRequest request); +# Future> register(RegisterRequest request); +# } diff --git a/lib/domain/usecases/.gitkeep b/lib/domain/usecases/.gitkeep new file mode 100644 index 0000000..3b49da7 --- /dev/null +++ b/lib/domain/usecases/.gitkeep @@ -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> call(LoginRequest request) { +# return repository.login(request); +# } +# } diff --git a/lib/main.dart b/lib/main.dart index 753db99..80a4edc 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,11 +1,16 @@ import 'package:flutter/material.dart'; import 'package:flutter_native_splash/flutter_native_splash.dart'; +import 'core/di/injection_container.dart'; import 'screens/splash_screen.dart'; -void main() { +void main() async { WidgetsBinding widgetsBinding = WidgetsFlutterBinding.ensureInitialized(); FlutterNativeSplash.preserve(widgetsBinding: widgetsBinding); + + // Initialize dependency injection + await initializeDependencies(); + runApp(const CodaApp()); } diff --git a/lib/presentation/blocs/.gitkeep b/lib/presentation/blocs/.gitkeep new file mode 100644 index 0000000..d45bece --- /dev/null +++ b/lib/presentation/blocs/.gitkeep @@ -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 diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index cccf817..724bb2a 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -5,6 +5,8 @@ import FlutterMacOS import Foundation +import shared_preferences_foundation func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { + SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) } diff --git a/macos/Podfile.lock b/macos/Podfile.lock new file mode 100644 index 0000000..d6b83f3 --- /dev/null +++ b/macos/Podfile.lock @@ -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 diff --git a/macos/Runner.xcodeproj/project.pbxproj b/macos/Runner.xcodeproj/project.pbxproj index d4841f4..b3e2fe0 100644 --- a/macos/Runner.xcodeproj/project.pbxproj +++ b/macos/Runner.xcodeproj/project.pbxproj @@ -27,6 +27,8 @@ 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F22044A3C60003C045 /* Assets.xcassets */; }; 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F42044A3C60003C045 /* MainMenu.xib */; }; 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 */ /* Begin PBXContainerItemProxy section */ @@ -60,11 +62,13 @@ /* End PBXCopyFilesBuildPhase 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 = ""; }; + 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 = ""; }; 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 = ""; }; 333000ED22D3DE5D00554162 /* Warnings.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Warnings.xcconfig; sourceTree = ""; }; 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneratedPluginRegistrant.swift; sourceTree = ""; }; - 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 = ""; }; 33CC10F22044A3C60003C045 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = Runner/Assets.xcassets; sourceTree = ""; }; 33CC10F52044A3C60003C045 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = ""; }; @@ -76,8 +80,14 @@ 33E51913231747F40026EE4D /* DebugProfile.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = DebugProfile.entitlements; sourceTree = ""; }; 33E51914231749380026EE4D /* Release.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = Release.entitlements; sourceTree = ""; }; 33E5194F232828860026EE4D /* AppInfo.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = AppInfo.xcconfig; sourceTree = ""; }; + 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 = ""; }; + 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 = ""; }; 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = ""; }; + 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 = ""; }; + 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 = ""; }; + 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 = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -85,6 +95,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 426126D67DBD3142EF2DC173 /* Pods_RunnerTests.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -92,6 +103,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + A34F225E7247409903A905E4 /* Pods_Runner.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -125,6 +137,7 @@ 331C80D6294CF71000263BE5 /* RunnerTests */, 33CC10EE2044A3C60003C045 /* Products */, D73912EC22F37F3D000D13A0 /* Frameworks */, + 90884EA0D598E32A2E874D7C /* Pods */, ); sourceTree = ""; }; @@ -172,9 +185,25 @@ path = Runner; sourceTree = ""; }; + 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 = ""; + }; D73912EC22F37F3D000D13A0 /* Frameworks */ = { isa = PBXGroup; children = ( + FAE82037DE8C61D063D492A5 /* Pods_Runner.framework */, + 59E8D21861D6167BC86AD219 /* Pods_RunnerTests.framework */, ); name = Frameworks; sourceTree = ""; @@ -186,6 +215,7 @@ isa = PBXNativeTarget; buildConfigurationList = 331C80DE294CF71000263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; buildPhases = ( + B5815815AFBCD5EB4EA925DE /* [CP] Check Pods Manifest.lock */, 331C80D1294CF70F00263BE5 /* Sources */, 331C80D2294CF70F00263BE5 /* Frameworks */, 331C80D3294CF70F00263BE5 /* Resources */, @@ -204,6 +234,7 @@ isa = PBXNativeTarget; buildConfigurationList = 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */; buildPhases = ( + 18A4EC124D3C458317CBA9CD /* [CP] Check Pods Manifest.lock */, 33CC10E92044A3C60003C045 /* Sources */, 33CC10EA2044A3C60003C045 /* Frameworks */, 33CC10EB2044A3C60003C045 /* Resources */, @@ -291,6 +322,28 @@ /* End PBXResourcesBuildPhase 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 */ = { isa = PBXShellScriptBuildPhase; alwaysOutOfDate = 1; @@ -329,6 +382,28 @@ shellPath = /bin/sh; 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 */ /* Begin PBXSourcesBuildPhase section */ @@ -380,6 +455,7 @@ /* Begin XCBuildConfiguration section */ 331C80DB294CF71000263BE5 /* Debug */ = { isa = XCBuildConfiguration; + baseConfigurationReference = 43EA9FAFB40865A263C1DE07 /* Pods-RunnerTests.debug.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CURRENT_PROJECT_VERSION = 1; @@ -394,6 +470,7 @@ }; 331C80DC294CF71000263BE5 /* Release */ = { isa = XCBuildConfiguration; + baseConfigurationReference = FDD61E81F4A4D672FA555791 /* Pods-RunnerTests.release.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CURRENT_PROJECT_VERSION = 1; @@ -408,6 +485,7 @@ }; 331C80DD294CF71000263BE5 /* Profile */ = { isa = XCBuildConfiguration; + baseConfigurationReference = 1947B83DB3093FD23FE0D59E /* Pods-RunnerTests.profile.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CURRENT_PROJECT_VERSION = 1; diff --git a/macos/Runner.xcworkspace/contents.xcworkspacedata b/macos/Runner.xcworkspace/contents.xcworkspacedata index 1d526a1..21a3cc1 100644 --- a/macos/Runner.xcworkspace/contents.xcworkspacedata +++ b/macos/Runner.xcworkspace/contents.xcworkspacedata @@ -4,4 +4,7 @@ + + diff --git a/pubspec.lock b/pubspec.lock index 4d34b0b..e309ec4 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -145,6 +145,38 @@ packages: url: "https://pub.dev" source: hosted 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: dependency: transitive description: @@ -161,6 +193,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.4" + file: + dependency: transitive + description: + name: file + sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4 + url: "https://pub.dev" + source: hosted + version: "7.0.1" flutter: dependency: "direct main" description: flutter @@ -216,6 +256,14 @@ packages: description: flutter source: sdk 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: dependency: transitive description: @@ -312,6 +360,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.16.0" + mime: + dependency: transitive + description: + name: mime + sha256: "41a20518f0cb1256669420fdba0cd90d21561e560ac240f26ef8322e45bb7ed6" + url: "https://pub.dev" + source: hosted + version: "2.0.0" path: dependency: transitive description: @@ -328,6 +384,30 @@ packages: url: "https://pub.dev" source: hosted 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: dependency: transitive description: @@ -336,6 +416,14 @@ packages: url: "https://pub.dev" source: hosted 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: dependency: transitive description: @@ -352,6 +440,62 @@ packages: url: "https://pub.dev" source: hosted 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: dependency: transitive description: flutter @@ -477,6 +621,14 @@ packages: url: "https://pub.dev" source: hosted 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: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 54c33ad..547495d 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -12,6 +12,11 @@ dependencies: flutter: sdk: flutter 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: flutter_test: