diff --git a/.env.demo b/.env.demo new file mode 100644 index 0000000..19c5530 --- /dev/null +++ b/.env.demo @@ -0,0 +1,2 @@ +# Demo Environment Configuration +API_BASE_URL=https://demo-api.requestn.com diff --git a/.env.dev b/.env.dev new file mode 100644 index 0000000..70782b3 --- /dev/null +++ b/.env.dev @@ -0,0 +1,2 @@ +# Dev Environment Configuration +API_BASE_URL=https://dev-api.requestn.com diff --git a/.env.local b/.env.local new file mode 100644 index 0000000..c2f6e67 --- /dev/null +++ b/.env.local @@ -0,0 +1,2 @@ +# Local Environment Configuration +API_BASE_URL=http://localhost:3001/api/v1 diff --git a/android/app/build.gradle.kts b/android/app/build.gradle.kts index 39a6dd5..f725ae1 100644 --- a/android/app/build.gradle.kts +++ b/android/app/build.gradle.kts @@ -20,20 +20,35 @@ android { } defaultConfig { - // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). applicationId = "com.requestn.real_estate_mobile" - // You can update the following values to match your application needs. - // For more information, see: https://flutter.dev/to/review-gradle-config. minSdk = flutter.minSdkVersion targetSdk = flutter.targetSdkVersion versionCode = flutter.versionCode versionName = flutter.versionName } + flavorDimensions += "flavor-type" + + productFlavors { + create("local") { + dimension = "flavor-type" + applicationIdSuffix = ".local" + resValue("string", "app_name", "RE-QuestN Local") + } + create("demo") { + dimension = "flavor-type" + applicationIdSuffix = ".demo" + resValue("string", "app_name", "RE-QuestN Demo") + } + create("dev") { + dimension = "flavor-type" + applicationIdSuffix = ".dev" + resValue("string", "app_name", "RE-QuestN Dev") + } + } + buildTypes { release { - // TODO: Add your own signing config for the release build. - // Signing with the debug keys for now, so `flutter run --release` works. signingConfig = signingConfigs.getByName("debug") } } diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index cf42387..4bff20e 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -1,6 +1,6 @@ + + diff --git a/assets/icons/google.svg b/assets/icons/google.svg new file mode 100644 index 0000000..d3cdb87 --- /dev/null +++ b/assets/icons/google.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/assets/icons/logo.png b/assets/icons/logo.png new file mode 100644 index 0000000..4dd93c8 Binary files /dev/null and b/assets/icons/logo.png differ diff --git a/assets/icons/logo.svg b/assets/icons/logo.svg new file mode 100644 index 0000000..c0ef4f5 --- /dev/null +++ b/assets/icons/logo.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/assets/icons/x.svg b/assets/icons/x.svg new file mode 100644 index 0000000..e029688 --- /dev/null +++ b/assets/icons/x.svg @@ -0,0 +1,3 @@ + + + diff --git a/ios/Flutter/flavors/demo.xcconfig b/ios/Flutter/flavors/demo.xcconfig new file mode 100644 index 0000000..2e362f8 --- /dev/null +++ b/ios/Flutter/flavors/demo.xcconfig @@ -0,0 +1,3 @@ +PRODUCT_BUNDLE_IDENTIFIER = com.requestn.realEstateMobile.demo +PRODUCT_NAME = RE-QuestN Demo +DISPLAY_NAME = RE-QuestN Demo diff --git a/ios/Flutter/flavors/dev.xcconfig b/ios/Flutter/flavors/dev.xcconfig new file mode 100644 index 0000000..58edbfb --- /dev/null +++ b/ios/Flutter/flavors/dev.xcconfig @@ -0,0 +1,3 @@ +PRODUCT_BUNDLE_IDENTIFIER = com.requestn.realEstateMobile.dev +PRODUCT_NAME = RE-QuestN Dev +DISPLAY_NAME = RE-QuestN Dev diff --git a/ios/Flutter/flavors/local.xcconfig b/ios/Flutter/flavors/local.xcconfig new file mode 100644 index 0000000..ee4eeb7 --- /dev/null +++ b/ios/Flutter/flavors/local.xcconfig @@ -0,0 +1,3 @@ +PRODUCT_BUNDLE_IDENTIFIER = com.requestn.realEstateMobile.local +PRODUCT_NAME = RE-QuestN Local +DISPLAY_NAME = RE-QuestN Local diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj index de7b8d0..d4fd0de 100644 --- a/ios/Runner.xcodeproj/project.pbxproj +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -42,21 +42,29 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ + 0067BF91F771E11BEFE77DC1 /* Pods-Runner.debug-local.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug-local.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug-local.xcconfig"; sourceTree = ""; }; + 1374C3BCAF51A7D21E3D1461 /* Pods-RunnerTests.release-dev.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release-dev.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release-dev.xcconfig"; sourceTree = ""; }; 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; + 153AF4674E578754D224255A /* Pods-RunnerTests.debug-local.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug-local.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug-local.xcconfig"; sourceTree = ""; }; 1D8BE6524BF4AB0EE07BCB97 /* 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 = ""; }; 21E2983329AA00CF7EB52D3B /* 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 = ""; }; 24530492AA29C1128B9DE21D /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 29B5F3411A6AC5DBA20732A3 /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 2D8696491011AC22E03BA171 /* Pods-RunnerTests.release-demo.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release-demo.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release-demo.xcconfig"; sourceTree = ""; }; 331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; 331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 356105A88B78E847BF4D11C9 /* 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 = ""; }; 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; 4066250348A89E723B2219B0 /* 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 = ""; }; + 4D438DDD79FAD837462612A5 /* Pods-Runner.release-local.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release-local.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release-local.xcconfig"; sourceTree = ""; }; 6A927CA3A4E6C4C36F72FEA4 /* 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 = ""; }; 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; + 857E8264E668F4190C7F35FD /* Pods-Runner.debug-dev.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug-dev.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug-dev.xcconfig"; sourceTree = ""; }; + 85DDCFDDDF32782AA3D474B8 /* Pods-RunnerTests.debug-dev.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug-dev.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug-dev.xcconfig"; sourceTree = ""; }; + 929C1186A6A8B9F13BFAF559 /* Pods-Runner.profile-local.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile-local.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile-local.xcconfig"; sourceTree = ""; }; 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -64,7 +72,17 @@ 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + C12B02D43854AE83D024ADEC /* Pods-RunnerTests.release-local.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release-local.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release-local.xcconfig"; sourceTree = ""; }; + C319912C50D88ACBF68DD642 /* Pods-Runner.release-dev.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release-dev.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release-dev.xcconfig"; sourceTree = ""; }; + C91BA0EAE82DD425F19AE333 /* Pods-RunnerTests.profile-dev.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile-dev.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile-dev.xcconfig"; sourceTree = ""; }; + D01B1D4903412E4E802B79FE /* Pods-RunnerTests.profile-local.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile-local.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile-local.xcconfig"; sourceTree = ""; }; + DA4C8512C9938132E1AD789C /* Pods-Runner.release-demo.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release-demo.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release-demo.xcconfig"; sourceTree = ""; }; E359BC0A45161327BB4B9171 /* 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 = ""; }; + E6727AD4DAC30D2D0F7F4E48 /* Pods-Runner.debug-demo.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug-demo.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug-demo.xcconfig"; sourceTree = ""; }; + E88CBAA9AD79E664A4EFB8CB /* Pods-RunnerTests.profile-demo.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile-demo.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile-demo.xcconfig"; sourceTree = ""; }; + F052FF2603443E00B48595AE /* Pods-Runner.profile-dev.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile-dev.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile-dev.xcconfig"; sourceTree = ""; }; + F57F2DCD57F0CBCBAC6A53D5 /* Pods-Runner.profile-demo.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile-demo.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile-demo.xcconfig"; sourceTree = ""; }; + FF6FDC5BF51328C7DFB6EFBC /* Pods-RunnerTests.debug-demo.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug-demo.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug-demo.xcconfig"; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -96,6 +114,24 @@ 4066250348A89E723B2219B0 /* Pods-RunnerTests.debug.xcconfig */, 21E2983329AA00CF7EB52D3B /* Pods-RunnerTests.release.xcconfig */, 1D8BE6524BF4AB0EE07BCB97 /* Pods-RunnerTests.profile.xcconfig */, + 0067BF91F771E11BEFE77DC1 /* Pods-Runner.debug-local.xcconfig */, + 4D438DDD79FAD837462612A5 /* Pods-Runner.release-local.xcconfig */, + 929C1186A6A8B9F13BFAF559 /* Pods-Runner.profile-local.xcconfig */, + E6727AD4DAC30D2D0F7F4E48 /* Pods-Runner.debug-demo.xcconfig */, + DA4C8512C9938132E1AD789C /* Pods-Runner.release-demo.xcconfig */, + F57F2DCD57F0CBCBAC6A53D5 /* Pods-Runner.profile-demo.xcconfig */, + 857E8264E668F4190C7F35FD /* Pods-Runner.debug-dev.xcconfig */, + C319912C50D88ACBF68DD642 /* Pods-Runner.release-dev.xcconfig */, + F052FF2603443E00B48595AE /* Pods-Runner.profile-dev.xcconfig */, + 153AF4674E578754D224255A /* Pods-RunnerTests.debug-local.xcconfig */, + C12B02D43854AE83D024ADEC /* Pods-RunnerTests.release-local.xcconfig */, + D01B1D4903412E4E802B79FE /* Pods-RunnerTests.profile-local.xcconfig */, + FF6FDC5BF51328C7DFB6EFBC /* Pods-RunnerTests.debug-demo.xcconfig */, + 2D8696491011AC22E03BA171 /* Pods-RunnerTests.release-demo.xcconfig */, + E88CBAA9AD79E664A4EFB8CB /* Pods-RunnerTests.profile-demo.xcconfig */, + 85DDCFDDDF32782AA3D474B8 /* Pods-RunnerTests.debug-dev.xcconfig */, + 1374C3BCAF51A7D21E3D1461 /* Pods-RunnerTests.release-dev.xcconfig */, + C91BA0EAE82DD425F19AE333 /* Pods-RunnerTests.profile-dev.xcconfig */, ); name = Pods; path = Pods; @@ -412,6 +448,82 @@ /* End PBXVariantGroup section */ /* Begin XCBuildConfiguration section */ + 0158064B71F36128EA5718A4 /* Release-local */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = "Release-local"; + }; + 0BED8561365AA0F03990AE37 /* Release-dev */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + DEVELOPMENT_TEAM = JKMS87XDA9; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.requestn.realEstateMobile.dev; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = "Release-dev"; + }; 249021D3217E4FDB00AE95B9 /* Profile */ = { isa = XCBuildConfiguration; buildSettings = { @@ -486,6 +598,22 @@ }; name = Profile; }; + 2D0A74D8CD7A709DE06DED90 /* Profile-demo */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = E88CBAA9AD79E664A4EFB8CB /* Pods-RunnerTests.profile-demo.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.requestn.realEstateMobile.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = "Profile-demo"; + }; 331C8088294A63A400263BE5 /* Debug */ = { isa = XCBuildConfiguration; baseConfigurationReference = 4066250348A89E723B2219B0 /* Pods-RunnerTests.debug.xcconfig */; @@ -536,6 +664,231 @@ }; name = Profile; }; + 507F3A49087C041C0B0ED844 /* Profile-demo */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + DEVELOPMENT_TEAM = JKMS87XDA9; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.requestn.realEstateMobile.demo; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = "Profile-demo"; + }; + 5E0EA2FFDC483989BE6C01C4 /* Release-demo */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = "Release-demo"; + }; + 6D8C679E5CD44ECF0FC9C0A3 /* Debug-demo */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = FF6FDC5BF51328C7DFB6EFBC /* Pods-RunnerTests.debug-demo.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.requestn.realEstateMobile.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = "Debug-demo"; + }; + 72CEF23ECB3DFA610555F226 /* Release-demo */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + DEVELOPMENT_TEAM = JKMS87XDA9; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.requestn.realEstateMobile.demo; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = "Release-demo"; + }; + 7CC3A09D3400C108F6A29007 /* Profile-dev */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = C91BA0EAE82DD425F19AE333 /* Pods-RunnerTests.profile-dev.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.requestn.realEstateMobile.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = "Profile-dev"; + }; + 82DB9D5FA2D536E02CF6EAF9 /* Debug-local */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + DEVELOPMENT_TEAM = JKMS87XDA9; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.requestn.realEstateMobile.local; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = "Debug-local"; + }; + 89C00FD288CE9A6E1BD8AA20 /* Profile-demo */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = "Profile-demo"; + }; + 8FD2AAFB98AB2839A7F5947D /* Debug-local */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 153AF4674E578754D224255A /* Pods-RunnerTests.debug-local.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.requestn.realEstateMobile.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = "Debug-local"; + }; 97C147031CF9000F007C117D /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { @@ -692,6 +1045,529 @@ }; name = Release; }; + 9DC995FEBE33FB9A4EB1955F /* Release-local */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = C12B02D43854AE83D024ADEC /* Pods-RunnerTests.release-local.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.requestn.realEstateMobile.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = "Release-local"; + }; + 9FAF9A229574206827263467 /* Profile-local */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = "Profile-local"; + }; + A3D29274AC287851224B521F /* Profile-local */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + DEVELOPMENT_TEAM = JKMS87XDA9; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.requestn.realEstateMobile.local; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = "Profile-local"; + }; + BD4547ED905A98C2070BB50D /* Debug-demo */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + DEVELOPMENT_TEAM = JKMS87XDA9; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.requestn.realEstateMobile.demo; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = "Debug-demo"; + }; + C16EE1C09FACCCAFF3F106D3 /* Release-dev */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = "Release-dev"; + }; + CD648F6540A5F518427BEB29 /* Debug-dev */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + DEVELOPMENT_TEAM = JKMS87XDA9; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.requestn.realEstateMobile.dev; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = "Debug-dev"; + }; + D2BDE725A9EA16DD5AD25757 /* Profile-dev */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + DEVELOPMENT_TEAM = JKMS87XDA9; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.requestn.realEstateMobile.dev; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = "Profile-dev"; + }; + D69EF99503ECFB21873F8F3A /* Release-demo */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 2D8696491011AC22E03BA171 /* Pods-RunnerTests.release-demo.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.requestn.realEstateMobile.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = "Release-demo"; + }; + DF7479150487BD8C7DEB6BDE /* Debug-dev */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = "Debug-dev"; + }; + E6963388CA2E4E9A12B711ED /* Release-local */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + DEVELOPMENT_TEAM = JKMS87XDA9; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.requestn.realEstateMobile.local; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = "Release-local"; + }; + E6D1F10A5742828D7DE891B5 /* Debug-local */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = "Debug-local"; + }; + E7ABD7387B22B2F5EB611A8E /* Debug-dev */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 85DDCFDDDF32782AA3D474B8 /* Pods-RunnerTests.debug-dev.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.requestn.realEstateMobile.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = "Debug-dev"; + }; + E86648F9A4C341AB32C0A1F0 /* Debug-demo */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = "Debug-demo"; + }; + F8EFA459AC82A2082BC387A9 /* Profile-local */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = D01B1D4903412E4E802B79FE /* Pods-RunnerTests.profile-local.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.requestn.realEstateMobile.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = "Profile-local"; + }; + FB56534F09D8A539306E474A /* Release-dev */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 1374C3BCAF51A7D21E3D1461 /* Pods-RunnerTests.release-dev.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.requestn.realEstateMobile.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = "Release-dev"; + }; + FB8168435EC5DF40B90BD5B2 /* Profile-dev */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = "Profile-dev"; + }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ @@ -701,6 +1577,15 @@ 331C8088294A63A400263BE5 /* Debug */, 331C8089294A63A400263BE5 /* Release */, 331C808A294A63A400263BE5 /* Profile */, + 8FD2AAFB98AB2839A7F5947D /* Debug-local */, + 9DC995FEBE33FB9A4EB1955F /* Release-local */, + F8EFA459AC82A2082BC387A9 /* Profile-local */, + 6D8C679E5CD44ECF0FC9C0A3 /* Debug-demo */, + D69EF99503ECFB21873F8F3A /* Release-demo */, + 2D0A74D8CD7A709DE06DED90 /* Profile-demo */, + E7ABD7387B22B2F5EB611A8E /* Debug-dev */, + FB56534F09D8A539306E474A /* Release-dev */, + 7CC3A09D3400C108F6A29007 /* Profile-dev */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; @@ -711,6 +1596,15 @@ 97C147031CF9000F007C117D /* Debug */, 97C147041CF9000F007C117D /* Release */, 249021D3217E4FDB00AE95B9 /* Profile */, + E6D1F10A5742828D7DE891B5 /* Debug-local */, + 0158064B71F36128EA5718A4 /* Release-local */, + 9FAF9A229574206827263467 /* Profile-local */, + E86648F9A4C341AB32C0A1F0 /* Debug-demo */, + 5E0EA2FFDC483989BE6C01C4 /* Release-demo */, + 89C00FD288CE9A6E1BD8AA20 /* Profile-demo */, + DF7479150487BD8C7DEB6BDE /* Debug-dev */, + C16EE1C09FACCCAFF3F106D3 /* Release-dev */, + FB8168435EC5DF40B90BD5B2 /* Profile-dev */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; @@ -721,6 +1615,15 @@ 97C147061CF9000F007C117D /* Debug */, 97C147071CF9000F007C117D /* Release */, 249021D4217E4FDB00AE95B9 /* Profile */, + 82DB9D5FA2D536E02CF6EAF9 /* Debug-local */, + E6963388CA2E4E9A12B711ED /* Release-local */, + A3D29274AC287851224B521F /* Profile-local */, + BD4547ED905A98C2070BB50D /* Debug-demo */, + 72CEF23ECB3DFA610555F226 /* Release-demo */, + 507F3A49087C041C0B0ED844 /* Profile-demo */, + CD648F6540A5F518427BEB29 /* Debug-dev */, + 0BED8561365AA0F03990AE37 /* Release-dev */, + D2BDE725A9EA16DD5AD25757 /* Profile-dev */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; diff --git a/ios/Runner.xcodeproj/xcshareddata/xcschemes/demo.xcscheme b/ios/Runner.xcodeproj/xcshareddata/xcschemes/demo.xcscheme new file mode 100644 index 0000000..abbaaa1 --- /dev/null +++ b/ios/Runner.xcodeproj/xcshareddata/xcschemes/demo.xcscheme @@ -0,0 +1,101 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ios/Runner.xcodeproj/xcshareddata/xcschemes/dev.xcscheme b/ios/Runner.xcodeproj/xcshareddata/xcschemes/dev.xcscheme new file mode 100644 index 0000000..d5ed531 --- /dev/null +++ b/ios/Runner.xcodeproj/xcshareddata/xcschemes/dev.xcscheme @@ -0,0 +1,101 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ios/Runner.xcodeproj/xcshareddata/xcschemes/local.xcscheme b/ios/Runner.xcodeproj/xcshareddata/xcschemes/local.xcscheme new file mode 100644 index 0000000..6af6cc6 --- /dev/null +++ b/ios/Runner.xcodeproj/xcshareddata/xcschemes/local.xcscheme @@ -0,0 +1,101 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist index ec6fbf7..3b6cf9e 100644 --- a/ios/Runner/Info.plist +++ b/ios/Runner/Info.plist @@ -7,7 +7,7 @@ CFBundleDevelopmentRegion $(DEVELOPMENT_LANGUAGE) CFBundleDisplayName - Real Estate Mobile + $(DISPLAY_NAME) CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier diff --git a/lib/config/app_config.dart b/lib/config/app_config.dart new file mode 100644 index 0000000..ec35109 --- /dev/null +++ b/lib/config/app_config.dart @@ -0,0 +1,16 @@ +import 'package:flutter_dotenv/flutter_dotenv.dart'; + +enum Flavor { local, demo, dev } + +class AppConfig { + static late Flavor flavor; + + static String get envFileName => '.env.${flavor.name}'; + + static Future init(Flavor f) async { + flavor = f; + await dotenv.load(fileName: envFileName); + } + + static String get apiBaseUrl => dotenv.env['API_BASE_URL'] ?? ''; +} diff --git a/lib/core/constants/api_constants.dart b/lib/core/constants/api_constants.dart new file mode 100644 index 0000000..251c2c5 --- /dev/null +++ b/lib/core/constants/api_constants.dart @@ -0,0 +1,13 @@ +class ApiConstants { + ApiConstants._(); + + static const String authRegister = '/auth/register'; + static const String authLogin = '/auth/login'; + static const String authForgotPassword = '/auth/forgot-password'; + static const String authResetPassword = '/auth/reset-password'; + static const String authVerifyEmail = '/auth/verify-email'; + static const String authResendVerification = '/auth/resend-verification'; + static const String authMe = '/auth/me'; + static const String authRefresh = '/auth/refresh'; + static const String authLogout = '/auth/logout'; +} diff --git a/lib/core/constants/app_colors.dart b/lib/core/constants/app_colors.dart new file mode 100644 index 0000000..88529f9 --- /dev/null +++ b/lib/core/constants/app_colors.dart @@ -0,0 +1,20 @@ +import 'package:flutter/material.dart'; + +class AppColors { + AppColors._(); + + static const Color primaryDark = Color(0xFF00293D); + static const Color accentOrange = Color(0xFFE58625); + static const Color accentOrangeHover = Color(0xFFD47A1F); + static const Color gradientStart = Color(0xFFC4D9D4); + static const Color gradientEnd = Color(0xFFF0F5FC); + static const Color inputFill = Color(0xFFF0F5FC); + static const Color white = Color(0xFFFFFFFF); + static const Color error = Color(0xFFDC2626); + static const Color errorBg = Color(0xFFFEF2F2); + static const Color errorBorder = Color(0xFFFECACA); + static const Color success = Color(0xFF22C55E); + static const Color divider = Color(0x3300293D); // 20% opacity + static const Color hintText = Color(0x9900293D); // 60% opacity + static const Color subtleText = Color(0x7000293D); // ~44% opacity +} diff --git a/lib/core/network/api_client.dart b/lib/core/network/api_client.dart new file mode 100644 index 0000000..1e85cf2 --- /dev/null +++ b/lib/core/network/api_client.dart @@ -0,0 +1,92 @@ +import 'package:dio/dio.dart'; +import 'package:real_estate_mobile/config/app_config.dart'; +import 'package:real_estate_mobile/core/network/api_exceptions.dart'; +import 'package:real_estate_mobile/core/storage/secure_storage.dart'; + +class ApiClient { + static ApiClient? _instance; + late final Dio _dio; + + ApiClient._() { + _dio = Dio( + BaseOptions( + baseUrl: AppConfig.apiBaseUrl, + connectTimeout: const Duration(seconds: 30), + receiveTimeout: const Duration(seconds: 30), + headers: { + 'Content-Type': 'application/json', + 'Accept': 'application/json', + }, + ), + ); + + _dio.interceptors.addAll([ + _authInterceptor(), + _errorInterceptor(), + ]); + } + + static ApiClient get instance { + _instance ??= ApiClient._(); + return _instance!; + } + + Dio get dio => _dio; + + InterceptorsWrapper _authInterceptor() { + return InterceptorsWrapper( + onRequest: (options, handler) async { + final token = await SecureStorage.getAccessToken(); + if (token != null) { + options.headers['Authorization'] = 'Bearer $token'; + } + handler.next(options); + }, + ); + } + + InterceptorsWrapper _errorInterceptor() { + return InterceptorsWrapper( + onError: (error, handler) { + final response = error.response; + if (response != null) { + final data = response.data; + if (data is Map) { + final message = data['message'] is List + ? (data['message'] as List).join(', ') + : data['message']?.toString() ?? 'An error occurred'; + + final errors = (data['errors'] as List?) + ?.map((e) => FieldError.fromJson(e as Map)) + .toList() ?? + []; + + handler.reject( + DioException( + requestOptions: error.requestOptions, + response: error.response, + error: ApiException( + message: message, + statusCode: response.statusCode, + fieldErrors: errors, + ), + ), + ); + return; + } + } + + handler.reject( + DioException( + requestOptions: error.requestOptions, + response: error.response, + error: ApiException( + message: error.message ?? 'Network error occurred', + statusCode: response?.statusCode, + ), + ), + ); + }, + ); + } +} diff --git a/lib/core/network/api_exceptions.dart b/lib/core/network/api_exceptions.dart new file mode 100644 index 0000000..5c42b76 --- /dev/null +++ b/lib/core/network/api_exceptions.dart @@ -0,0 +1,31 @@ +class ApiException implements Exception { + final String message; + final int? statusCode; + final List fieldErrors; + + const ApiException({ + required this.message, + this.statusCode, + this.fieldErrors = const [], + }); + + @override + String toString() => 'ApiException: $message (status: $statusCode)'; +} + +class FieldError { + final String field; + final List errors; + + const FieldError({required this.field, required this.errors}); + + factory FieldError.fromJson(Map json) { + return FieldError( + field: json['field'] as String? ?? '', + errors: (json['errors'] as List?) + ?.map((e) => e.toString()) + .toList() ?? + [], + ); + } +} diff --git a/lib/core/storage/secure_storage.dart b/lib/core/storage/secure_storage.dart new file mode 100644 index 0000000..ae73f71 --- /dev/null +++ b/lib/core/storage/secure_storage.dart @@ -0,0 +1,33 @@ +import 'package:flutter_secure_storage/flutter_secure_storage.dart'; + +class SecureStorage { + static const _storage = FlutterSecureStorage(); + + static const _accessTokenKey = 'access_token'; + static const _refreshTokenKey = 'refresh_token'; + + static Future saveTokens({ + required String accessToken, + required String refreshToken, + }) async { + await Future.wait([ + _storage.write(key: _accessTokenKey, value: accessToken), + _storage.write(key: _refreshTokenKey, value: refreshToken), + ]); + } + + static Future getAccessToken() async { + return _storage.read(key: _accessTokenKey); + } + + static Future getRefreshToken() async { + return _storage.read(key: _refreshTokenKey); + } + + static Future clearTokens() async { + await Future.wait([ + _storage.delete(key: _accessTokenKey), + _storage.delete(key: _refreshTokenKey), + ]); + } +} diff --git a/lib/core/theme/app_theme.dart b/lib/core/theme/app_theme.dart new file mode 100644 index 0000000..9e0b3b3 --- /dev/null +++ b/lib/core/theme/app_theme.dart @@ -0,0 +1,118 @@ +import 'package:flutter/material.dart'; +import 'package:real_estate_mobile/core/constants/app_colors.dart'; + +class AppTheme { + AppTheme._(); + + static ThemeData get light { + return ThemeData( + useMaterial3: true, + fontFamily: 'Fractul', + scaffoldBackgroundColor: AppColors.white, + colorScheme: ColorScheme.fromSeed( + seedColor: AppColors.primaryDark, + primary: AppColors.primaryDark, + secondary: AppColors.accentOrange, + surface: AppColors.white, + error: AppColors.error, + ), + textTheme: const TextTheme( + headlineLarge: TextStyle( + fontFamily: 'Fractul', + fontSize: 30, + fontWeight: FontWeight.w400, + color: AppColors.primaryDark, + ), + headlineMedium: TextStyle( + fontFamily: 'Fractul', + fontSize: 25, + fontWeight: FontWeight.w400, + color: AppColors.primaryDark, + ), + titleMedium: TextStyle( + fontFamily: 'Fractul', + fontSize: 16, + fontWeight: FontWeight.w400, + color: AppColors.primaryDark, + ), + bodyMedium: TextStyle( + fontFamily: 'Fractul', + fontSize: 14, + fontWeight: FontWeight.w300, + color: AppColors.primaryDark, + ), + bodySmall: TextStyle( + fontFamily: 'Fractul', + fontSize: 12, + fontWeight: FontWeight.w300, + color: AppColors.hintText, + ), + labelLarge: TextStyle( + fontFamily: 'Fractul', + fontSize: 14, + fontWeight: FontWeight.w700, + color: AppColors.primaryDark, + ), + ), + inputDecorationTheme: InputDecorationTheme( + filled: true, + fillColor: AppColors.inputFill, + contentPadding: const EdgeInsets.symmetric( + horizontal: 24, + vertical: 20, + ), + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(20), + borderSide: BorderSide.none, + ), + enabledBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(20), + borderSide: BorderSide.none, + ), + focusedBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(20), + borderSide: const BorderSide( + color: AppColors.primaryDark, + width: 1.5, + ), + ), + errorBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(20), + borderSide: const BorderSide( + color: AppColors.error, + width: 1.5, + ), + ), + focusedErrorBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(20), + borderSide: const BorderSide( + color: AppColors.error, + width: 1.5, + ), + ), + hintStyle: const TextStyle( + fontFamily: 'Fractul', + fontSize: 14, + fontWeight: FontWeight.w300, + color: AppColors.primaryDark, + ), + ), + elevatedButtonTheme: ElevatedButtonThemeData( + style: ElevatedButton.styleFrom( + backgroundColor: AppColors.accentOrange, + foregroundColor: AppColors.primaryDark, + elevation: 0, + padding: const EdgeInsets.symmetric(vertical: 20), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(20), + ), + textStyle: const TextStyle( + fontFamily: 'Fractul', + fontSize: 14, + fontWeight: FontWeight.w700, + ), + ), + ), + ); + } +} diff --git a/lib/features/auth/data/auth_repository.dart b/lib/features/auth/data/auth_repository.dart new file mode 100644 index 0000000..688cbd6 --- /dev/null +++ b/lib/features/auth/data/auth_repository.dart @@ -0,0 +1,81 @@ +import 'package:dio/dio.dart'; +import 'package:real_estate_mobile/core/constants/api_constants.dart'; +import 'package:real_estate_mobile/core/network/api_client.dart'; +import 'package:real_estate_mobile/core/network/api_exceptions.dart'; +import 'package:real_estate_mobile/core/storage/secure_storage.dart'; +import 'package:real_estate_mobile/features/auth/data/models/auth_response.dart'; +import 'package:real_estate_mobile/features/auth/data/models/register_request.dart'; +import 'package:real_estate_mobile/features/auth/data/models/user_model.dart'; + +class AuthRepository { + final Dio _dio = ApiClient.instance.dio; + + Future> login({ + required String email, + required String password, + }) async { + try { + final response = await _dio.post( + ApiConstants.authLogin, + data: {'email': email, 'password': password}, + ); + + final data = response.data['data'] as Map; + + // Check if 2FA is required + if (data['requiresTwoFactor'] == true) { + return { + 'requiresTwoFactor': true, + 'tempToken': data['tempToken'], + }; + } + + final authResponse = AuthResponse.fromJson(data); + + await SecureStorage.saveTokens( + accessToken: authResponse.accessToken, + refreshToken: authResponse.refreshToken, + ); + + return { + 'requiresTwoFactor': false, + 'user': authResponse.user, + }; + } on DioException catch (e) { + if (e.error is ApiException) { + throw e.error as ApiException; + } + throw ApiException( + message: e.message ?? 'Login failed', + statusCode: e.response?.statusCode, + ); + } + } + + Future register(RegisterRequest request) async { + try { + final response = await _dio.post( + ApiConstants.authRegister, + data: request.toJson(), + ); + + final data = response.data['data'] as Map; + final authResponse = AuthResponse.fromJson(data); + + await SecureStorage.saveTokens( + accessToken: authResponse.accessToken, + refreshToken: authResponse.refreshToken, + ); + + return authResponse.user; + } on DioException catch (e) { + if (e.error is ApiException) { + throw e.error as ApiException; + } + throw ApiException( + message: e.message ?? 'Registration failed', + statusCode: e.response?.statusCode, + ); + } + } +} diff --git a/lib/features/auth/data/models/auth_response.dart b/lib/features/auth/data/models/auth_response.dart new file mode 100644 index 0000000..b0ac904 --- /dev/null +++ b/lib/features/auth/data/models/auth_response.dart @@ -0,0 +1,17 @@ +import 'package:freezed_annotation/freezed_annotation.dart'; +import 'package:real_estate_mobile/features/auth/data/models/user_model.dart'; + +part 'auth_response.freezed.dart'; +part 'auth_response.g.dart'; + +@freezed +abstract class AuthResponse with _$AuthResponse { + const factory AuthResponse({ + required UserModel user, + required String accessToken, + required String refreshToken, + }) = _AuthResponse; + + factory AuthResponse.fromJson(Map json) => + _$AuthResponseFromJson(json); +} diff --git a/lib/features/auth/data/models/auth_response.freezed.dart b/lib/features/auth/data/models/auth_response.freezed.dart new file mode 100644 index 0000000..4cb49e5 --- /dev/null +++ b/lib/features/auth/data/models/auth_response.freezed.dart @@ -0,0 +1,301 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND +// coverage:ignore-file +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'auth_response.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +// dart format off +T _$identity(T value) => value; + +/// @nodoc +mixin _$AuthResponse { + + UserModel get user; String get accessToken; String get refreshToken; +/// Create a copy of AuthResponse +/// with the given fields replaced by the non-null parameter values. +@JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +$AuthResponseCopyWith get copyWith => _$AuthResponseCopyWithImpl(this as AuthResponse, _$identity); + + /// Serializes this AuthResponse to a JSON map. + Map toJson(); + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is AuthResponse&&(identical(other.user, user) || other.user == user)&&(identical(other.accessToken, accessToken) || other.accessToken == accessToken)&&(identical(other.refreshToken, refreshToken) || other.refreshToken == refreshToken)); +} + +@JsonKey(includeFromJson: false, includeToJson: false) +@override +int get hashCode => Object.hash(runtimeType,user,accessToken,refreshToken); + +@override +String toString() { + return 'AuthResponse(user: $user, accessToken: $accessToken, refreshToken: $refreshToken)'; +} + + +} + +/// @nodoc +abstract mixin class $AuthResponseCopyWith<$Res> { + factory $AuthResponseCopyWith(AuthResponse value, $Res Function(AuthResponse) _then) = _$AuthResponseCopyWithImpl; +@useResult +$Res call({ + UserModel user, String accessToken, String refreshToken +}); + + +$UserModelCopyWith<$Res> get user; + +} +/// @nodoc +class _$AuthResponseCopyWithImpl<$Res> + implements $AuthResponseCopyWith<$Res> { + _$AuthResponseCopyWithImpl(this._self, this._then); + + final AuthResponse _self; + final $Res Function(AuthResponse) _then; + +/// Create a copy of AuthResponse +/// with the given fields replaced by the non-null parameter values. +@pragma('vm:prefer-inline') @override $Res call({Object? user = null,Object? accessToken = null,Object? refreshToken = null,}) { + return _then(_self.copyWith( +user: null == user ? _self.user : user // ignore: cast_nullable_to_non_nullable +as UserModel,accessToken: null == accessToken ? _self.accessToken : accessToken // ignore: cast_nullable_to_non_nullable +as String,refreshToken: null == refreshToken ? _self.refreshToken : refreshToken // ignore: cast_nullable_to_non_nullable +as String, + )); +} +/// Create a copy of AuthResponse +/// with the given fields replaced by the non-null parameter values. +@override +@pragma('vm:prefer-inline') +$UserModelCopyWith<$Res> get user { + + return $UserModelCopyWith<$Res>(_self.user, (value) { + return _then(_self.copyWith(user: value)); + }); +} +} + + +/// Adds pattern-matching-related methods to [AuthResponse]. +extension AuthResponsePatterns on AuthResponse { +/// A variant of `map` that fallback to returning `orElse`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeMap(TResult Function( _AuthResponse value)? $default,{required TResult orElse(),}){ +final _that = this; +switch (_that) { +case _AuthResponse() when $default != null: +return $default(_that);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// Callbacks receives the raw object, upcasted. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case final Subclass2 value: +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult map(TResult Function( _AuthResponse value) $default,){ +final _that = this; +switch (_that) { +case _AuthResponse(): +return $default(_that);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `map` that fallback to returning `null`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? mapOrNull(TResult? Function( _AuthResponse value)? $default,){ +final _that = this; +switch (_that) { +case _AuthResponse() when $default != null: +return $default(_that);case _: + return null; + +} +} +/// A variant of `when` that fallback to an `orElse` callback. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeWhen(TResult Function( UserModel user, String accessToken, String refreshToken)? $default,{required TResult orElse(),}) {final _that = this; +switch (_that) { +case _AuthResponse() when $default != null: +return $default(_that.user,_that.accessToken,_that.refreshToken);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// As opposed to `map`, this offers destructuring. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case Subclass2(:final field2): +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult when(TResult Function( UserModel user, String accessToken, String refreshToken) $default,) {final _that = this; +switch (_that) { +case _AuthResponse(): +return $default(_that.user,_that.accessToken,_that.refreshToken);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `when` that fallback to returning `null` +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? whenOrNull(TResult? Function( UserModel user, String accessToken, String refreshToken)? $default,) {final _that = this; +switch (_that) { +case _AuthResponse() when $default != null: +return $default(_that.user,_that.accessToken,_that.refreshToken);case _: + return null; + +} +} + +} + +/// @nodoc +@JsonSerializable() + +class _AuthResponse implements AuthResponse { + const _AuthResponse({required this.user, required this.accessToken, required this.refreshToken}); + factory _AuthResponse.fromJson(Map json) => _$AuthResponseFromJson(json); + +@override final UserModel user; +@override final String accessToken; +@override final String refreshToken; + +/// Create a copy of AuthResponse +/// with the given fields replaced by the non-null parameter values. +@override @JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +_$AuthResponseCopyWith<_AuthResponse> get copyWith => __$AuthResponseCopyWithImpl<_AuthResponse>(this, _$identity); + +@override +Map toJson() { + return _$AuthResponseToJson(this, ); +} + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is _AuthResponse&&(identical(other.user, user) || other.user == user)&&(identical(other.accessToken, accessToken) || other.accessToken == accessToken)&&(identical(other.refreshToken, refreshToken) || other.refreshToken == refreshToken)); +} + +@JsonKey(includeFromJson: false, includeToJson: false) +@override +int get hashCode => Object.hash(runtimeType,user,accessToken,refreshToken); + +@override +String toString() { + return 'AuthResponse(user: $user, accessToken: $accessToken, refreshToken: $refreshToken)'; +} + + +} + +/// @nodoc +abstract mixin class _$AuthResponseCopyWith<$Res> implements $AuthResponseCopyWith<$Res> { + factory _$AuthResponseCopyWith(_AuthResponse value, $Res Function(_AuthResponse) _then) = __$AuthResponseCopyWithImpl; +@override @useResult +$Res call({ + UserModel user, String accessToken, String refreshToken +}); + + +@override $UserModelCopyWith<$Res> get user; + +} +/// @nodoc +class __$AuthResponseCopyWithImpl<$Res> + implements _$AuthResponseCopyWith<$Res> { + __$AuthResponseCopyWithImpl(this._self, this._then); + + final _AuthResponse _self; + final $Res Function(_AuthResponse) _then; + +/// Create a copy of AuthResponse +/// with the given fields replaced by the non-null parameter values. +@override @pragma('vm:prefer-inline') $Res call({Object? user = null,Object? accessToken = null,Object? refreshToken = null,}) { + return _then(_AuthResponse( +user: null == user ? _self.user : user // ignore: cast_nullable_to_non_nullable +as UserModel,accessToken: null == accessToken ? _self.accessToken : accessToken // ignore: cast_nullable_to_non_nullable +as String,refreshToken: null == refreshToken ? _self.refreshToken : refreshToken // ignore: cast_nullable_to_non_nullable +as String, + )); +} + +/// Create a copy of AuthResponse +/// with the given fields replaced by the non-null parameter values. +@override +@pragma('vm:prefer-inline') +$UserModelCopyWith<$Res> get user { + + return $UserModelCopyWith<$Res>(_self.user, (value) { + return _then(_self.copyWith(user: value)); + }); +} +} + +// dart format on diff --git a/lib/features/auth/data/models/auth_response.g.dart b/lib/features/auth/data/models/auth_response.g.dart new file mode 100644 index 0000000..2bf06f7 --- /dev/null +++ b/lib/features/auth/data/models/auth_response.g.dart @@ -0,0 +1,21 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'auth_response.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +_AuthResponse _$AuthResponseFromJson(Map json) => + _AuthResponse( + user: UserModel.fromJson(json['user'] as Map), + accessToken: json['accessToken'] as String, + refreshToken: json['refreshToken'] as String, + ); + +Map _$AuthResponseToJson(_AuthResponse instance) => + { + 'user': instance.user, + 'accessToken': instance.accessToken, + 'refreshToken': instance.refreshToken, + }; diff --git a/lib/features/auth/data/models/register_request.dart b/lib/features/auth/data/models/register_request.dart new file mode 100644 index 0000000..7b86157 --- /dev/null +++ b/lib/features/auth/data/models/register_request.dart @@ -0,0 +1,18 @@ +import 'package:freezed_annotation/freezed_annotation.dart'; + +part 'register_request.freezed.dart'; +part 'register_request.g.dart'; + +@freezed +abstract class RegisterRequest with _$RegisterRequest { + const factory RegisterRequest({ + required String email, + required String password, + required String role, + String? firstName, + String? lastName, + }) = _RegisterRequest; + + factory RegisterRequest.fromJson(Map json) => + _$RegisterRequestFromJson(json); +} diff --git a/lib/features/auth/data/models/register_request.freezed.dart b/lib/features/auth/data/models/register_request.freezed.dart new file mode 100644 index 0000000..2259a83 --- /dev/null +++ b/lib/features/auth/data/models/register_request.freezed.dart @@ -0,0 +1,289 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND +// coverage:ignore-file +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'register_request.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +// dart format off +T _$identity(T value) => value; + +/// @nodoc +mixin _$RegisterRequest { + + String get email; String get password; String get role; String? get firstName; String? get lastName; +/// Create a copy of RegisterRequest +/// with the given fields replaced by the non-null parameter values. +@JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +$RegisterRequestCopyWith get copyWith => _$RegisterRequestCopyWithImpl(this as RegisterRequest, _$identity); + + /// Serializes this RegisterRequest to a JSON map. + Map toJson(); + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is RegisterRequest&&(identical(other.email, email) || other.email == email)&&(identical(other.password, password) || other.password == password)&&(identical(other.role, role) || other.role == role)&&(identical(other.firstName, firstName) || other.firstName == firstName)&&(identical(other.lastName, lastName) || other.lastName == lastName)); +} + +@JsonKey(includeFromJson: false, includeToJson: false) +@override +int get hashCode => Object.hash(runtimeType,email,password,role,firstName,lastName); + +@override +String toString() { + return 'RegisterRequest(email: $email, password: $password, role: $role, firstName: $firstName, lastName: $lastName)'; +} + + +} + +/// @nodoc +abstract mixin class $RegisterRequestCopyWith<$Res> { + factory $RegisterRequestCopyWith(RegisterRequest value, $Res Function(RegisterRequest) _then) = _$RegisterRequestCopyWithImpl; +@useResult +$Res call({ + String email, String password, String role, String? firstName, String? lastName +}); + + + + +} +/// @nodoc +class _$RegisterRequestCopyWithImpl<$Res> + implements $RegisterRequestCopyWith<$Res> { + _$RegisterRequestCopyWithImpl(this._self, this._then); + + final RegisterRequest _self; + final $Res Function(RegisterRequest) _then; + +/// Create a copy of RegisterRequest +/// with the given fields replaced by the non-null parameter values. +@pragma('vm:prefer-inline') @override $Res call({Object? email = null,Object? password = null,Object? role = null,Object? firstName = freezed,Object? lastName = freezed,}) { + return _then(_self.copyWith( +email: null == email ? _self.email : email // ignore: cast_nullable_to_non_nullable +as String,password: null == password ? _self.password : password // ignore: cast_nullable_to_non_nullable +as String,role: null == role ? _self.role : role // ignore: cast_nullable_to_non_nullable +as String,firstName: freezed == firstName ? _self.firstName : firstName // ignore: cast_nullable_to_non_nullable +as String?,lastName: freezed == lastName ? _self.lastName : lastName // ignore: cast_nullable_to_non_nullable +as String?, + )); +} + +} + + +/// Adds pattern-matching-related methods to [RegisterRequest]. +extension RegisterRequestPatterns on RegisterRequest { +/// A variant of `map` that fallback to returning `orElse`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeMap(TResult Function( _RegisterRequest value)? $default,{required TResult orElse(),}){ +final _that = this; +switch (_that) { +case _RegisterRequest() when $default != null: +return $default(_that);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// Callbacks receives the raw object, upcasted. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case final Subclass2 value: +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult map(TResult Function( _RegisterRequest value) $default,){ +final _that = this; +switch (_that) { +case _RegisterRequest(): +return $default(_that);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `map` that fallback to returning `null`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? mapOrNull(TResult? Function( _RegisterRequest value)? $default,){ +final _that = this; +switch (_that) { +case _RegisterRequest() when $default != null: +return $default(_that);case _: + return null; + +} +} +/// A variant of `when` that fallback to an `orElse` callback. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeWhen(TResult Function( String email, String password, String role, String? firstName, String? lastName)? $default,{required TResult orElse(),}) {final _that = this; +switch (_that) { +case _RegisterRequest() when $default != null: +return $default(_that.email,_that.password,_that.role,_that.firstName,_that.lastName);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// As opposed to `map`, this offers destructuring. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case Subclass2(:final field2): +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult when(TResult Function( String email, String password, String role, String? firstName, String? lastName) $default,) {final _that = this; +switch (_that) { +case _RegisterRequest(): +return $default(_that.email,_that.password,_that.role,_that.firstName,_that.lastName);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `when` that fallback to returning `null` +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? whenOrNull(TResult? Function( String email, String password, String role, String? firstName, String? lastName)? $default,) {final _that = this; +switch (_that) { +case _RegisterRequest() when $default != null: +return $default(_that.email,_that.password,_that.role,_that.firstName,_that.lastName);case _: + return null; + +} +} + +} + +/// @nodoc +@JsonSerializable() + +class _RegisterRequest implements RegisterRequest { + const _RegisterRequest({required this.email, required this.password, required this.role, this.firstName, this.lastName}); + factory _RegisterRequest.fromJson(Map json) => _$RegisterRequestFromJson(json); + +@override final String email; +@override final String password; +@override final String role; +@override final String? firstName; +@override final String? lastName; + +/// Create a copy of RegisterRequest +/// with the given fields replaced by the non-null parameter values. +@override @JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +_$RegisterRequestCopyWith<_RegisterRequest> get copyWith => __$RegisterRequestCopyWithImpl<_RegisterRequest>(this, _$identity); + +@override +Map toJson() { + return _$RegisterRequestToJson(this, ); +} + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is _RegisterRequest&&(identical(other.email, email) || other.email == email)&&(identical(other.password, password) || other.password == password)&&(identical(other.role, role) || other.role == role)&&(identical(other.firstName, firstName) || other.firstName == firstName)&&(identical(other.lastName, lastName) || other.lastName == lastName)); +} + +@JsonKey(includeFromJson: false, includeToJson: false) +@override +int get hashCode => Object.hash(runtimeType,email,password,role,firstName,lastName); + +@override +String toString() { + return 'RegisterRequest(email: $email, password: $password, role: $role, firstName: $firstName, lastName: $lastName)'; +} + + +} + +/// @nodoc +abstract mixin class _$RegisterRequestCopyWith<$Res> implements $RegisterRequestCopyWith<$Res> { + factory _$RegisterRequestCopyWith(_RegisterRequest value, $Res Function(_RegisterRequest) _then) = __$RegisterRequestCopyWithImpl; +@override @useResult +$Res call({ + String email, String password, String role, String? firstName, String? lastName +}); + + + + +} +/// @nodoc +class __$RegisterRequestCopyWithImpl<$Res> + implements _$RegisterRequestCopyWith<$Res> { + __$RegisterRequestCopyWithImpl(this._self, this._then); + + final _RegisterRequest _self; + final $Res Function(_RegisterRequest) _then; + +/// Create a copy of RegisterRequest +/// with the given fields replaced by the non-null parameter values. +@override @pragma('vm:prefer-inline') $Res call({Object? email = null,Object? password = null,Object? role = null,Object? firstName = freezed,Object? lastName = freezed,}) { + return _then(_RegisterRequest( +email: null == email ? _self.email : email // ignore: cast_nullable_to_non_nullable +as String,password: null == password ? _self.password : password // ignore: cast_nullable_to_non_nullable +as String,role: null == role ? _self.role : role // ignore: cast_nullable_to_non_nullable +as String,firstName: freezed == firstName ? _self.firstName : firstName // ignore: cast_nullable_to_non_nullable +as String?,lastName: freezed == lastName ? _self.lastName : lastName // ignore: cast_nullable_to_non_nullable +as String?, + )); +} + + +} + +// dart format on diff --git a/lib/features/auth/data/models/register_request.g.dart b/lib/features/auth/data/models/register_request.g.dart new file mode 100644 index 0000000..5177f06 --- /dev/null +++ b/lib/features/auth/data/models/register_request.g.dart @@ -0,0 +1,25 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'register_request.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +_RegisterRequest _$RegisterRequestFromJson(Map json) => + _RegisterRequest( + email: json['email'] as String, + password: json['password'] as String, + role: json['role'] as String, + firstName: json['firstName'] as String?, + lastName: json['lastName'] as String?, + ); + +Map _$RegisterRequestToJson(_RegisterRequest instance) => + { + 'email': instance.email, + 'password': instance.password, + 'role': instance.role, + 'firstName': instance.firstName, + 'lastName': instance.lastName, + }; diff --git a/lib/features/auth/data/models/user_model.dart b/lib/features/auth/data/models/user_model.dart new file mode 100644 index 0000000..052681e --- /dev/null +++ b/lib/features/auth/data/models/user_model.dart @@ -0,0 +1,19 @@ +import 'package:freezed_annotation/freezed_annotation.dart'; + +part 'user_model.freezed.dart'; +part 'user_model.g.dart'; + +@freezed +abstract class UserModel with _$UserModel { + const factory UserModel({ + required String id, + required String email, + required String role, + String? firstName, + String? lastName, + String? avatar, + }) = _UserModel; + + factory UserModel.fromJson(Map json) => + _$UserModelFromJson(json); +} diff --git a/lib/features/auth/data/models/user_model.freezed.dart b/lib/features/auth/data/models/user_model.freezed.dart new file mode 100644 index 0000000..a6aca2b --- /dev/null +++ b/lib/features/auth/data/models/user_model.freezed.dart @@ -0,0 +1,292 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND +// coverage:ignore-file +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'user_model.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +// dart format off +T _$identity(T value) => value; + +/// @nodoc +mixin _$UserModel { + + String get id; String get email; String get role; String? get firstName; String? get lastName; String? get avatar; +/// Create a copy of UserModel +/// with the given fields replaced by the non-null parameter values. +@JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +$UserModelCopyWith get copyWith => _$UserModelCopyWithImpl(this as UserModel, _$identity); + + /// Serializes this UserModel to a JSON map. + Map toJson(); + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is UserModel&&(identical(other.id, id) || other.id == id)&&(identical(other.email, email) || other.email == email)&&(identical(other.role, role) || other.role == role)&&(identical(other.firstName, firstName) || other.firstName == firstName)&&(identical(other.lastName, lastName) || other.lastName == lastName)&&(identical(other.avatar, avatar) || other.avatar == avatar)); +} + +@JsonKey(includeFromJson: false, includeToJson: false) +@override +int get hashCode => Object.hash(runtimeType,id,email,role,firstName,lastName,avatar); + +@override +String toString() { + return 'UserModel(id: $id, email: $email, role: $role, firstName: $firstName, lastName: $lastName, avatar: $avatar)'; +} + + +} + +/// @nodoc +abstract mixin class $UserModelCopyWith<$Res> { + factory $UserModelCopyWith(UserModel value, $Res Function(UserModel) _then) = _$UserModelCopyWithImpl; +@useResult +$Res call({ + String id, String email, String role, String? firstName, String? lastName, String? avatar +}); + + + + +} +/// @nodoc +class _$UserModelCopyWithImpl<$Res> + implements $UserModelCopyWith<$Res> { + _$UserModelCopyWithImpl(this._self, this._then); + + final UserModel _self; + final $Res Function(UserModel) _then; + +/// Create a copy of UserModel +/// with the given fields replaced by the non-null parameter values. +@pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? email = null,Object? role = null,Object? firstName = freezed,Object? lastName = freezed,Object? avatar = freezed,}) { + return _then(_self.copyWith( +id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable +as String,email: null == email ? _self.email : email // ignore: cast_nullable_to_non_nullable +as String,role: null == role ? _self.role : role // ignore: cast_nullable_to_non_nullable +as String,firstName: freezed == firstName ? _self.firstName : firstName // ignore: cast_nullable_to_non_nullable +as String?,lastName: freezed == lastName ? _self.lastName : lastName // ignore: cast_nullable_to_non_nullable +as String?,avatar: freezed == avatar ? _self.avatar : avatar // ignore: cast_nullable_to_non_nullable +as String?, + )); +} + +} + + +/// Adds pattern-matching-related methods to [UserModel]. +extension UserModelPatterns on UserModel { +/// A variant of `map` that fallback to returning `orElse`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeMap(TResult Function( _UserModel value)? $default,{required TResult orElse(),}){ +final _that = this; +switch (_that) { +case _UserModel() when $default != null: +return $default(_that);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// Callbacks receives the raw object, upcasted. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case final Subclass2 value: +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult map(TResult Function( _UserModel value) $default,){ +final _that = this; +switch (_that) { +case _UserModel(): +return $default(_that);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `map` that fallback to returning `null`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? mapOrNull(TResult? Function( _UserModel value)? $default,){ +final _that = this; +switch (_that) { +case _UserModel() when $default != null: +return $default(_that);case _: + return null; + +} +} +/// A variant of `when` that fallback to an `orElse` callback. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeWhen(TResult Function( String id, String email, String role, String? firstName, String? lastName, String? avatar)? $default,{required TResult orElse(),}) {final _that = this; +switch (_that) { +case _UserModel() when $default != null: +return $default(_that.id,_that.email,_that.role,_that.firstName,_that.lastName,_that.avatar);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// As opposed to `map`, this offers destructuring. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case Subclass2(:final field2): +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult when(TResult Function( String id, String email, String role, String? firstName, String? lastName, String? avatar) $default,) {final _that = this; +switch (_that) { +case _UserModel(): +return $default(_that.id,_that.email,_that.role,_that.firstName,_that.lastName,_that.avatar);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `when` that fallback to returning `null` +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? whenOrNull(TResult? Function( String id, String email, String role, String? firstName, String? lastName, String? avatar)? $default,) {final _that = this; +switch (_that) { +case _UserModel() when $default != null: +return $default(_that.id,_that.email,_that.role,_that.firstName,_that.lastName,_that.avatar);case _: + return null; + +} +} + +} + +/// @nodoc +@JsonSerializable() + +class _UserModel implements UserModel { + const _UserModel({required this.id, required this.email, required this.role, this.firstName, this.lastName, this.avatar}); + factory _UserModel.fromJson(Map json) => _$UserModelFromJson(json); + +@override final String id; +@override final String email; +@override final String role; +@override final String? firstName; +@override final String? lastName; +@override final String? avatar; + +/// Create a copy of UserModel +/// with the given fields replaced by the non-null parameter values. +@override @JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +_$UserModelCopyWith<_UserModel> get copyWith => __$UserModelCopyWithImpl<_UserModel>(this, _$identity); + +@override +Map toJson() { + return _$UserModelToJson(this, ); +} + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is _UserModel&&(identical(other.id, id) || other.id == id)&&(identical(other.email, email) || other.email == email)&&(identical(other.role, role) || other.role == role)&&(identical(other.firstName, firstName) || other.firstName == firstName)&&(identical(other.lastName, lastName) || other.lastName == lastName)&&(identical(other.avatar, avatar) || other.avatar == avatar)); +} + +@JsonKey(includeFromJson: false, includeToJson: false) +@override +int get hashCode => Object.hash(runtimeType,id,email,role,firstName,lastName,avatar); + +@override +String toString() { + return 'UserModel(id: $id, email: $email, role: $role, firstName: $firstName, lastName: $lastName, avatar: $avatar)'; +} + + +} + +/// @nodoc +abstract mixin class _$UserModelCopyWith<$Res> implements $UserModelCopyWith<$Res> { + factory _$UserModelCopyWith(_UserModel value, $Res Function(_UserModel) _then) = __$UserModelCopyWithImpl; +@override @useResult +$Res call({ + String id, String email, String role, String? firstName, String? lastName, String? avatar +}); + + + + +} +/// @nodoc +class __$UserModelCopyWithImpl<$Res> + implements _$UserModelCopyWith<$Res> { + __$UserModelCopyWithImpl(this._self, this._then); + + final _UserModel _self; + final $Res Function(_UserModel) _then; + +/// Create a copy of UserModel +/// with the given fields replaced by the non-null parameter values. +@override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? email = null,Object? role = null,Object? firstName = freezed,Object? lastName = freezed,Object? avatar = freezed,}) { + return _then(_UserModel( +id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable +as String,email: null == email ? _self.email : email // ignore: cast_nullable_to_non_nullable +as String,role: null == role ? _self.role : role // ignore: cast_nullable_to_non_nullable +as String,firstName: freezed == firstName ? _self.firstName : firstName // ignore: cast_nullable_to_non_nullable +as String?,lastName: freezed == lastName ? _self.lastName : lastName // ignore: cast_nullable_to_non_nullable +as String?,avatar: freezed == avatar ? _self.avatar : avatar // ignore: cast_nullable_to_non_nullable +as String?, + )); +} + + +} + +// dart format on diff --git a/lib/features/auth/data/models/user_model.g.dart b/lib/features/auth/data/models/user_model.g.dart new file mode 100644 index 0000000..c05729d --- /dev/null +++ b/lib/features/auth/data/models/user_model.g.dart @@ -0,0 +1,26 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'user_model.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +_UserModel _$UserModelFromJson(Map json) => _UserModel( + id: json['id'] as String, + email: json['email'] as String, + role: json['role'] as String, + firstName: json['firstName'] as String?, + lastName: json['lastName'] as String?, + avatar: json['avatar'] as String?, +); + +Map _$UserModelToJson(_UserModel instance) => + { + 'id': instance.id, + 'email': instance.email, + 'role': instance.role, + 'firstName': instance.firstName, + 'lastName': instance.lastName, + 'avatar': instance.avatar, + }; diff --git a/lib/features/auth/presentation/providers/auth_provider.dart b/lib/features/auth/presentation/providers/auth_provider.dart new file mode 100644 index 0000000..daacfad --- /dev/null +++ b/lib/features/auth/presentation/providers/auth_provider.dart @@ -0,0 +1,159 @@ +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:real_estate_mobile/core/network/api_exceptions.dart'; +import 'package:real_estate_mobile/features/auth/data/auth_repository.dart'; +import 'package:real_estate_mobile/features/auth/data/models/register_request.dart'; +import 'package:real_estate_mobile/features/auth/data/models/user_model.dart'; + +// Auth state +enum AuthStatus { initial, loading, success, error } + +class AuthState { + final AuthStatus status; + final UserModel? user; + final String? errorMessage; + final List fieldErrors; + final String registeredEmail; + + const AuthState({ + this.status = AuthStatus.initial, + this.user, + this.errorMessage, + this.fieldErrors = const [], + this.registeredEmail = '', + }); + + AuthState copyWith({ + AuthStatus? status, + UserModel? user, + String? errorMessage, + List? fieldErrors, + String? registeredEmail, + }) { + return AuthState( + status: status ?? this.status, + user: user ?? this.user, + errorMessage: errorMessage, + fieldErrors: fieldErrors ?? this.fieldErrors, + registeredEmail: registeredEmail ?? this.registeredEmail, + ); + } + + String? getFieldError(String fieldName) { + final match = fieldErrors.where((e) => e.field == fieldName); + if (match.isEmpty) return null; + return match.first.errors.isNotEmpty ? match.first.errors.first : null; + } +} + +// Auth notifier +class AuthNotifier extends StateNotifier { + final AuthRepository _repository; + + AuthNotifier(this._repository) : super(const AuthState()); + + Future login({ + required String email, + required String password, + }) async { + state = state.copyWith( + status: AuthStatus.loading, + errorMessage: null, + fieldErrors: [], + ); + + try { + final result = await _repository.login( + email: email, + password: password, + ); + + if (result['requiresTwoFactor'] == true) { + state = state.copyWith( + status: AuthStatus.error, + errorMessage: '2FA verification required', + ); + return; + } + + state = state.copyWith( + status: AuthStatus.success, + user: result['user'] as UserModel, + ); + } on ApiException catch (e) { + state = state.copyWith( + status: AuthStatus.error, + errorMessage: e.fieldErrors.isNotEmpty + ? 'Please fix the errors below' + : e.message, + fieldErrors: e.fieldErrors, + ); + } catch (e) { + state = state.copyWith( + status: AuthStatus.error, + errorMessage: 'An unexpected error occurred', + ); + } + } + + Future register({ + required String name, + required String email, + required String password, + required String role, + }) async { + state = state.copyWith( + status: AuthStatus.loading, + errorMessage: null, + fieldErrors: [], + ); + + // Split name into firstName and lastName + final nameParts = name.trim().split(RegExp(r'\s+')); + final firstName = nameParts.first; + final lastName = nameParts.length > 1 ? nameParts.sublist(1).join(' ') : null; + + try { + final user = await _repository.register( + RegisterRequest( + email: email, + password: password, + role: role, + firstName: firstName, + lastName: lastName, + ), + ); + + state = state.copyWith( + status: AuthStatus.success, + user: user, + registeredEmail: email, + ); + } on ApiException catch (e) { + state = state.copyWith( + status: AuthStatus.error, + errorMessage: e.fieldErrors.isNotEmpty + ? 'Please fix the errors below' + : e.message, + fieldErrors: e.fieldErrors, + ); + } catch (e) { + state = state.copyWith( + status: AuthStatus.error, + errorMessage: 'An unexpected error occurred', + ); + } + } + + void reset() { + state = const AuthState(); + } +} + +// Providers +final authRepositoryProvider = Provider((ref) { + return AuthRepository(); +}); + +final authProvider = StateNotifierProvider((ref) { + return AuthNotifier(ref.watch(authRepositoryProvider)); +}); diff --git a/lib/features/auth/presentation/screens/login_screen.dart b/lib/features/auth/presentation/screens/login_screen.dart new file mode 100644 index 0000000..af0c932 --- /dev/null +++ b/lib/features/auth/presentation/screens/login_screen.dart @@ -0,0 +1,252 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:go_router/go_router.dart'; +import 'package:real_estate_mobile/core/constants/app_colors.dart'; +import 'package:real_estate_mobile/features/auth/presentation/providers/auth_provider.dart'; +import 'package:real_estate_mobile/features/auth/presentation/widgets/auth_text_field.dart'; +import 'package:real_estate_mobile/features/auth/presentation/widgets/or_divider.dart'; +import 'package:real_estate_mobile/features/auth/presentation/widgets/role_toggle.dart'; +import 'package:real_estate_mobile/features/auth/presentation/widgets/social_login_buttons.dart'; + +class LoginScreen extends ConsumerStatefulWidget { + const LoginScreen({super.key}); + + @override + ConsumerState createState() => _LoginScreenState(); +} + +class _LoginScreenState extends ConsumerState { + final _nameController = TextEditingController(); + final _emailController = TextEditingController(); + final _passwordController = TextEditingController(); + String _selectedRole = 'USER'; + bool _obscurePassword = true; + + @override + void dispose() { + _nameController.dispose(); + _emailController.dispose(); + _passwordController.dispose(); + super.dispose(); + } + + String? _validateLocally() { + if (_emailController.text.trim().isEmpty) { + return 'Please enter your email'; + } + if (_passwordController.text.isEmpty) { + return 'Please enter a password'; + } + return null; + } + + void _handleLogin() { + final localError = _validateLocally(); + if (localError != null) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text(localError), + behavior: SnackBarBehavior.floating, + ), + ); + return; + } + + ref.read(authProvider.notifier).login( + email: _emailController.text.trim(), + password: _passwordController.text, + ); + } + + @override + Widget build(BuildContext context) { + final authState = ref.watch(authProvider); + final isLoading = authState.status == AuthStatus.loading; + + return Scaffold( + body: Container( + width: double.infinity, + height: double.infinity, + decoration: const BoxDecoration( + gradient: LinearGradient( + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + colors: [AppColors.gradientStart, AppColors.gradientEnd], + ), + ), + child: SafeArea( + child: SingleChildScrollView( + padding: const EdgeInsets.symmetric(horizontal: 24), + child: Column( + children: [ + const SizedBox(height: 40), + + // Logo + Image.asset( + 'assets/icons/logo.png', + width: 143, + height: 41, + ), + const SizedBox(height: 24), + + // Title + const Text( + 'Login', + style: TextStyle( + fontFamily: 'Fractul', + fontSize: 25, + fontWeight: FontWeight.w400, + color: AppColors.primaryDark, + ), + ), + const SizedBox(height: 24), + + // Role toggle (User / Admin) + RoleToggle( + selectedRole: _selectedRole, + onChanged: (role) => setState(() => _selectedRole = role), + ), + const SizedBox(height: 20), + + // Subtitle + const Padding( + padding: EdgeInsets.symmetric(horizontal: 16), + child: Text( + 'Login or create an account to enjoy FREE consultation on all property inquiries.', + textAlign: TextAlign.center, + style: TextStyle( + fontFamily: 'SourceSerif4', + fontSize: 14, + fontWeight: FontWeight.w500, + color: AppColors.primaryDark, + ), + ), + ), + const SizedBox(height: 24), + + // Error banner + if (authState.errorMessage != null && + authState.fieldErrors.isEmpty) ...[ + Container( + width: double.infinity, + padding: const EdgeInsets.symmetric( + horizontal: 16, vertical: 12), + decoration: BoxDecoration( + color: AppColors.errorBg, + border: Border.all(color: AppColors.errorBorder), + borderRadius: BorderRadius.circular(7), + ), + child: Text( + authState.errorMessage!, + style: const TextStyle( + fontFamily: 'Fractul', + fontSize: 14, + fontWeight: FontWeight.w300, + color: AppColors.error, + ), + ), + ), + const SizedBox(height: 16), + ], + + // Name field + AuthTextField( + hintText: 'Name', + controller: _nameController, + keyboardType: TextInputType.name, + ), + const SizedBox(height: 16), + + // Email field + AuthTextField( + hintText: 'Email', + controller: _emailController, + keyboardType: TextInputType.emailAddress, + errorText: authState.getFieldError('email'), + ), + const SizedBox(height: 16), + + // Password field + AuthTextField( + hintText: 'Password', + controller: _passwordController, + obscureText: _obscurePassword, + textInputAction: TextInputAction.done, + errorText: authState.getFieldError('password'), + suffixIcon: IconButton( + icon: Icon( + _obscurePassword + ? Icons.visibility_off + : Icons.visibility, + color: AppColors.hintText, + size: 22, + ), + onPressed: () => + setState(() => _obscurePassword = !_obscurePassword), + ), + ), + const SizedBox(height: 24), + + // Login button + SizedBox( + width: double.infinity, + child: ElevatedButton( + onPressed: isLoading ? null : _handleLogin, + child: isLoading + ? const SizedBox( + height: 20, + width: 20, + child: CircularProgressIndicator( + strokeWidth: 2, + color: AppColors.primaryDark, + ), + ) + : const Text('Login'), + ), + ), + const SizedBox(height: 24), + + // Don't have an account? Sign up + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Text( + "Don't have an account? ", + style: TextStyle( + fontFamily: 'SourceSerif4', + fontSize: 14, + fontWeight: FontWeight.w400, + color: AppColors.primaryDark, + ), + ), + GestureDetector( + onTap: () => context.go('/signup'), + child: const Text( + 'Sign up', + style: TextStyle( + fontFamily: 'Fractul', + fontSize: 14, + fontWeight: FontWeight.w500, + color: AppColors.primaryDark, + ), + ), + ), + ], + ), + const SizedBox(height: 24), + + // Or divider + const OrDivider(), + const SizedBox(height: 24), + + // Social login buttons + const SocialLoginButtons(), + const SizedBox(height: 40), + ], + ), + ), + ), + ), + ); + } +} diff --git a/lib/features/auth/presentation/screens/signup_screen.dart b/lib/features/auth/presentation/screens/signup_screen.dart new file mode 100644 index 0000000..7ef2df6 --- /dev/null +++ b/lib/features/auth/presentation/screens/signup_screen.dart @@ -0,0 +1,359 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:real_estate_mobile/core/constants/app_colors.dart'; +import 'package:real_estate_mobile/features/auth/presentation/providers/auth_provider.dart'; +import 'package:real_estate_mobile/features/auth/presentation/widgets/auth_text_field.dart'; +import 'package:real_estate_mobile/features/auth/presentation/widgets/or_divider.dart'; +import 'package:real_estate_mobile/features/auth/presentation/widgets/role_toggle.dart'; +import 'package:real_estate_mobile/features/auth/presentation/widgets/social_login_buttons.dart'; +import 'package:go_router/go_router.dart'; + +class SignupScreen extends ConsumerStatefulWidget { + const SignupScreen({super.key}); + + @override + ConsumerState createState() => _SignupScreenState(); +} + +class _SignupScreenState extends ConsumerState { + final _nameController = TextEditingController(); + final _emailController = TextEditingController(); + final _passwordController = TextEditingController(); + String _selectedRole = 'USER'; + bool _obscurePassword = true; + + @override + void dispose() { + _nameController.dispose(); + _emailController.dispose(); + _passwordController.dispose(); + super.dispose(); + } + + String? _validateLocally() { + if (_nameController.text.trim().isEmpty) { + return 'Please enter your name'; + } + if (_emailController.text.trim().isEmpty) { + return 'Please enter your email'; + } + if (_passwordController.text.isEmpty) { + return 'Please enter a password'; + } + return null; + } + + void _handleSignup() { + final localError = _validateLocally(); + if (localError != null) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text(localError), + behavior: SnackBarBehavior.floating, + ), + ); + return; + } + + ref.read(authProvider.notifier).register( + name: _nameController.text.trim(), + email: _emailController.text.trim(), + password: _passwordController.text, + role: _selectedRole, + ); + } + + @override + Widget build(BuildContext context) { + final authState = ref.watch(authProvider); + + return Scaffold( + body: Container( + width: double.infinity, + height: double.infinity, + decoration: const BoxDecoration( + gradient: LinearGradient( + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + colors: [AppColors.gradientStart, AppColors.gradientEnd], + ), + ), + child: SafeArea( + child: SingleChildScrollView( + padding: const EdgeInsets.symmetric(horizontal: 24), + child: authState.status == AuthStatus.success + ? _buildSuccessContent(authState) + : _buildFormContent(authState), + ), + ), + ), + ); + } + + Widget _buildSuccessContent(AuthState authState) { + return Padding( + padding: const EdgeInsets.only(top: 80), + child: Container( + padding: const EdgeInsets.all(32), + decoration: BoxDecoration( + color: AppColors.white, + borderRadius: BorderRadius.circular(20), + ), + child: Column( + children: [ + const Icon( + Icons.check_circle_outline, + size: 64, + color: AppColors.success, + ), + const SizedBox(height: 24), + const Text( + 'Check Your Email', + style: TextStyle( + fontFamily: 'Fractul', + fontSize: 24, + fontWeight: FontWeight.w400, + color: AppColors.primaryDark, + ), + ), + const SizedBox(height: 12), + Text( + 'We\'ve sent a verification link to ', + textAlign: TextAlign.center, + style: TextStyle( + fontFamily: 'Fractul', + fontSize: 14, + fontWeight: FontWeight.w300, + color: AppColors.primaryDark.withValues(alpha: 0.7), + ), + ), + Text( + authState.registeredEmail, + textAlign: TextAlign.center, + style: const TextStyle( + fontFamily: 'Fractul', + fontSize: 14, + fontWeight: FontWeight.w600, + color: AppColors.primaryDark, + ), + ), + const SizedBox(height: 4), + Text( + 'Please check your inbox and click the link to verify your account before logging in.', + textAlign: TextAlign.center, + style: TextStyle( + fontFamily: 'Fractul', + fontSize: 14, + fontWeight: FontWeight.w300, + color: AppColors.primaryDark.withValues(alpha: 0.7), + ), + ), + const SizedBox(height: 24), + SizedBox( + width: double.infinity, + child: ElevatedButton( + onPressed: () { + ref.read(authProvider.notifier).reset(); + context.go('/login'); + }, + child: const Text('Go to Login'), + ), + ), + const SizedBox(height: 16), + Text( + 'Didn\'t receive the email? Check your spam folder or contact support.', + textAlign: TextAlign.center, + style: TextStyle( + fontFamily: 'Fractul', + fontSize: 12, + fontWeight: FontWeight.w300, + color: AppColors.primaryDark.withValues(alpha: 0.5), + ), + ), + ], + ), + ), + ); + } + + Widget _buildFormContent(AuthState authState) { + final isLoading = authState.status == AuthStatus.loading; + + return Column( + children: [ + const SizedBox(height: 40), + + // Logo + Image.asset( + 'assets/icons/logo.png', + width: 143, + height: 41, + ), + const SizedBox(height: 24), + + // Title + const Text( + 'Sign Up', + style: TextStyle( + fontFamily: 'Fractul', + fontSize: 30, + fontWeight: FontWeight.w400, + color: AppColors.primaryDark, + ), + ), + const SizedBox(height: 24), + + // Role toggle + RoleToggle( + selectedRole: _selectedRole, + onChanged: (role) => setState(() => _selectedRole = role), + ), + const SizedBox(height: 20), + + // Subtitle + Text( + 'Enter Your Name to Continue', + style: TextStyle( + fontFamily: 'Fractul', + fontSize: 14, + fontWeight: FontWeight.w300, + color: AppColors.primaryDark.withValues(alpha: 0.7), + ), + ), + const SizedBox(height: 20), + + // Error banner + if (authState.errorMessage != null && + authState.fieldErrors.isEmpty) ...[ + Container( + width: double.infinity, + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), + decoration: BoxDecoration( + color: AppColors.errorBg, + border: Border.all(color: AppColors.errorBorder), + borderRadius: BorderRadius.circular(20), + ), + child: Text( + authState.errorMessage!, + style: const TextStyle( + fontFamily: 'Fractul', + fontSize: 14, + fontWeight: FontWeight.w300, + color: AppColors.error, + ), + ), + ), + const SizedBox(height: 16), + ], + + // Name field + AuthTextField( + hintText: 'Name', + controller: _nameController, + keyboardType: TextInputType.name, + errorText: authState.getFieldError('firstName'), + ), + const SizedBox(height: 16), + + // Email field + AuthTextField( + hintText: 'Email', + controller: _emailController, + keyboardType: TextInputType.emailAddress, + errorText: authState.getFieldError('email'), + ), + const SizedBox(height: 16), + + // Password field + AuthTextField( + hintText: 'Password', + controller: _passwordController, + obscureText: _obscurePassword, + textInputAction: TextInputAction.done, + errorText: authState.getFieldError('password'), + suffixIcon: IconButton( + icon: Icon( + _obscurePassword ? Icons.visibility_off : Icons.visibility, + color: AppColors.hintText, + size: 22, + ), + onPressed: () => + setState(() => _obscurePassword = !_obscurePassword), + ), + ), + Padding( + padding: const EdgeInsets.only(top: 8, left: 8), + child: Align( + alignment: Alignment.centerLeft, + child: Text( + 'Min 8 chars, 1 uppercase, 1 lowercase, 1 number, 1 special character', + style: TextStyle( + fontFamily: 'Fractul', + fontSize: 12, + fontWeight: FontWeight.w300, + color: AppColors.primaryDark.withValues(alpha: 0.6), + ), + ), + ), + ), + const SizedBox(height: 24), + + // Sign Up button + SizedBox( + width: double.infinity, + child: ElevatedButton( + onPressed: isLoading ? null : _handleSignup, + child: isLoading + ? const SizedBox( + height: 20, + width: 20, + child: CircularProgressIndicator( + strokeWidth: 2, + color: AppColors.primaryDark, + ), + ) + : const Text('Sign Up'), + ), + ), + const SizedBox(height: 24), + + // Already have account + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Text( + 'Already Have An Account ? ', + style: TextStyle( + fontFamily: 'Fractul', + fontSize: 14, + fontWeight: FontWeight.w300, + color: AppColors.primaryDark, + ), + ), + GestureDetector( + onTap: () => context.go('/login'), + child: const Text( + 'Login', + style: TextStyle( + fontFamily: 'Fractul', + fontSize: 14, + fontWeight: FontWeight.w400, + color: AppColors.primaryDark, + ), + ), + ), + ], + ), + const SizedBox(height: 24), + + // Or divider + const OrDivider(), + const SizedBox(height: 24), + + // Social login buttons + const SocialLoginButtons(), + const SizedBox(height: 40), + ], + ); + } +} diff --git a/lib/features/auth/presentation/widgets/auth_text_field.dart b/lib/features/auth/presentation/widgets/auth_text_field.dart new file mode 100644 index 0000000..f5a6d1e --- /dev/null +++ b/lib/features/auth/presentation/widgets/auth_text_field.dart @@ -0,0 +1,67 @@ +import 'package:flutter/material.dart'; +import 'package:real_estate_mobile/core/constants/app_colors.dart'; + +class AuthTextField extends StatelessWidget { + final String hintText; + final TextEditingController controller; + final bool obscureText; + final TextInputType keyboardType; + final Widget? suffixIcon; + final String? errorText; + final TextInputAction textInputAction; + + const AuthTextField({ + super.key, + required this.hintText, + required this.controller, + this.obscureText = false, + this.keyboardType = TextInputType.text, + this.suffixIcon, + this.errorText, + this.textInputAction = TextInputAction.next, + }); + + @override + Widget build(BuildContext context) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + TextField( + controller: controller, + obscureText: obscureText, + keyboardType: keyboardType, + textInputAction: textInputAction, + style: const TextStyle( + fontFamily: 'Fractul', + fontSize: 14, + fontWeight: FontWeight.w300, + color: AppColors.primaryDark, + ), + decoration: InputDecoration( + hintText: hintText, + suffixIcon: suffixIcon, + enabledBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(20), + borderSide: errorText != null + ? const BorderSide(color: AppColors.error, width: 1.5) + : BorderSide.none, + ), + ), + ), + if (errorText != null) + Padding( + padding: const EdgeInsets.only(top: 4, left: 8), + child: Text( + errorText!, + style: const TextStyle( + fontFamily: 'Fractul', + fontSize: 12, + fontWeight: FontWeight.w300, + color: AppColors.error, + ), + ), + ), + ], + ); + } +} diff --git a/lib/features/auth/presentation/widgets/or_divider.dart b/lib/features/auth/presentation/widgets/or_divider.dart new file mode 100644 index 0000000..2f58bcc --- /dev/null +++ b/lib/features/auth/presentation/widgets/or_divider.dart @@ -0,0 +1,32 @@ +import 'package:flutter/material.dart'; +import 'package:real_estate_mobile/core/constants/app_colors.dart'; + +class OrDivider extends StatelessWidget { + const OrDivider({super.key}); + + @override + Widget build(BuildContext context) { + return Row( + children: [ + const Expanded( + child: Divider(color: AppColors.divider, thickness: 1), + ), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 16), + child: Text( + 'Or', + style: TextStyle( + fontFamily: 'Fractul', + fontSize: 14, + fontWeight: FontWeight.w300, + color: AppColors.primaryDark.withValues(alpha: 0.6), + ), + ), + ), + const Expanded( + child: Divider(color: AppColors.divider, thickness: 1), + ), + ], + ); + } +} diff --git a/lib/features/auth/presentation/widgets/role_toggle.dart b/lib/features/auth/presentation/widgets/role_toggle.dart new file mode 100644 index 0000000..d880057 --- /dev/null +++ b/lib/features/auth/presentation/widgets/role_toggle.dart @@ -0,0 +1,55 @@ +import 'package:flutter/material.dart'; +import 'package:real_estate_mobile/core/constants/app_colors.dart'; + +class RoleToggle extends StatelessWidget { + final String selectedRole; + final ValueChanged onChanged; + + const RoleToggle({ + super.key, + required this.selectedRole, + required this.onChanged, + }); + + @override + Widget build(BuildContext context) { + return Container( + decoration: BoxDecoration( + color: AppColors.inputFill, + borderRadius: BorderRadius.circular(20), + ), + padding: const EdgeInsets.all(2), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + _buildToggleButton('User', 'USER'), + _buildToggleButton('Admin', 'AGENT'), + ], + ), + ); + } + + Widget _buildToggleButton(String label, String role) { + final isSelected = selectedRole == role; + return GestureDetector( + onTap: () => onChanged(role), + child: AnimatedContainer( + duration: const Duration(milliseconds: 200), + padding: const EdgeInsets.symmetric(horizontal: 28, vertical: 10), + decoration: BoxDecoration( + color: isSelected ? AppColors.primaryDark : Colors.transparent, + borderRadius: BorderRadius.circular(20), + ), + child: Text( + label, + style: TextStyle( + fontFamily: 'Fractul', + fontSize: 14, + fontWeight: FontWeight.w300, + color: isSelected ? AppColors.inputFill : AppColors.primaryDark, + ), + ), + ), + ); + } +} diff --git a/lib/features/auth/presentation/widgets/social_login_buttons.dart b/lib/features/auth/presentation/widgets/social_login_buttons.dart new file mode 100644 index 0000000..e1f14e0 --- /dev/null +++ b/lib/features/auth/presentation/widgets/social_login_buttons.dart @@ -0,0 +1,87 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:real_estate_mobile/core/constants/app_colors.dart'; + +class SocialLoginButtons extends StatelessWidget { + const SocialLoginButtons({super.key}); + + void _showComingSoon(BuildContext context) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('Coming soon'), + duration: Duration(seconds: 1), + behavior: SnackBarBehavior.floating, + ), + ); + } + + @override + Widget build(BuildContext context) { + return Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + // Apple + _SocialButton( + onTap: () => _showComingSoon(context), + child: const Icon( + Icons.apple, + size: 28, + color: AppColors.primaryDark, + ), + ), + const SizedBox(width: 32), + // Google + _SocialButton( + onTap: () => _showComingSoon(context), + child: SvgPicture.asset( + 'assets/icons/google.svg', + width: 24, + height: 24, + ), + ), + const SizedBox(width: 32), + // X (Twitter) + _SocialButton( + onTap: () => _showComingSoon(context), + child: SvgPicture.asset( + 'assets/icons/x.svg', + width: 24, + height: 24, + ), + ), + const SizedBox(width: 32), + // Facebook + _SocialButton( + onTap: () => _showComingSoon(context), + child: SvgPicture.asset( + 'assets/icons/facebook.svg', + width: 24, + height: 24, + ), + ), + ], + ); + } +} + +class _SocialButton extends StatelessWidget { + final VoidCallback onTap; + final Widget child; + + const _SocialButton({ + required this.onTap, + required this.child, + }); + + @override + Widget build(BuildContext context) { + return GestureDetector( + onTap: onTap, + child: SizedBox( + width: 40, + height: 40, + child: Center(child: child), + ), + ); + } +} diff --git a/lib/main.dart b/lib/main.dart index 244a702..0e94a7b 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,122 +1,25 @@ import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:real_estate_mobile/config/app_config.dart'; +import 'package:real_estate_mobile/core/theme/app_theme.dart'; +import 'package:real_estate_mobile/routing/app_router.dart'; -void main() { - runApp(const MyApp()); +Future mainCommon(Flavor flavor) async { + WidgetsFlutterBinding.ensureInitialized(); + await AppConfig.init(flavor); + runApp(const ProviderScope(child: MyApp())); } class MyApp extends StatelessWidget { const MyApp({super.key}); - // This widget is the root of your application. @override Widget build(BuildContext context) { - return MaterialApp( - title: 'Flutter Demo', - theme: ThemeData( - // This is the theme of your application. - // - // TRY THIS: Try running your application with "flutter run". You'll see - // the application has a purple toolbar. Then, without quitting the app, - // try changing the seedColor in the colorScheme below to Colors.green - // and then invoke "hot reload" (save your changes or press the "hot - // reload" button in a Flutter-supported IDE, or press "r" if you used - // the command line to start the app). - // - // Notice that the counter didn't reset back to zero; the application - // state is not lost during the reload. To reset the state, use hot - // restart instead. - // - // This works for code too, not just values: Most code changes can be - // tested with just a hot reload. - colorScheme: .fromSeed(seedColor: Colors.deepPurple), - ), - home: const MyHomePage(title: 'Flutter Demo Home Page'), - ); - } -} - -class MyHomePage extends StatefulWidget { - const MyHomePage({super.key, required this.title}); - - // This widget is the home page of your application. It is stateful, meaning - // that it has a State object (defined below) that contains fields that affect - // how it looks. - - // This class is the configuration for the state. It holds the values (in this - // case the title) provided by the parent (in this case the App widget) and - // used by the build method of the State. Fields in a Widget subclass are - // always marked "final". - - final String title; - - @override - State createState() => _MyHomePageState(); -} - -class _MyHomePageState extends State { - int _counter = 0; - - void _incrementCounter() { - setState(() { - // This call to setState tells the Flutter framework that something has - // changed in this State, which causes it to rerun the build method below - // so that the display can reflect the updated values. If we changed - // _counter without calling setState(), then the build method would not be - // called again, and so nothing would appear to happen. - _counter++; - }); - } - - @override - Widget build(BuildContext context) { - // This method is rerun every time setState is called, for instance as done - // by the _incrementCounter method above. - // - // The Flutter framework has been optimized to make rerunning build methods - // fast, so that you can just rebuild anything that needs updating rather - // than having to individually change instances of widgets. - return Scaffold( - appBar: AppBar( - // TRY THIS: Try changing the color here to a specific color (to - // Colors.amber, perhaps?) and trigger a hot reload to see the AppBar - // change color while the other colors stay the same. - backgroundColor: Theme.of(context).colorScheme.inversePrimary, - // Here we take the value from the MyHomePage object that was created by - // the App.build method, and use it to set our appbar title. - title: Text(widget.title), - ), - body: Center( - // Center is a layout widget. It takes a single child and positions it - // in the middle of the parent. - child: Column( - // Column is also a layout widget. It takes a list of children and - // arranges them vertically. By default, it sizes itself to fit its - // children horizontally, and tries to be as tall as its parent. - // - // Column has various properties to control how it sizes itself and - // how it positions its children. Here we use mainAxisAlignment to - // center the children vertically; the main axis here is the vertical - // axis because Columns are vertical (the cross axis would be - // horizontal). - // - // TRY THIS: Invoke "debug painting" (choose the "Toggle Debug Paint" - // action in the IDE, or press "p" in the console), to see the - // wireframe for each widget. - mainAxisAlignment: .center, - children: [ - const Text('You have pushed the button this many times:'), - Text( - '$_counter', - style: Theme.of(context).textTheme.headlineMedium, - ), - ], - ), - ), - floatingActionButton: FloatingActionButton( - onPressed: _incrementCounter, - tooltip: 'Increment', - child: const Icon(Icons.add), - ), + return MaterialApp.router( + title: 'RE-QuestN', + theme: AppTheme.light, + routerConfig: AppRouter.router, + debugShowCheckedModeBanner: false, ); } } diff --git a/lib/main_demo.dart b/lib/main_demo.dart new file mode 100644 index 0000000..3c6d2b2 --- /dev/null +++ b/lib/main_demo.dart @@ -0,0 +1,4 @@ +import 'package:real_estate_mobile/config/app_config.dart'; +import 'package:real_estate_mobile/main.dart'; + +void main() => mainCommon(Flavor.demo); diff --git a/lib/main_dev.dart b/lib/main_dev.dart new file mode 100644 index 0000000..57f76f8 --- /dev/null +++ b/lib/main_dev.dart @@ -0,0 +1,4 @@ +import 'package:real_estate_mobile/config/app_config.dart'; +import 'package:real_estate_mobile/main.dart'; + +void main() => mainCommon(Flavor.dev); diff --git a/lib/main_local.dart b/lib/main_local.dart new file mode 100644 index 0000000..bd3fab0 --- /dev/null +++ b/lib/main_local.dart @@ -0,0 +1,4 @@ +import 'package:real_estate_mobile/config/app_config.dart'; +import 'package:real_estate_mobile/main.dart'; + +void main() => mainCommon(Flavor.local); diff --git a/lib/routing/app_router.dart b/lib/routing/app_router.dart new file mode 100644 index 0000000..18e092b --- /dev/null +++ b/lib/routing/app_router.dart @@ -0,0 +1,22 @@ +import 'package:flutter/material.dart'; +import 'package:go_router/go_router.dart'; +import 'package:real_estate_mobile/features/auth/presentation/screens/login_screen.dart'; +import 'package:real_estate_mobile/features/auth/presentation/screens/signup_screen.dart'; + +class AppRouter { + AppRouter._(); + + static final GoRouter router = GoRouter( + initialLocation: '/signup', + routes: [ + GoRoute( + path: '/signup', + builder: (context, state) => const SignupScreen(), + ), + GoRoute( + path: '/login', + builder: (context, state) => const LoginScreen(), + ), + ], + ); +} diff --git a/pubspec.lock b/pubspec.lock index 6f14ac1..3d5ff37 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -25,6 +25,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.13.4" + archive: + dependency: transitive + description: + name: archive + sha256: a96e8b390886ee8abb49b7bd3ac8df6f451c621619f52a26e815fdcf568959ff + url: "https://pub.dev" + source: hosted + version: "4.0.9" args: dependency: transitive description: @@ -302,6 +310,14 @@ packages: url: "https://pub.dev" source: hosted version: "5.2.1" + flutter_flavorizr: + dependency: "direct dev" + description: + name: flutter_flavorizr + sha256: "7ac1a53e95d36e96017bbf4aa94023dc652e455de3f0ce9c80e9eaa274c73e81" + url: "https://pub.dev" + source: hosted + version: "2.4.1" flutter_lints: dependency: "direct dev" description: @@ -464,6 +480,14 @@ packages: url: "https://pub.dev" source: hosted version: "4.1.2" + image: + dependency: transitive + description: + name: image + sha256: f9881ff4998044947ec38d098bc7c8316ae1186fa786eddffdb867b9bc94dfce + url: "https://pub.dev" + source: hosted + version: "4.8.0" intl: dependency: "direct main" description: @@ -552,6 +576,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.3.0" + mason_logger: + dependency: transitive + description: + name: mason_logger + sha256: "6d5a989ff41157915cb5162ed6e41196d5e31b070d2f86e1c2edf216996a158c" + url: "https://pub.dev" + source: hosted + version: "0.3.3" matcher: dependency: transitive description: @@ -712,6 +744,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.5.2" + posix: + dependency: transitive + description: + name: posix + sha256: "185ef7606574f789b40f289c233efa52e96dead518aed988e040a10737febb07" + url: "https://pub.dev" + source: hosted + version: "6.5.0" pub_semver: dependency: transitive description: @@ -877,6 +917,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.10.1" + sprintf: + dependency: transitive + description: + name: sprintf + sha256: "1fc9ffe69d4df602376b52949af107d8f5703b77cda567c4d7d86a0693120f23" + url: "https://pub.dev" + source: hosted + version: "7.0.0" sqflite: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 8473bd9..7df3010 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -53,6 +53,9 @@ dev_dependencies: json_serializable: ^6.9.5 riverpod_generator: ^2.6.5 + # Flavors + flutter_flavorizr: ^2.2.3 + flutter: uses-material-design: true @@ -60,4 +63,54 @@ flutter: - assets/ - assets/images/ - assets/icons/ - - .env + - assets/fonts/ + - .env.local + - .env.demo + - .env.dev + + fonts: + - family: Fractul + fonts: + - asset: assets/fonts/Fractul-Light.ttf + weight: 300 + - asset: assets/fonts/Fractul-Regular.ttf + weight: 400 + - asset: assets/fonts/Fractul-Medium.ttf + weight: 500 + - asset: assets/fonts/Fractul-SemiBold.ttf + weight: 600 + - asset: assets/fonts/Fractul-Bold.ttf + weight: 700 + - family: SourceSerif4 + fonts: + - asset: assets/fonts/SourceSerif4-VariableFont_opsz,wght.ttf + +flavorizr: + flavors: + local: + app: + name: "RE-QuestN Local" + android: + applicationId: "com.requestn.real_estate_mobile.local" + ios: + bundleId: "com.requestn.realEstateMobile.local" + buildSettings: + DEVELOPMENT_TEAM: JKMS87XDA9 + demo: + app: + name: "RE-QuestN Demo" + android: + applicationId: "com.requestn.real_estate_mobile.demo" + ios: + bundleId: "com.requestn.realEstateMobile.demo" + buildSettings: + DEVELOPMENT_TEAM: JKMS87XDA9 + dev: + app: + name: "RE-QuestN Dev" + android: + applicationId: "com.requestn.real_estate_mobile.dev" + ios: + bundleId: "com.requestn.realEstateMobile.dev" + buildSettings: + DEVELOPMENT_TEAM: JKMS87XDA9