diff --git a/.env b/.env new file mode 100644 index 0000000..e69de29 diff --git a/android/app/build.gradle b/android/app/build.gradle index f73b2e7..f5af78e 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -22,6 +22,12 @@ if (flutterVersionName == null) { flutterVersionName = '1.0' } +def keystoreProperties = new Properties() +def keystorePropertiesFile = rootProject.file('key.properties') +if (keystorePropertiesFile.exists()) { + keystoreProperties.load(new FileInputStream(keystorePropertiesFile)) +} + android { namespace "com.golub.golub" compileSdkVersion flutter.compileSdkVersion @@ -51,11 +57,25 @@ android { versionName flutterVersionName } + signingConfigs { + release { + if (System.getenv()["CI"]) { // CI=true is exported by Codemagic + storeFile file(System.getenv()["CM_KEYSTORE_PATH"]) + storePassword System.getenv()["CM_KEYSTORE_PASSWORD"] + keyAlias System.getenv()["CM_KEY_ALIAS"] + keyPassword System.getenv()["CM_KEY_PASSWORD"] + } else { + keyAlias keystoreProperties['keyAlias'] + keyPassword keystoreProperties['keyPassword'] + storeFile keystoreProperties['storeFile'] ? file(keystoreProperties['storeFile']) : null + storePassword keystoreProperties['storePassword'] + } + } + } + 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.debug + signingConfig signingConfigs.release } } } diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 778788a..12532cf 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -2,7 +2,7 @@ + android:icon="@mipmap/launcher_icon"> + + diff --git a/assets/icons/camera.svg b/assets/icons/camera.svg new file mode 100644 index 0000000..2267f52 --- /dev/null +++ b/assets/icons/camera.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/icons/user_profile.svg b/assets/icons/user_profile.svg new file mode 100644 index 0000000..a8eba92 --- /dev/null +++ b/assets/icons/user_profile.svg @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/images/chats.svg b/assets/images/chats.svg new file mode 100644 index 0000000..e72f660 --- /dev/null +++ b/assets/images/chats.svg @@ -0,0 +1,4 @@ + + + + diff --git a/assets/logo/logo.png b/assets/logo/logo.png new file mode 100644 index 0000000..a88e662 Binary files /dev/null and b/assets/logo/logo.png differ diff --git a/codemagic.yaml b/codemagic.yaml new file mode 100644 index 0000000..c54e85b --- /dev/null +++ b/codemagic.yaml @@ -0,0 +1,41 @@ +workflows: + android-dev: + name: Android Development + instance_type: mac_mini_m1 + max_build_duration: 90 + environment: + groups: + - google_credentials + flutter: stable + android_signing: + - golub_keystore + triggering: + events: + - push + branch_patterns: + - pattern: '{feature}/*' + include: true + source: false + scripts: + - name: Get Flutter dependencies + script: flutter packages pub get + # - name: Build APK + # script: flutter build apk --release --build-name=1.0.0 --build-number=2 + - name: Build App Bundle + script: flutter build appbundle --release --build-name=1.0.0 --build-number=$(($(google-play get-latest-build-number --package-name 'com.golub.golub') + 1)) + artifacts: + #- build/**/outputs/apk/**/*.apk + - build/**/outputs/**/*.aab + - build/**/outputs/**/mapping.txt + - flutter_drive.log + publishing: + google_play: + credentials: $GCLOUD_SERVICE_ACCOUNT_CREDENTIALS + track: internal + submit_as_draft: true + email: + recipients: + - "denisdubov88@gmail.com" + notify: + success: true + diff --git a/ios/Flutter/AppFrameworkInfo.plist b/ios/Flutter/AppFrameworkInfo.plist index 9625e10..7c56964 100644 --- a/ios/Flutter/AppFrameworkInfo.plist +++ b/ios/Flutter/AppFrameworkInfo.plist @@ -21,6 +21,6 @@ CFBundleVersion 1.0 MinimumOSVersion - 11.0 + 12.0 diff --git a/ios/Podfile b/ios/Podfile index fdcc671..d97f17e 100644 --- a/ios/Podfile +++ b/ios/Podfile @@ -1,5 +1,5 @@ # Uncomment this line to define a global platform for your project -# platform :ios, '11.0' +# platform :ios, '12.0' # CocoaPods analytics sends network stats synchronously affecting flutter build latency. ENV['COCOAPODS_DISABLE_STATS'] = 'true' diff --git a/ios/Podfile.lock b/ios/Podfile.lock new file mode 100644 index 0000000..426afef --- /dev/null +++ b/ios/Podfile.lock @@ -0,0 +1,22 @@ +PODS: + - Flutter (1.0.0) + - url_launcher_ios (0.0.1): + - Flutter + +DEPENDENCIES: + - Flutter (from `Flutter`) + - url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`) + +EXTERNAL SOURCES: + Flutter: + :path: Flutter + url_launcher_ios: + :path: ".symlinks/plugins/url_launcher_ios/ios" + +SPEC CHECKSUMS: + Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7 + url_launcher_ios: bbd758c6e7f9fd7b5b1d4cde34d2b95fcce5e812 + +PODFILE CHECKSUM: 819463e6a0290f5a72f145ba7cde16e8b6ef0796 + +COCOAPODS: 1.14.3 diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj index 1ac8d01..08977f1 100644 --- a/ios/Runner.xcodeproj/project.pbxproj +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -7,13 +7,15 @@ objects = { /* Begin PBXBuildFile section */ + 057D6EE5BB80267DE9A309AA /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 03A891C613C2EFEAD176D53B /* Pods_Runner.framework */; }; 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; + 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; }; 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; + 68F71A49BBCF9E4F15814E75 /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6EBB174393ECDA22EA32D646 /* Pods_RunnerTests.framework */; }; 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; - 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -40,9 +42,16 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ + 03A891C613C2EFEAD176D53B /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 0838C6D9BB79BBF30B30F3C9 /* 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 = ""; }; 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 = ""; }; + 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; }; + 36EE64B9A5FD16B7801F8CFE /* 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 = ""; }; + 3AD801084A3A630BCCB5FCBE /* 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 = ""; }; 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; + 6EBB174393ECDA22EA32D646 /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 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 = ""; }; @@ -53,30 +62,52 @@ 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 = ""; }; - 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; }; + CC231CDD254204C24741D157 /* 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 = ""; }; + E43A4490609820A20ABBB892 /* 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 = ""; }; + EC4F3BF1DF395A7A97C42544 /* 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 = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ + 08E30F5925EDFADEE4A2ED9E /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 68F71A49BBCF9E4F15814E75 /* Pods_RunnerTests.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; 97C146EB1CF9000F007C117D /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 057D6EE5BB80267DE9A309AA /* Pods_Runner.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ - 9740EEB11CF90186004384FC /* Flutter */ = { + 13F8F8B5EE5AE5965780B8D9 /* Frameworks */ = { isa = PBXGroup; children = ( - 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, - 9740EEB21CF90195004384FC /* Debug.xcconfig */, - 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, - 9740EEB31CF90195004384FC /* Generated.xcconfig */, + 03A891C613C2EFEAD176D53B /* Pods_Runner.framework */, + 6EBB174393ECDA22EA32D646 /* Pods_RunnerTests.framework */, ); - name = Flutter; + name = Frameworks; + sourceTree = ""; + }; + 26B95A65C0ED1248809BBA5D /* Pods */ = { + isa = PBXGroup; + children = ( + 0838C6D9BB79BBF30B30F3C9 /* Pods-Runner.debug.xcconfig */, + EC4F3BF1DF395A7A97C42544 /* Pods-Runner.release.xcconfig */, + CC231CDD254204C24741D157 /* Pods-Runner.profile.xcconfig */, + 36EE64B9A5FD16B7801F8CFE /* Pods-RunnerTests.debug.xcconfig */, + E43A4490609820A20ABBB892 /* Pods-RunnerTests.release.xcconfig */, + 3AD801084A3A630BCCB5FCBE /* Pods-RunnerTests.profile.xcconfig */, + ); + name = Pods; + path = Pods; sourceTree = ""; }; 331C8082294A63A400263BE5 /* RunnerTests */ = { @@ -87,6 +118,17 @@ path = RunnerTests; sourceTree = ""; }; + 9740EEB11CF90186004384FC /* Flutter */ = { + isa = PBXGroup; + children = ( + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, + 9740EEB21CF90195004384FC /* Debug.xcconfig */, + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, + 9740EEB31CF90195004384FC /* Generated.xcconfig */, + ); + name = Flutter; + sourceTree = ""; + }; 97C146E51CF9000F007C117D = { isa = PBXGroup; children = ( @@ -94,6 +136,8 @@ 97C146F01CF9000F007C117D /* Runner */, 97C146EF1CF9000F007C117D /* Products */, 331C8082294A63A400263BE5 /* RunnerTests */, + 26B95A65C0ED1248809BBA5D /* Pods */, + 13F8F8B5EE5AE5965780B8D9 /* Frameworks */, ); sourceTree = ""; }; @@ -128,9 +172,10 @@ isa = PBXNativeTarget; buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; buildPhases = ( + 15069A8F7A1A01DD662AA45E /* [CP] Check Pods Manifest.lock */, 331C807D294A63A400263BE5 /* Sources */, - 331C807E294A63A400263BE5 /* Frameworks */, 331C807F294A63A400263BE5 /* Resources */, + 08E30F5925EDFADEE4A2ED9E /* Frameworks */, ); buildRules = ( ); @@ -146,12 +191,14 @@ isa = PBXNativeTarget; buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; buildPhases = ( + CDFE34D4CA13DB196E682F5E /* [CP] Check Pods Manifest.lock */, 9740EEB61CF901F6004384FC /* Run Script */, 97C146EA1CF9000F007C117D /* Sources */, 97C146EB1CF9000F007C117D /* Frameworks */, 97C146EC1CF9000F007C117D /* Resources */, 9705A1C41CF9048500538489 /* Embed Frameworks */, 3B06AD1E1E4923F5004D2608 /* Thin Binary */, + 83A7B1F3EC4453E10234DFDB /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); @@ -223,6 +270,28 @@ /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ + 15069A8F7A1A01DD662AA45E /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-RunnerTests-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { isa = PBXShellScriptBuildPhase; alwaysOutOfDate = 1; @@ -239,6 +308,23 @@ shellPath = /bin/sh; shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; }; + 83A7B1F3EC4453E10234DFDB /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; 9740EEB61CF901F6004384FC /* Run Script */ = { isa = PBXShellScriptBuildPhase; alwaysOutOfDate = 1; @@ -254,6 +340,28 @@ shellPath = /bin/sh; shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; }; + CDFE34D4CA13DB196E682F5E /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ @@ -345,7 +453,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 11.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = iphoneos; @@ -378,7 +486,7 @@ }; 331C8088294A63A400263BE5 /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = AE0B7B92F70575B8D7E0D07E /* Pods-RunnerTests.debug.xcconfig */; + baseConfigurationReference = 36EE64B9A5FD16B7801F8CFE /* Pods-RunnerTests.debug.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CODE_SIGN_STYLE = Automatic; @@ -396,7 +504,7 @@ }; 331C8089294A63A400263BE5 /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 89B67EB44CE7B6631473024E /* Pods-RunnerTests.release.xcconfig */; + baseConfigurationReference = E43A4490609820A20ABBB892 /* Pods-RunnerTests.release.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CODE_SIGN_STYLE = Automatic; @@ -412,7 +520,7 @@ }; 331C808A294A63A400263BE5 /* Profile */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 640959BDD8F10B91D80A66BE /* Pods-RunnerTests.profile.xcconfig */; + baseConfigurationReference = 3AD801084A3A630BCCB5FCBE /* Pods-RunnerTests.profile.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CODE_SIGN_STYLE = Automatic; @@ -473,7 +581,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 11.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -522,7 +630,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 11.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = iphoneos; diff --git a/ios/Runner.xcworkspace/contents.xcworkspacedata b/ios/Runner.xcworkspace/contents.xcworkspacedata index 1d526a1..21a3cc1 100644 --- a/ios/Runner.xcworkspace/contents.xcworkspacedata +++ b/ios/Runner.xcworkspace/contents.xcworkspacedata @@ -4,4 +4,7 @@ + + diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png index dc9ada4..9f47d97 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png index 7353c41..b1109a4 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png index 797d452..54dc729 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png index 6ed2d93..d8b8734 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png index 4cd7b00..8c4c51b 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png index fe73094..297be3a 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png index 321773c..dcf1eb5 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png index 797d452..54dc729 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png index 502f463..59d085c 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png index 0ec3034..fe1e286 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@1x.png new file mode 100644 index 0000000..a8873eb Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@1x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@2x.png new file mode 100644 index 0000000..3f6ddda Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@1x.png new file mode 100644 index 0000000..bc562da Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@1x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@2x.png new file mode 100644 index 0000000..2e74f7a Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png index 0ec3034..fe1e286 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png index e9f5fea..d4f470f 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@1x.png new file mode 100644 index 0000000..d79a1d3 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@1x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@2x.png new file mode 100644 index 0000000..2dfd997 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png index 84ac32a..fcd69f0 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png index 8953cba..bb6e108 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png index 0467bf1..2f7eba7 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png differ diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist index 6de8b10..f2a3a6f 100644 --- a/ios/Runner/Info.plist +++ b/ios/Runner/Info.plist @@ -45,5 +45,10 @@ UIApplicationSupportsIndirectInputEvents + CFBundleLocalizations + + en + uk + diff --git a/lib/generated/intl/messages_all.dart b/lib/generated/intl/messages_all.dart deleted file mode 100644 index 9dc98bc..0000000 --- a/lib/generated/intl/messages_all.dart +++ /dev/null @@ -1,67 +0,0 @@ -// DO NOT EDIT. This is code generated via package:intl/generate_localized.dart -// This is a library that looks up messages for specific locales by -// delegating to the appropriate library. - -// Ignore issues from commonly used lints in this file. -// ignore_for_file:implementation_imports, file_names, unnecessary_new -// ignore_for_file:unnecessary_brace_in_string_interps, directives_ordering -// ignore_for_file:argument_type_not_assignable, invalid_assignment -// ignore_for_file:prefer_single_quotes, prefer_generic_function_type_aliases -// ignore_for_file:comment_references - -import 'dart:async'; - -import 'package:flutter/foundation.dart'; -import 'package:intl/intl.dart'; -import 'package:intl/message_lookup_by_library.dart'; -import 'package:intl/src/intl_helpers.dart'; - -import 'messages_en.dart' as messages_en; -import 'messages_uk.dart' as messages_uk; - -typedef Future LibraryLoader(); -Map _deferredLibraries = { - 'en': () => new SynchronousFuture(null), - 'uk': () => new SynchronousFuture(null), -}; - -MessageLookupByLibrary? _findExact(String localeName) { - switch (localeName) { - case 'en': - return messages_en.messages; - case 'uk': - return messages_uk.messages; - default: - return null; - } -} - -/// User programs should call this before using [localeName] for messages. -Future initializeMessages(String localeName) { - var availableLocale = Intl.verifiedLocale( - localeName, (locale) => _deferredLibraries[locale] != null, - onFailure: (_) => null); - if (availableLocale == null) { - return new SynchronousFuture(false); - } - var lib = _deferredLibraries[availableLocale]; - lib == null ? new SynchronousFuture(false) : lib(); - initializeInternalMessageLookup(() => new CompositeMessageLookup()); - messageLookup.addLocale(availableLocale, _findGeneratedMessagesFor); - return new SynchronousFuture(true); -} - -bool _messagesExistFor(String locale) { - try { - return _findExact(locale) != null; - } catch (e) { - return false; - } -} - -MessageLookupByLibrary? _findGeneratedMessagesFor(String locale) { - var actualLocale = - Intl.verifiedLocale(locale, _messagesExistFor, onFailure: (_) => null); - if (actualLocale == null) return null; - return _findExact(actualLocale); -} diff --git a/lib/generated/intl/messages_en.dart b/lib/generated/intl/messages_en.dart deleted file mode 100644 index 6931fd2..0000000 --- a/lib/generated/intl/messages_en.dart +++ /dev/null @@ -1,27 +0,0 @@ -// DO NOT EDIT. This is code generated via package:intl/generate_localized.dart -// This is a library that provides messages for a en locale. All the -// messages from the main program should be duplicated here with the same -// function name. - -// Ignore issues from commonly used lints in this file. -// ignore_for_file:unnecessary_brace_in_string_interps, unnecessary_new -// ignore_for_file:prefer_single_quotes,comment_references, directives_ordering -// ignore_for_file:annotate_overrides,prefer_generic_function_type_aliases -// ignore_for_file:unused_import, file_names, avoid_escaping_inner_quotes -// ignore_for_file:unnecessary_string_interpolations, unnecessary_string_escapes - -import 'package:intl/intl.dart'; -import 'package:intl/message_lookup_by_library.dart'; - -final messages = new MessageLookup(); - -typedef String MessageIfAbsent(String messageStr, List args); - -class MessageLookup extends MessageLookupByLibrary { - String get localeName => 'en'; - - final messages = _notInlinedMessages(_notInlinedMessages); - static Map _notInlinedMessages(_) => { - "labelContacts": MessageLookupByLibrary.simpleMessage("Contacts") - }; -} diff --git a/lib/generated/intl/messages_uk.dart b/lib/generated/intl/messages_uk.dart deleted file mode 100644 index e1ba0a4..0000000 --- a/lib/generated/intl/messages_uk.dart +++ /dev/null @@ -1,27 +0,0 @@ -// DO NOT EDIT. This is code generated via package:intl/generate_localized.dart -// This is a library that provides messages for a uk locale. All the -// messages from the main program should be duplicated here with the same -// function name. - -// Ignore issues from commonly used lints in this file. -// ignore_for_file:unnecessary_brace_in_string_interps, unnecessary_new -// ignore_for_file:prefer_single_quotes,comment_references, directives_ordering -// ignore_for_file:annotate_overrides,prefer_generic_function_type_aliases -// ignore_for_file:unused_import, file_names, avoid_escaping_inner_quotes -// ignore_for_file:unnecessary_string_interpolations, unnecessary_string_escapes - -import 'package:intl/intl.dart'; -import 'package:intl/message_lookup_by_library.dart'; - -final messages = new MessageLookup(); - -typedef String MessageIfAbsent(String messageStr, List args); - -class MessageLookup extends MessageLookupByLibrary { - String get localeName => 'uk'; - - final messages = _notInlinedMessages(_notInlinedMessages); - static Map _notInlinedMessages(_) => { - "labelContacts": MessageLookupByLibrary.simpleMessage("Контакти") - }; -} diff --git a/lib/generated/l10n.dart b/lib/generated/l10n.dart deleted file mode 100644 index 6fd7872..0000000 --- a/lib/generated/l10n.dart +++ /dev/null @@ -1,89 +0,0 @@ -// GENERATED CODE - DO NOT MODIFY BY HAND -import 'package:flutter/material.dart'; -import 'package:intl/intl.dart'; -import 'intl/messages_all.dart'; - -// ************************************************************************** -// Generator: Flutter Intl IDE plugin -// Made by Localizely -// ************************************************************************** - -// ignore_for_file: non_constant_identifier_names, lines_longer_than_80_chars -// ignore_for_file: join_return_with_assignment, prefer_final_in_for_each -// ignore_for_file: avoid_redundant_argument_values, avoid_escaping_inner_quotes - -class S { - S(); - - static S? _current; - - static S get current { - assert(_current != null, - 'No instance of S was loaded. Try to initialize the S delegate before accessing S.current.'); - return _current!; - } - - static const AppLocalizationDelegate delegate = AppLocalizationDelegate(); - - static Future load(Locale locale) { - final name = (locale.countryCode?.isEmpty ?? false) - ? locale.languageCode - : locale.toString(); - final localeName = Intl.canonicalizedLocale(name); - return initializeMessages(localeName).then((_) { - Intl.defaultLocale = localeName; - final instance = S(); - S._current = instance; - - return instance; - }); - } - - static S of(BuildContext context) { - final instance = S.maybeOf(context); - assert(instance != null, - 'No instance of S present in the widget tree. Did you add S.delegate in localizationsDelegates?'); - return instance!; - } - - static S? maybeOf(BuildContext context) { - return Localizations.of(context, S); - } - - /// `Contacts` - String get labelContacts { - return Intl.message( - 'Contacts', - name: 'labelContacts', - desc: '', - args: [], - ); - } -} - -class AppLocalizationDelegate extends LocalizationsDelegate { - const AppLocalizationDelegate(); - - List get supportedLocales { - return const [ - Locale.fromSubtags(languageCode: 'en'), - Locale.fromSubtags(languageCode: 'uk'), - ]; - } - - @override - bool isSupported(Locale locale) => _isSupported(locale); - @override - Future load(Locale locale) => S.load(locale); - @override - bool shouldReload(AppLocalizationDelegate old) => false; - - bool _isSupported(Locale locale) { - for (var supportedLocale in supportedLocales) { - if (supportedLocale.languageCode == locale.languageCode) { - return true; - } - } - return false; - } -} diff --git a/lib/i18n/.gitignore b/lib/i18n/.gitignore new file mode 100644 index 0000000..2536133 --- /dev/null +++ b/lib/i18n/.gitignore @@ -0,0 +1 @@ +*.g.dart \ No newline at end of file diff --git a/lib/i18n/README.md b/lib/i18n/README.md new file mode 100644 index 0000000..6d13f9f --- /dev/null +++ b/lib/i18n/README.md @@ -0,0 +1,22 @@ +# README: Localization File Guide + +## Overview + +This README provides a guide on how to effectively use and populate a localization file using +the provided JSON model. The JSON structure encompasses various sections, including common elements, +error messages, and screen-specific content for an authentication and onboarding system. + +## Categories and Guidelines + +### Common + +- **buttons** +- **placeholders** + +### Errors + +- **validation** + +### Screens + +#### Particular Screen \ No newline at end of file diff --git a/lib/i18n/locale_en.i18n.json b/lib/i18n/locale_en.i18n.json new file mode 100644 index 0000000..6960f5d --- /dev/null +++ b/lib/i18n/locale_en.i18n.json @@ -0,0 +1,39 @@ +{ + "@@locale": "en", + "common": { + "buttons": { + "next": "Next", + "done": "Done" + }, + "placeholders": { + "email": "Email" + }, + "inputs": { + "profileHint": "Profile Name" + } + }, + "errors": { + "validation": { + "invalidEmail": "Please enter a valid email" + } + }, + "screens": { + "auth": { + "title": "Authentication", + "description": "Please enter your credentials to login.", + "statementIAgree": "I agree to ", + "statementPrivacyPolicy": "Privacy Policy", + "statementTermsAndConditions": "Terms & Conditions", + "statementAnd": " and " + }, + "verification": { + "title": "Check your Email", + "description": "Paste dynamically generated code", + "noCodeQuestion": "Didn't get anything?", + "noCodeButtonLabel": "Resend code" + }, + "onboardingProfile": { + "title": "Profile" + } + } +} diff --git a/lib/i18n/locale_uk.i18n.json b/lib/i18n/locale_uk.i18n.json new file mode 100644 index 0000000..f8aa65f --- /dev/null +++ b/lib/i18n/locale_uk.i18n.json @@ -0,0 +1,39 @@ +{ + "@@locale": "uk", + "common": { + "buttons": { + "next": "Далі", + "done": "Done" + }, + "placeholders": { + "email": "Емейл" + }, + "inputs": { + "profileHint": "Profile Name" + } + }, + "errors": { + "validation": { + "invalidEmail": "Введіть коректний email" + } + }, + "screens": { + "auth": { + "title": "Авторизація", + "description": "Введіть email для авторизації", + "statementIAgree": "Я погоджуюсь з ", + "statementPrivacyPolicy": "Політикою конфіденційності", + "statementTermsAndConditions": "Умовами використання", + "statementAnd": " та " + }, + "verification": { + "title": "Перевірте електронну пошту", + "description": "Ми надіслали вам код підтвердження на електронну пошту", + "noCodeQuestion": "Не отримали код?", + "noCodeButtonLabel": "Відправити ще раз" + }, + "onboardingProfile": { + "title": "Профіль" + } + } +} \ No newline at end of file diff --git a/lib/i18n/strings.g.dart b/lib/i18n/strings.g.dart new file mode 100644 index 0000000..e598858 --- /dev/null +++ b/lib/i18n/strings.g.dart @@ -0,0 +1,460 @@ +/// Generated file. Do not edit. +/// +/// Original: lib/i18n +/// To regenerate, run: `dart run slang` +/// +/// Locales: 2 +/// Strings: 32 (16 per locale) +/// +/// Built on 2024-02-03 at 15:42 UTC + +// coverage:ignore-file +// ignore_for_file: type=lint + +import 'package:flutter/widgets.dart'; +import 'package:slang/builder/model/node.dart'; +import 'package:slang_flutter/slang_flutter.dart'; +export 'package:slang_flutter/slang_flutter.dart'; + +const AppLocale _baseLocale = AppLocale.en; + +/// Supported locales, see extension methods below. +/// +/// Usage: +/// - LocaleSettings.setLocale(AppLocale.en) // set locale +/// - Locale locale = AppLocale.en.flutterLocale // get flutter locale from enum +/// - if (LocaleSettings.currentLocale == AppLocale.en) // locale check +enum AppLocale with BaseAppLocale { + en(languageCode: 'en', build: Translations.build), + uk(languageCode: 'uk', build: _StringsUk.build); + + const AppLocale({required this.languageCode, this.scriptCode, this.countryCode, required this.build}); // ignore: unused_element + + @override final String languageCode; + @override final String? scriptCode; + @override final String? countryCode; + @override final TranslationBuilder build; + + /// Gets current instance managed by [LocaleSettings]. + Translations get translations => LocaleSettings.instance.translationMap[this]!; +} + +/// Method A: Simple +/// +/// No rebuild after locale change. +/// Translation happens during initialization of the widget (call of t). +/// Configurable via 'translate_var'. +/// +/// Usage: +/// String a = t.someKey.anotherKey; +/// String b = t['someKey.anotherKey']; // Only for edge cases! +Translations get t => LocaleSettings.instance.currentTranslations; + +/// Method B: Advanced +/// +/// All widgets using this method will trigger a rebuild when locale changes. +/// Use this if you have e.g. a settings page where the user can select the locale during runtime. +/// +/// Step 1: +/// wrap your App with +/// TranslationProvider( +/// child: MyApp() +/// ); +/// +/// Step 2: +/// final t = Translations.of(context); // Get t variable. +/// String a = t.someKey.anotherKey; // Use t variable. +/// String b = t['someKey.anotherKey']; // Only for edge cases! +class TranslationProvider extends BaseTranslationProvider { + TranslationProvider({required super.child}) : super(settings: LocaleSettings.instance); + + static InheritedLocaleData of(BuildContext context) => InheritedLocaleData.of(context); +} + +/// Method B shorthand via [BuildContext] extension method. +/// Configurable via 'translate_var'. +/// +/// Usage (e.g. in a widget's build method): +/// context.t.someKey.anotherKey +extension BuildContextTranslationsExtension on BuildContext { + Translations get t => TranslationProvider.of(this).translations; +} + +/// Manages all translation instances and the current locale +class LocaleSettings extends BaseFlutterLocaleSettings { + LocaleSettings._() : super(utils: AppLocaleUtils.instance); + + static final instance = LocaleSettings._(); + + // static aliases (checkout base methods for documentation) + static AppLocale get currentLocale => instance.currentLocale; + static Stream getLocaleStream() => instance.getLocaleStream(); + static AppLocale setLocale(AppLocale locale, {bool? listenToDeviceLocale = false}) => instance.setLocale(locale, listenToDeviceLocale: listenToDeviceLocale); + static AppLocale setLocaleRaw(String rawLocale, {bool? listenToDeviceLocale = false}) => instance.setLocaleRaw(rawLocale, listenToDeviceLocale: listenToDeviceLocale); + static AppLocale useDeviceLocale() => instance.useDeviceLocale(); + @Deprecated('Use [AppLocaleUtils.supportedLocales]') static List get supportedLocales => instance.supportedLocales; + @Deprecated('Use [AppLocaleUtils.supportedLocalesRaw]') static List get supportedLocalesRaw => instance.supportedLocalesRaw; + static void setPluralResolver({String? language, AppLocale? locale, PluralResolver? cardinalResolver, PluralResolver? ordinalResolver}) => instance.setPluralResolver( + language: language, + locale: locale, + cardinalResolver: cardinalResolver, + ordinalResolver: ordinalResolver, + ); +} + +/// Provides utility functions without any side effects. +class AppLocaleUtils extends BaseAppLocaleUtils { + AppLocaleUtils._() : super(baseLocale: _baseLocale, locales: AppLocale.values); + + static final instance = AppLocaleUtils._(); + + // static aliases (checkout base methods for documentation) + static AppLocale parse(String rawLocale) => instance.parse(rawLocale); + static AppLocale parseLocaleParts({required String languageCode, String? scriptCode, String? countryCode}) => instance.parseLocaleParts(languageCode: languageCode, scriptCode: scriptCode, countryCode: countryCode); + static AppLocale findDeviceLocale() => instance.findDeviceLocale(); + static List get supportedLocales => instance.supportedLocales; + static List get supportedLocalesRaw => instance.supportedLocalesRaw; +} + +// translations + +// Path: +class Translations implements BaseTranslations { + /// Returns the current translations of the given [context]. + /// + /// Usage: + /// final t = Translations.of(context); + static Translations of(BuildContext context) => InheritedLocaleData.of(context).translations; + + /// You can call this constructor and build your own translation instance of this locale. + /// Constructing via the enum [AppLocale.build] is preferred. + Translations.build({Map? overrides, PluralResolver? cardinalResolver, PluralResolver? ordinalResolver}) + : assert(overrides == null, 'Set "translation_overrides: true" in order to enable this feature.'), + $meta = TranslationMetadata( + locale: AppLocale.en, + overrides: overrides ?? {}, + cardinalResolver: cardinalResolver, + ordinalResolver: ordinalResolver, + ) { + $meta.setFlatMapFunction(_flatMapFunction); + } + + /// Metadata for the translations of . + @override final TranslationMetadata $meta; + + /// Access flat map + dynamic operator[](String key) => $meta.getTranslation(key); + + late final Translations _root = this; // ignore: unused_field + + // Translations + late final _StringsCommonEn common = _StringsCommonEn._(_root); + late final _StringsErrorsEn errors = _StringsErrorsEn._(_root); + late final _StringsScreensEn screens = _StringsScreensEn._(_root); +} + +// Path: common +class _StringsCommonEn { + _StringsCommonEn._(this._root); + + final Translations _root; // ignore: unused_field + + // Translations + late final _StringsCommonButtonsEn buttons = _StringsCommonButtonsEn._(_root); + late final _StringsCommonPlaceholdersEn placeholders = _StringsCommonPlaceholdersEn._(_root); + late final _StringsCommonInputsEn inputs = _StringsCommonInputsEn._(_root); +} + +// Path: errors +class _StringsErrorsEn { + _StringsErrorsEn._(this._root); + + final Translations _root; // ignore: unused_field + + // Translations + late final _StringsErrorsValidationEn validation = _StringsErrorsValidationEn._(_root); +} + +// Path: screens +class _StringsScreensEn { + _StringsScreensEn._(this._root); + + final Translations _root; // ignore: unused_field + + // Translations + late final _StringsScreensAuthEn auth = _StringsScreensAuthEn._(_root); + late final _StringsScreensVerificationEn verification = _StringsScreensVerificationEn._(_root); + late final _StringsScreensOnboardingProfileEn onboardingProfile = _StringsScreensOnboardingProfileEn._(_root); +} + +// Path: common.buttons +class _StringsCommonButtonsEn { + _StringsCommonButtonsEn._(this._root); + + final Translations _root; // ignore: unused_field + + // Translations + String get next => 'Next'; + String get done => 'Done'; +} + +// Path: common.placeholders +class _StringsCommonPlaceholdersEn { + _StringsCommonPlaceholdersEn._(this._root); + + final Translations _root; // ignore: unused_field + + // Translations + String get email => 'Email'; +} + +// Path: common.inputs +class _StringsCommonInputsEn { + _StringsCommonInputsEn._(this._root); + + final Translations _root; // ignore: unused_field + + // Translations + String get profileHint => 'Profile Name'; +} + +// Path: errors.validation +class _StringsErrorsValidationEn { + _StringsErrorsValidationEn._(this._root); + + final Translations _root; // ignore: unused_field + + // Translations + String get invalidEmail => 'Please enter a valid email'; +} + +// Path: screens.auth +class _StringsScreensAuthEn { + _StringsScreensAuthEn._(this._root); + + final Translations _root; // ignore: unused_field + + // Translations + String get title => 'Authentication'; + String get description => 'Please enter your credentials to login.'; + String get statementIAgree => 'I agree to '; + String get statementPrivacyPolicy => 'Privacy Policy'; + String get statementTermsAndConditions => 'Terms & Conditions'; + String get statementAnd => ' and '; +} + +// Path: screens.verification +class _StringsScreensVerificationEn { + _StringsScreensVerificationEn._(this._root); + + final Translations _root; // ignore: unused_field + + // Translations + String get title => 'Check your Email'; + String get description => 'Paste dynamically generated code'; + String get noCodeQuestion => 'Didn\'t get anything?'; + String get noCodeButtonLabel => 'Resend code'; +} + +// Path: screens.onboardingProfile +class _StringsScreensOnboardingProfileEn { + _StringsScreensOnboardingProfileEn._(this._root); + + final Translations _root; // ignore: unused_field + + // Translations + String get title => 'Profile'; +} + +// Path: +class _StringsUk implements Translations { + /// You can call this constructor and build your own translation instance of this locale. + /// Constructing via the enum [AppLocale.build] is preferred. + _StringsUk.build({Map? overrides, PluralResolver? cardinalResolver, PluralResolver? ordinalResolver}) + : assert(overrides == null, 'Set "translation_overrides: true" in order to enable this feature.'), + $meta = TranslationMetadata( + locale: AppLocale.uk, + overrides: overrides ?? {}, + cardinalResolver: cardinalResolver, + ordinalResolver: ordinalResolver, + ) { + $meta.setFlatMapFunction(_flatMapFunction); + } + + /// Metadata for the translations of . + @override final TranslationMetadata $meta; + + /// Access flat map + @override dynamic operator[](String key) => $meta.getTranslation(key); + + @override late final _StringsUk _root = this; // ignore: unused_field + + // Translations + @override late final _StringsCommonUk common = _StringsCommonUk._(_root); + @override late final _StringsErrorsUk errors = _StringsErrorsUk._(_root); + @override late final _StringsScreensUk screens = _StringsScreensUk._(_root); +} + +// Path: common +class _StringsCommonUk implements _StringsCommonEn { + _StringsCommonUk._(this._root); + + @override final _StringsUk _root; // ignore: unused_field + + // Translations + @override late final _StringsCommonButtonsUk buttons = _StringsCommonButtonsUk._(_root); + @override late final _StringsCommonPlaceholdersUk placeholders = _StringsCommonPlaceholdersUk._(_root); + @override late final _StringsCommonInputsUk inputs = _StringsCommonInputsUk._(_root); +} + +// Path: errors +class _StringsErrorsUk implements _StringsErrorsEn { + _StringsErrorsUk._(this._root); + + @override final _StringsUk _root; // ignore: unused_field + + // Translations + @override late final _StringsErrorsValidationUk validation = _StringsErrorsValidationUk._(_root); +} + +// Path: screens +class _StringsScreensUk implements _StringsScreensEn { + _StringsScreensUk._(this._root); + + @override final _StringsUk _root; // ignore: unused_field + + // Translations + @override late final _StringsScreensAuthUk auth = _StringsScreensAuthUk._(_root); + @override late final _StringsScreensVerificationUk verification = _StringsScreensVerificationUk._(_root); + @override late final _StringsScreensOnboardingProfileUk onboardingProfile = _StringsScreensOnboardingProfileUk._(_root); +} + +// Path: common.buttons +class _StringsCommonButtonsUk implements _StringsCommonButtonsEn { + _StringsCommonButtonsUk._(this._root); + + @override final _StringsUk _root; // ignore: unused_field + + // Translations + @override String get next => 'Далі'; + @override String get done => 'Done'; +} + +// Path: common.placeholders +class _StringsCommonPlaceholdersUk implements _StringsCommonPlaceholdersEn { + _StringsCommonPlaceholdersUk._(this._root); + + @override final _StringsUk _root; // ignore: unused_field + + // Translations + @override String get email => 'Емейл'; +} + +// Path: common.inputs +class _StringsCommonInputsUk implements _StringsCommonInputsEn { + _StringsCommonInputsUk._(this._root); + + @override final _StringsUk _root; // ignore: unused_field + + // Translations + @override String get profileHint => 'Profile Name'; +} + +// Path: errors.validation +class _StringsErrorsValidationUk implements _StringsErrorsValidationEn { + _StringsErrorsValidationUk._(this._root); + + @override final _StringsUk _root; // ignore: unused_field + + // Translations + @override String get invalidEmail => 'Введіть коректний email'; +} + +// Path: screens.auth +class _StringsScreensAuthUk implements _StringsScreensAuthEn { + _StringsScreensAuthUk._(this._root); + + @override final _StringsUk _root; // ignore: unused_field + + // Translations + @override String get title => 'Авторизація'; + @override String get description => 'Введіть email для авторизації'; + @override String get statementIAgree => 'Я погоджуюсь з '; + @override String get statementPrivacyPolicy => 'Політикою конфіденційності'; + @override String get statementTermsAndConditions => 'Умовами використання'; + @override String get statementAnd => ' та '; +} + +// Path: screens.verification +class _StringsScreensVerificationUk implements _StringsScreensVerificationEn { + _StringsScreensVerificationUk._(this._root); + + @override final _StringsUk _root; // ignore: unused_field + + // Translations + @override String get title => 'Перевірте електронну пошту'; + @override String get description => 'Ми надіслали вам код підтвердження на електронну пошту'; + @override String get noCodeQuestion => 'Не отримали код?'; + @override String get noCodeButtonLabel => 'Відправити ще раз'; +} + +// Path: screens.onboardingProfile +class _StringsScreensOnboardingProfileUk implements _StringsScreensOnboardingProfileEn { + _StringsScreensOnboardingProfileUk._(this._root); + + @override final _StringsUk _root; // ignore: unused_field + + // Translations + @override String get title => 'Профіль'; +} + +/// Flat map(s) containing all translations. +/// Only for edge cases! For simple maps, use the map function of this library. + +extension on Translations { + dynamic _flatMapFunction(String path) { + switch (path) { + case 'common.buttons.next': return 'Next'; + case 'common.buttons.done': return 'Done'; + case 'common.placeholders.email': return 'Email'; + case 'common.inputs.profileHint': return 'Profile Name'; + case 'errors.validation.invalidEmail': return 'Please enter a valid email'; + case 'screens.auth.title': return 'Authentication'; + case 'screens.auth.description': return 'Please enter your credentials to login.'; + case 'screens.auth.statementIAgree': return 'I agree to '; + case 'screens.auth.statementPrivacyPolicy': return 'Privacy Policy'; + case 'screens.auth.statementTermsAndConditions': return 'Terms & Conditions'; + case 'screens.auth.statementAnd': return ' and '; + case 'screens.verification.title': return 'Check your Email'; + case 'screens.verification.description': return 'Paste dynamically generated code'; + case 'screens.verification.noCodeQuestion': return 'Didn\'t get anything?'; + case 'screens.verification.noCodeButtonLabel': return 'Resend code'; + case 'screens.onboardingProfile.title': return 'Profile'; + default: return null; + } + } +} + +extension on _StringsUk { + dynamic _flatMapFunction(String path) { + switch (path) { + case 'common.buttons.next': return 'Далі'; + case 'common.buttons.done': return 'Done'; + case 'common.placeholders.email': return 'Емейл'; + case 'common.inputs.profileHint': return 'Profile Name'; + case 'errors.validation.invalidEmail': return 'Введіть коректний email'; + case 'screens.auth.title': return 'Авторизація'; + case 'screens.auth.description': return 'Введіть email для авторизації'; + case 'screens.auth.statementIAgree': return 'Я погоджуюсь з '; + case 'screens.auth.statementPrivacyPolicy': return 'Політикою конфіденційності'; + case 'screens.auth.statementTermsAndConditions': return 'Умовами використання'; + case 'screens.auth.statementAnd': return ' та '; + case 'screens.verification.title': return 'Перевірте електронну пошту'; + case 'screens.verification.description': return 'Ми надіслали вам код підтвердження на електронну пошту'; + case 'screens.verification.noCodeQuestion': return 'Не отримали код?'; + case 'screens.verification.noCodeButtonLabel': return 'Відправити ще раз'; + case 'screens.onboardingProfile.title': return 'Профіль'; + default: return null; + } + } +} diff --git a/lib/l10n/intl_en.arb b/lib/l10n/intl_en.arb deleted file mode 100644 index 732389d..0000000 --- a/lib/l10n/intl_en.arb +++ /dev/null @@ -1,4 +0,0 @@ -{ - "@@locale": "en", - "labelContacts": "Contacts" -} \ No newline at end of file diff --git a/lib/l10n/intl_uk.arb b/lib/l10n/intl_uk.arb deleted file mode 100644 index cdb226f..0000000 --- a/lib/l10n/intl_uk.arb +++ /dev/null @@ -1,4 +0,0 @@ -{ - "@@locale": "uk", - "labelContacts": "Контакти" -} \ No newline at end of file diff --git a/lib/main.dart b/lib/main.dart index 4d6b501..2e5c164 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,15 +1,19 @@ import 'package:flutter/material.dart'; import 'package:flutter_svg/svg.dart'; +import 'package:golub/i18n/strings.g.dart'; import 'package:golub/src/app.dart'; +import 'package:golub/src/core/di/injectable.dart'; import 'package:golub/src/presentation/ui_kit/ui.dart'; Future main() async { WidgetsFlutterBinding.ensureInitialized(); + LocaleSettings.useDeviceLocale(); await _precacheLogoImage(); - runApp(const App()); + configureDependencies(); + runApp(TranslationProvider(child: const App())); } Future _precacheLogoImage() async { const loader = SvgAssetLoader(AppAssets.splashLogo); svg.cache.putIfAbsent(loader.cacheKey(null), () => loader.loadBytes(null)); -} \ No newline at end of file +} diff --git a/lib/src/app.dart b/lib/src/app.dart index 80f13d3..dbad391 100644 --- a/lib/src/app.dart +++ b/lib/src/app.dart @@ -1,7 +1,8 @@ import 'package:flutter/material.dart'; -import 'package:golub/generated/l10n.dart'; -import 'package:golub/src/presentation/navigation/app_router.dart'; import 'package:flutter_localizations/flutter_localizations.dart'; +import 'package:golub/i18n/strings.g.dart'; +import 'package:golub/src/presentation/navigation/app_router.dart'; +import 'package:golub/src/presentation/ui_kit/theme/themes/light_theme.dart'; class App extends StatelessWidget { const App({super.key}); @@ -13,16 +14,10 @@ class App extends StatelessWidget { // themeMode: themeState.currentThemeMode, // theme: themeState.currentTheme, // darkTheme: themeState.currentThemeDark, - localizationsDelegates: const [ - S.delegate, - GlobalMaterialLocalizations.delegate, - GlobalWidgetsLocalizations.delegate, - GlobalCupertinoLocalizations.delegate, - ], - supportedLocales: const [ - Locale('en', 'US'), - Locale('uk', 'UA'), - ], + theme: lightTheme, + locale: TranslationProvider.of(context).flutterLocale, + localizationsDelegates: GlobalMaterialLocalizations.delegates, + supportedLocales: AppLocaleUtils.supportedLocales, routerConfig: routerConfig, ); } diff --git a/lib/src/core/di/injectable.config.dart b/lib/src/core/di/injectable.config.dart new file mode 100644 index 0000000..91e9735 --- /dev/null +++ b/lib/src/core/di/injectable.config.dart @@ -0,0 +1,41 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ************************************************************************** +// InjectableConfigGenerator +// ************************************************************************** + +// ignore_for_file: type=lint +// coverage:ignore-file + +// ignore_for_file: no_leading_underscores_for_library_prefixes +import 'package:get_it/get_it.dart' as _i1; +import 'package:golub/src/data/repositories/auth_repository_impl.dart' as _i6; +import 'package:golub/src/data/services/dio/dio_client.dart' as _i3; +import 'package:golub/src/data/sources/remote/auth_remote_source.dart' as _i4; +import 'package:golub/src/domain/blocs/auth/auth_bloc.dart' as _i8; +import 'package:golub/src/domain/repositories/auth_repository.dart' as _i5; +import 'package:golub/src/domain/usecases/auth_by_email_usecase.dart' as _i7; +import 'package:injectable/injectable.dart' as _i2; + +extension GetItInjectableX on _i1.GetIt { +// initializes the registration of main-scope dependencies inside of GetIt + _i1.GetIt init({ + String? environment, + _i2.EnvironmentFilter? environmentFilter, + }) { + final gh = _i2.GetItHelper( + this, + environment, + environmentFilter, + ); + gh.singleton<_i3.DioClient>(_i3.DioClient(baseUrl: gh())); + gh.singleton<_i4.AuthRemoteSource>( + _i4.AuthRemoteSource(dioClient: gh<_i3.DioClient>())); + gh.factory<_i5.AuthRepository>( + () => _i6.AuthRepositoryImpl(gh<_i4.AuthRemoteSource>())); + gh.factory<_i7.AuthByEmailUseCase>( + () => _i7.AuthByEmailUseCase(gh<_i5.AuthRepository>())); + gh.factory<_i8.AuthBloc>(() => _i8.AuthBloc(gh<_i7.AuthByEmailUseCase>())); + return this; + } +} diff --git a/lib/src/core/di/injectable.dart b/lib/src/core/di/injectable.dart new file mode 100644 index 0000000..926245e --- /dev/null +++ b/lib/src/core/di/injectable.dart @@ -0,0 +1,22 @@ +import 'package:get_it/get_it.dart'; +import 'package:golub/src/data/repositories/auth_repository_impl.dart'; +import 'package:golub/src/data/services/dio/dio_client.dart'; +import 'package:golub/src/data/sources/remote/auth_remote_source.dart'; +import 'package:golub/src/domain/blocs/auth/auth_bloc.dart'; +import 'package:golub/src/domain/repositories/auth_repository.dart'; +import 'package:golub/src/domain/usecases/auth_by_email_usecase.dart'; +import 'package:injectable/injectable.dart'; + +final getIt = GetIt.instance; + +@InjectableInit() +void configureDependencies() { + getIt.registerSingleton(DioClient(baseUrl: 'https://google.com')); + getIt.registerSingleton( + AuthRemoteSource(dioClient: getIt())); + getIt.registerSingleton( + AuthRepositoryImpl(getIt())); + getIt.registerSingleton( + AuthByEmailUseCase(getIt())); + getIt.registerFactory(() => AuthBloc(getIt())); +} diff --git a/lib/src/data/repositories/auth_repository_impl.dart b/lib/src/data/repositories/auth_repository_impl.dart new file mode 100644 index 0000000..d88e397 --- /dev/null +++ b/lib/src/data/repositories/auth_repository_impl.dart @@ -0,0 +1,15 @@ +import 'package:golub/src/data/sources/remote/auth_remote_source.dart'; +import 'package:golub/src/domain/repositories/auth_repository.dart'; +import 'package:injectable/injectable.dart'; + +@Injectable(as: AuthRepository) +class AuthRepositoryImpl extends AuthRepository { + final AuthRemoteSource _authRemoteSource; + + AuthRepositoryImpl(this._authRemoteSource); + + @override + Future authenticateByEmail(String email) async { + return _authRemoteSource.authenticateByEmail(email); + } +} diff --git a/lib/src/data/services/dio/dio_client.dart b/lib/src/data/services/dio/dio_client.dart new file mode 100644 index 0000000..5038950 --- /dev/null +++ b/lib/src/data/services/dio/dio_client.dart @@ -0,0 +1,97 @@ +import 'dart:developer'; +import 'package:dio/dio.dart'; +import 'package:injectable/injectable.dart'; + +part 'dio_interceptor.dart'; + +@singleton +class DioClient { + final String baseUrl; + late Dio _dio; + + DioClient({required this.baseUrl}) { + _dio = Dio(BaseOptions( + baseUrl: baseUrl, + connectTimeout: const Duration(milliseconds: 30000), + sendTimeout: const Duration(milliseconds: 30000), + receiveTimeout: const Duration(milliseconds: 30000), + responseType: ResponseType.json, + )); + + /// Add logging interceptor + _dio.interceptors.add(DioInterceptor()); + } + + /// Get method + Future get( + String path, { + Map? headers, + Map? queryParameters, + }) async { + return _dio.get( + path, + options: Options(headers: headers), + queryParameters: queryParameters + ); + } + + /// Post method + Future post( + String path, { + Map? headers, + Map? queryParameters, + dynamic data, + }) async { + return _dio.post( + path, + options: Options(headers: headers), + queryParameters: queryParameters, + data: data, + ); + } + + /// Put method + Future put( + String path, { + Map? headers, + Map? queryParameters, + dynamic data, + }) async { + return _dio.put( + path, + options: Options(headers: headers), + queryParameters: queryParameters, + data: data, + ); + } + + /// Delete method + Future delete( + String path, { + Map? headers, + Map? queryParameters, + dynamic data, + }) async { + return _dio.delete( + path, + options: Options(headers: headers), + queryParameters: queryParameters, + data: data, + ); + } + + /// Patch method + Future patch( + String path, { + Map? headers, + Map? queryParameters, + dynamic data, + }) async { + return _dio.patch( + path, + options: Options(headers: headers), + queryParameters: queryParameters, + data: data, + ); + } +} \ No newline at end of file diff --git a/lib/src/data/services/dio/dio_interceptor.dart b/lib/src/data/services/dio/dio_interceptor.dart new file mode 100644 index 0000000..63552bf --- /dev/null +++ b/lib/src/data/services/dio/dio_interceptor.dart @@ -0,0 +1,27 @@ +part of 'dio_client.dart'; + +class DioInterceptor implements Interceptor { + @override + void onRequest( + RequestOptions options, RequestInterceptorHandler handler) async { + log('🔸Request[${options.method}] => Path: ${options.uri}'); + return handler.next(options); + } + + @override + void onResponse(Response response, ResponseInterceptorHandler handler) async { + log('🔹Response[${response.statusCode}] =>' + ' Data: ${_formattedData(response.data.toString())}'); + return handler.next(response); + } + + @override + void onError(DioException e, ErrorInterceptorHandler handler) async { + log('🚧onError[${e.type}] => Path: ${e.response?.realUri}'); + return handler.next(e); + } + + String _formattedData(String data) { + return data.length > 64 ? '${data.substring(0, 64)} ... }' : data; + } +} \ No newline at end of file diff --git a/lib/src/data/sources/remote/auth_remote_source.dart b/lib/src/data/sources/remote/auth_remote_source.dart new file mode 100644 index 0000000..bf429cd --- /dev/null +++ b/lib/src/data/sources/remote/auth_remote_source.dart @@ -0,0 +1,19 @@ +import 'package:golub/src/data/services/dio/dio_client.dart'; +import 'package:injectable/injectable.dart'; + +@singleton +class AuthRemoteSource { + final DioClient dioClient; + + const AuthRemoteSource({required this.dioClient}); + + static const String _pathAuthenticateByEmail = '/auth'; + + Future authenticateByEmail(String email) async { + print('__source $email'); + await dioClient.post( + _pathAuthenticateByEmail, + data: {'email': email}, + ); + } +} diff --git a/lib/src/domain/blocs/auth/auth_bloc.dart b/lib/src/domain/blocs/auth/auth_bloc.dart new file mode 100644 index 0000000..2acd06c --- /dev/null +++ b/lib/src/domain/blocs/auth/auth_bloc.dart @@ -0,0 +1,55 @@ +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; +import 'package:golub/i18n/strings.g.dart'; +import 'package:golub/src/domain/extenstions/string_validation_ext.dart'; +import 'package:golub/src/domain/usecases/auth_by_email_usecase.dart'; +import 'package:injectable/injectable.dart'; + +part 'auth_bloc.freezed.dart'; +part 'auth_event.dart'; +part 'auth_state.dart'; + +@injectable +class AuthBloc extends Bloc { + final AuthByEmailUseCase _authByEmailUseCase; + + AuthBloc(this._authByEmailUseCase) : super(AuthState.initial()) { + on(_changeEmailEvent); + on(_changePrivacyPolicyStatusEvent); + on(_authenticateByEmailEvent); + on(_clearStateEvent); + } + + Future _changeEmailEvent(ChangeEmailEvent event, Emitter emit) async { + emit(state.copyWith(email: event.value, validationError: null)); + } + + Future _changePrivacyPolicyStatusEvent( + ChangePrivacyPolicyStatusEvent event, Emitter emit) async { + emit(state.copyWith(privacyPolicyAccepted: event.value)); + } + + Future _authenticateByEmailEvent( + AuthenticateByEmailEvent event, Emitter emit) async { + if (!state.email.isValidEmail) { + emit(state.copyWith(validationError: t.errors.validation.invalidEmail)); + return; + } + + emit(state.copyWith(status: AuthStatus.loading)); + try { + await Future.delayed(const Duration(milliseconds: 2000)); + //await _authByEmailUseCase(state.email); + emit(state.copyWith(status: AuthStatus.success)); + } catch (e) { + emit(state.copyWith(status: AuthStatus.error, error: e.toString())); + } + } + + Future _clearStateEvent(ClearStateEvent event, Emitter emit) async { + emit(AuthState.initial()); + } + + /// Getters + bool get isButtonDisabled => !state.privacyPolicyAccepted || state.email.isEmpty; +} diff --git a/lib/src/domain/blocs/auth/auth_bloc.freezed.dart b/lib/src/domain/blocs/auth/auth_bloc.freezed.dart new file mode 100644 index 0000000..f522a0e --- /dev/null +++ b/lib/src/domain/blocs/auth/auth_bloc.freezed.dart @@ -0,0 +1,223 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// 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_bloc.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#custom-getters-and-methods'); + +/// @nodoc +mixin _$AuthState { + AuthStatus get status => throw _privateConstructorUsedError; + String get email => throw _privateConstructorUsedError; + String? get validationError => throw _privateConstructorUsedError; + String? get error => throw _privateConstructorUsedError; + bool get privacyPolicyAccepted => throw _privateConstructorUsedError; + + @JsonKey(ignore: true) + $AuthStateCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $AuthStateCopyWith<$Res> { + factory $AuthStateCopyWith(AuthState value, $Res Function(AuthState) then) = + _$AuthStateCopyWithImpl<$Res, AuthState>; + @useResult + $Res call( + {AuthStatus status, + String email, + String? validationError, + String? error, + bool privacyPolicyAccepted}); +} + +/// @nodoc +class _$AuthStateCopyWithImpl<$Res, $Val extends AuthState> + implements $AuthStateCopyWith<$Res> { + _$AuthStateCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? status = null, + Object? email = null, + Object? validationError = freezed, + Object? error = freezed, + Object? privacyPolicyAccepted = null, + }) { + return _then(_value.copyWith( + status: null == status + ? _value.status + : status // ignore: cast_nullable_to_non_nullable + as AuthStatus, + email: null == email + ? _value.email + : email // ignore: cast_nullable_to_non_nullable + as String, + validationError: freezed == validationError + ? _value.validationError + : validationError // ignore: cast_nullable_to_non_nullable + as String?, + error: freezed == error + ? _value.error + : error // ignore: cast_nullable_to_non_nullable + as String?, + privacyPolicyAccepted: null == privacyPolicyAccepted + ? _value.privacyPolicyAccepted + : privacyPolicyAccepted // ignore: cast_nullable_to_non_nullable + as bool, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$AuthStateImplCopyWith<$Res> + implements $AuthStateCopyWith<$Res> { + factory _$$AuthStateImplCopyWith( + _$AuthStateImpl value, $Res Function(_$AuthStateImpl) then) = + __$$AuthStateImplCopyWithImpl<$Res>; + @override + @useResult + $Res call( + {AuthStatus status, + String email, + String? validationError, + String? error, + bool privacyPolicyAccepted}); +} + +/// @nodoc +class __$$AuthStateImplCopyWithImpl<$Res> + extends _$AuthStateCopyWithImpl<$Res, _$AuthStateImpl> + implements _$$AuthStateImplCopyWith<$Res> { + __$$AuthStateImplCopyWithImpl( + _$AuthStateImpl _value, $Res Function(_$AuthStateImpl) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? status = null, + Object? email = null, + Object? validationError = freezed, + Object? error = freezed, + Object? privacyPolicyAccepted = null, + }) { + return _then(_$AuthStateImpl( + status: null == status + ? _value.status + : status // ignore: cast_nullable_to_non_nullable + as AuthStatus, + email: null == email + ? _value.email + : email // ignore: cast_nullable_to_non_nullable + as String, + validationError: freezed == validationError + ? _value.validationError + : validationError // ignore: cast_nullable_to_non_nullable + as String?, + error: freezed == error + ? _value.error + : error // ignore: cast_nullable_to_non_nullable + as String?, + privacyPolicyAccepted: null == privacyPolicyAccepted + ? _value.privacyPolicyAccepted + : privacyPolicyAccepted // ignore: cast_nullable_to_non_nullable + as bool, + )); + } +} + +/// @nodoc + +class _$AuthStateImpl implements _AuthState { + const _$AuthStateImpl( + {this.status = AuthStatus.initial, + this.email = '', + this.validationError = null, + this.error = null, + this.privacyPolicyAccepted = false}); + + @override + @JsonKey() + final AuthStatus status; + @override + @JsonKey() + final String email; + @override + @JsonKey() + final String? validationError; + @override + @JsonKey() + final String? error; + @override + @JsonKey() + final bool privacyPolicyAccepted; + + @override + String toString() { + return 'AuthState(status: $status, email: $email, validationError: $validationError, error: $error, privacyPolicyAccepted: $privacyPolicyAccepted)'; + } + + @override + bool operator ==(dynamic other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$AuthStateImpl && + (identical(other.status, status) || other.status == status) && + (identical(other.email, email) || other.email == email) && + (identical(other.validationError, validationError) || + other.validationError == validationError) && + (identical(other.error, error) || other.error == error) && + (identical(other.privacyPolicyAccepted, privacyPolicyAccepted) || + other.privacyPolicyAccepted == privacyPolicyAccepted)); + } + + @override + int get hashCode => Object.hash(runtimeType, status, email, validationError, + error, privacyPolicyAccepted); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$AuthStateImplCopyWith<_$AuthStateImpl> get copyWith => + __$$AuthStateImplCopyWithImpl<_$AuthStateImpl>(this, _$identity); +} + +abstract class _AuthState implements AuthState { + const factory _AuthState( + {final AuthStatus status, + final String email, + final String? validationError, + final String? error, + final bool privacyPolicyAccepted}) = _$AuthStateImpl; + + @override + AuthStatus get status; + @override + String get email; + @override + String? get validationError; + @override + String? get error; + @override + bool get privacyPolicyAccepted; + @override + @JsonKey(ignore: true) + _$$AuthStateImplCopyWith<_$AuthStateImpl> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/lib/src/domain/blocs/auth/auth_event.dart b/lib/src/domain/blocs/auth/auth_event.dart new file mode 100644 index 0000000..dae8e7a --- /dev/null +++ b/lib/src/domain/blocs/auth/auth_event.dart @@ -0,0 +1,17 @@ +part of 'auth_bloc.dart'; + +abstract class AuthEvent {} + +class ChangeEmailEvent extends AuthEvent { + final String value; + ChangeEmailEvent(this.value); +} + +class ChangePrivacyPolicyStatusEvent extends AuthEvent { + final bool value; + ChangePrivacyPolicyStatusEvent(this.value); +} + +class AuthenticateByEmailEvent extends AuthEvent {} + +class ClearStateEvent extends AuthEvent {} diff --git a/lib/src/domain/blocs/auth/auth_state.dart b/lib/src/domain/blocs/auth/auth_state.dart new file mode 100644 index 0000000..13810bb --- /dev/null +++ b/lib/src/domain/blocs/auth/auth_state.dart @@ -0,0 +1,21 @@ +part of 'auth_bloc.dart'; + +enum AuthStatus { + initial, + loading, + success, + error, +} + +@freezed +class AuthState with _$AuthState { + const factory AuthState({ + @Default(AuthStatus.initial) AuthStatus status, + @Default('') String email, + @Default(null) String? validationError, + @Default(null) String? error, + @Default(false) bool privacyPolicyAccepted, + }) = _AuthState; + + factory AuthState.initial() => const AuthState(); +} diff --git a/lib/src/domain/extenstions/string_validation_ext.dart b/lib/src/domain/extenstions/string_validation_ext.dart new file mode 100644 index 0000000..3f4a34a --- /dev/null +++ b/lib/src/domain/extenstions/string_validation_ext.dart @@ -0,0 +1,5 @@ +extension StringValidationExt on String { + bool get isValidEmail { + return RegExp(r'^[a-zA-Z0-9.]+@[a-zA-Z0-9]+\.[a-zA-Z]+').hasMatch(this); + } +} \ No newline at end of file diff --git a/lib/src/domain/repositories/auth_repository.dart b/lib/src/domain/repositories/auth_repository.dart new file mode 100644 index 0000000..269e846 --- /dev/null +++ b/lib/src/domain/repositories/auth_repository.dart @@ -0,0 +1,3 @@ +abstract class AuthRepository { + Future authenticateByEmail(String email); +} diff --git a/lib/src/domain/usecases/auth_by_email_usecase.dart b/lib/src/domain/usecases/auth_by_email_usecase.dart new file mode 100644 index 0000000..ff163c6 --- /dev/null +++ b/lib/src/domain/usecases/auth_by_email_usecase.dart @@ -0,0 +1,13 @@ +import 'package:golub/src/domain/repositories/auth_repository.dart'; +import 'package:injectable/injectable.dart'; + +@injectable +class AuthByEmailUseCase { + final AuthRepository _repository; + + AuthByEmailUseCase(this._repository); + + Future call(String email) async { + return _repository.authenticateByEmail(email); + } +} diff --git a/lib/src/presentation/features/auth/screens/auth_screen.dart b/lib/src/presentation/features/auth/screens/auth_screen.dart new file mode 100644 index 0000000..b542974 --- /dev/null +++ b/lib/src/presentation/features/auth/screens/auth_screen.dart @@ -0,0 +1,192 @@ +import 'package:flutter/gestures.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:go_router/go_router.dart'; +import 'package:golub/i18n/strings.g.dart'; +import 'package:golub/src/core/di/injectable.dart'; +import 'package:golub/src/domain/blocs/auth/auth_bloc.dart'; +import 'package:golub/src/presentation/navigation/app_router.dart'; +import 'package:golub/src/presentation/ui_kit/theme/app_colors.dart'; +import 'package:golub/src/presentation/ui_kit/theme/app_styles.dart'; +import 'package:golub/src/presentation/ui_kit/ui.dart'; +import 'package:golub/src/presentation/ui_kit/widgets/input/gtext_field.dart'; +import 'package:golub/src/presentation/utils/link_contants.dart'; +import 'package:golub/src/presentation/utils/link_launcher.dart'; + +class AuthScreen extends StatefulWidget { + const AuthScreen({super.key}); + + @override + State createState() => _AuthScreenState(); +} + +class _AuthScreenState extends State { + final AuthBloc _authBloc = getIt(); + + final TextEditingController _emailController = TextEditingController( + text: 'test@gmail.com', + ); + final FocusNode _emailFocusNode = FocusNode(); + + final TapGestureRecognizer _termsConditionsTapRecognizer = TapGestureRecognizer(); + final TapGestureRecognizer _privacyPolicyTapRecognizer = TapGestureRecognizer(); + + @override + void initState() { + super.initState(); + _emailController.addListener(_handleEmailString); + _authBloc.add(ChangeEmailEvent(_emailController.text)); + _authBloc.add(ChangePrivacyPolicyStatusEvent(true)); + } + + void _handleEmailString() { + _authBloc.add(ChangeEmailEvent(_emailController.text)); + } + + @override + void dispose() { + _emailController.removeListener(_handleEmailString); + _emailController.dispose(); + _termsConditionsTapRecognizer.dispose(); + _privacyPolicyTapRecognizer.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + final size = MediaQuery.of(context).size; + + return BlocProvider( + create: (_) => _authBloc, + child: Scaffold( + body: Container( + constraints: const BoxConstraints.expand(), + decoration: const BoxDecoration( + gradient: AppStyles.blueWhiteGradient, + ), + child: SafeArea( + child: Padding( + padding: const EdgeInsets.only(top: 16.0, left: 16.0, right: 16.0), + child: SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: EdgeInsets.only( + top: size.height / 6, + ), + child: Text( + t.screens.auth.title, + style: Theme.of(context).textTheme.titleLarge, + ), + ), + const SizedBox(height: 32.0), + Text( + t.screens.auth.description, + style: Theme.of(context).textTheme.bodyMedium, + ), + const SizedBox(height: 16.0), + BlocBuilder( + bloc: _authBloc, + builder: (BuildContext context, AuthState state) { + return GTextField( + textEditingController: _emailController, + focusNode: _emailFocusNode, + hintText: t.common.placeholders.email, + error: state.validationError, + ); + }), + Padding( + padding: const EdgeInsets.only( + top: 60.0, + bottom: 32.0, + ), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox( + width: 20.0, + height: 20.0, + child: BlocBuilder( + bloc: _authBloc, + builder: (BuildContext context, AuthState state) { + return Checkbox( + materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(4.0)), + side: const BorderSide( + color: Colors.black, + width: 1.0, + ), + checkColor: Colors.white, + activeColor: AppColors.brightLightPurple, + value: state.privacyPolicyAccepted, + onChanged: (bool? value) => _authBloc.add( + ChangePrivacyPolicyStatusEvent(value ?? false), + ), + ); + }, + ), + ), + const SizedBox(width: 12.0), + Expanded( + child: RichText( + maxLines: 2, + text: TextSpan( + text: t.screens.auth.statementIAgree, + style: Theme.of(context).textTheme.bodySmall, + children: [ + TextSpan( + recognizer: _termsConditionsTapRecognizer + ..onTap = () => launchLink(LinkConstants.termAndCondition), + text: t.screens.auth.statementTermsAndConditions, + style: Theme.of(context) + .textTheme + .bodySmall + ?.copyWith(color: AppColors.brightBlue), + ), + TextSpan( + text: t.screens.auth.statementAnd, + ), + TextSpan( + recognizer: _privacyPolicyTapRecognizer + ..onTap = () => launchLink(LinkConstants.privacy), + text: t.screens.auth.statementPrivacyPolicy, + style: Theme.of(context) + .textTheme + .bodySmall + ?.copyWith(color: AppColors.brightBlue), + ), + ], + ), + ), + ), + ], + ), + ), + BlocConsumer( + bloc: _authBloc, + listener: (BuildContext context, AuthState state) { + if (state.status == AuthStatus.success) { + context.pushNamed(AppRoutes.verification); + } + }, + builder: (BuildContext context, AuthState state) { + return GButton( + isDisabled: _authBloc.isButtonDisabled, + isLoading: state.status == AuthStatus.loading, + onPressed: () => _authBloc.add(AuthenticateByEmailEvent()), + buttonLabel: t.common.buttons.next, + ); + }, + ), + ], + ), + ), + ), + ), + ), + ), + ); + } +} diff --git a/lib/src/presentation/features/auth/screens/verification_screen.dart b/lib/src/presentation/features/auth/screens/verification_screen.dart new file mode 100644 index 0000000..d29bf39 --- /dev/null +++ b/lib/src/presentation/features/auth/screens/verification_screen.dart @@ -0,0 +1,167 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:go_router/go_router.dart'; +import 'package:golub/i18n/strings.g.dart'; +import 'package:golub/src/presentation/features/auth/widgets/verification/verification_input.dart'; +import 'package:golub/src/presentation/navigation/app_router.dart'; +import 'package:golub/src/presentation/ui_kit/theme/app_colors.dart'; +import 'package:golub/src/presentation/ui_kit/theme/app_styles.dart'; +import 'package:golub/src/presentation/ui_kit/ui.dart'; + +class VerificationScreen extends StatefulWidget { + const VerificationScreen({super.key}); + + @override + State createState() => _VerificationScreenState(); +} + +class _VerificationScreenState extends State { + final TextEditingController _verificationDigitFirst = TextEditingController(); + final TextEditingController _verificationDigitSecond = TextEditingController(); + final TextEditingController _verificationDigitThird = TextEditingController(); + final TextEditingController _verificationDigitFourth = TextEditingController(); + final TextEditingController _verificationDigitFifth = TextEditingController(); + + final FocusNode _fnFirst = FocusNode(); + final FocusNode _fnSecond = FocusNode(); + final FocusNode _fnThird = FocusNode(); + final FocusNode _fnFourth = FocusNode(); + final FocusNode _fnFifth = FocusNode(); + + @override + Widget build(BuildContext context) { + final t = Translations.of(context); + + return Scaffold( + body: Container( + constraints: const BoxConstraints.expand(), + decoration: const BoxDecoration( + gradient: AppStyles.blueWhiteGradient, + ), + child: SafeArea( + bottom: false, + child: Padding( + padding: const EdgeInsets.only( + left: 16.0, + right: 16.0, + top: 20.0, + ), + child: Column(children: [ + Align( + alignment: Alignment.topLeft, + child: GestureDetector( + behavior: HitTestBehavior.opaque, + onTap: () => context.pop(), + child: SvgPicture.asset( + AppAssets.arrowBackIcon, + fit: BoxFit.cover, + ), + ), + ), + SizedBox( + width: 64.0, + height: 64.0, + child: SvgPicture.asset( + AppAssets.chatsIcon, + fit: BoxFit.cover, + ), + ), + const SizedBox(height: 32.0), + Text( + t.screens.verification.title, + style: Theme.of(context).textTheme.titleLarge, + ), + const SizedBox(height: 12.0), + Text( + t.screens.verification.description, + style: Theme.of(context).textTheme.bodyMedium, + ), + Container( + padding: const EdgeInsets.only(top: 40.0, bottom: 28.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Expanded( + flex: 1, + child: VerificationInput( + textInputAction: TextInputAction.next, + controller: _verificationDigitFirst, + focusNode: _fnFirst, + nextFocusNode: _fnSecond, + ), + ), + const SizedBox(width: 8.0), + Expanded( + flex: 1, + child: VerificationInput( + textInputAction: TextInputAction.next, + controller: _verificationDigitSecond, + focusNode: _fnSecond, + nextFocusNode: _fnThird, + ), + ), + const SizedBox(width: 8.0), + Expanded( + flex: 1, + child: VerificationInput( + textInputAction: TextInputAction.next, + controller: _verificationDigitThird, + focusNode: _fnThird, + nextFocusNode: _fnFourth, + ), + ), + const SizedBox(width: 8.0), + Expanded( + flex: 1, + child: VerificationInput( + textInputAction: TextInputAction.next, + controller: _verificationDigitFourth, + focusNode: _fnFourth, + nextFocusNode: _fnFifth, + ), + ), + const SizedBox(width: 8.0), + Expanded( + flex: 1, + child: VerificationInput( + controller: _verificationDigitFifth, + focusNode: _fnFifth, + ), + ), + ], + ), + ), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + t.screens.verification.noCodeQuestion, + ), + const SizedBox(width: 4.0), + GestureDetector( + behavior: HitTestBehavior.opaque, + onTap: () { + print('resend code'); + }, + child: Text( + t.screens.verification.noCodeButtonLabel, + style: Theme.of(context).textTheme.bodyMedium?.copyWith( + color: AppColors.brightOrange, + ), + ), + ), + ], + ), + const SizedBox(height: 36.0), + GButton( + buttonLabel: t.common.buttons.next, + onPressed: () { + context.pushNamed(AppRoutes.onboardingProfile); + }), + ]), + ), + ), + ), + ); + } +} diff --git a/lib/src/presentation/features/auth/widgets/verification/verification_input.dart b/lib/src/presentation/features/auth/widgets/verification/verification_input.dart new file mode 100644 index 0000000..1fdeb85 --- /dev/null +++ b/lib/src/presentation/features/auth/widgets/verification/verification_input.dart @@ -0,0 +1,68 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:golub/src/presentation/ui_kit/theme/app_colors.dart'; + +class VerificationInput extends StatelessWidget { + final TextEditingController? controller; + final FocusNode? focusNode; + final FocusNode? nextFocusNode; + final TextInputAction textInputAction; + final VoidCallback? onEditingComplete; + + const VerificationInput({ + this.controller, + this.focusNode, + this.nextFocusNode, + this.textInputAction = TextInputAction.done, + this.onEditingComplete, + super.key + }); + + @override + Widget build(BuildContext context) { + return Container( + constraints: const BoxConstraints( + maxWidth: 56.0, + minHeight: 76.0, + ), + child: TextField( + controller: controller, + focusNode: focusNode, + keyboardType: TextInputType.number, + textInputAction: textInputAction, + inputFormatters: [ + LengthLimitingTextInputFormatter(1), + FilteringTextInputFormatter.digitsOnly, + ], + textAlign: TextAlign.center, + style: Theme.of(context).textTheme.titleLarge, + decoration: InputDecoration( + fillColor: Colors.transparent, + contentPadding: const EdgeInsets.symmetric(vertical: 24.0, horizontal: 12.0), + focusedBorder: OutlineInputBorder( + borderSide: const BorderSide( + color: AppColors.brightBlue, + width: 1.5, + ), + borderRadius: BorderRadius.circular(16.0), + ), + enabledBorder: OutlineInputBorder( + borderSide: const BorderSide(color: AppColors.baseGray3), + borderRadius: BorderRadius.circular(16.0), + ), + disabledBorder: OutlineInputBorder( + borderSide: const BorderSide(color: AppColors.baseWhite), + borderRadius: BorderRadius.circular(16.0), + ), + ), + onEditingComplete: () { + focusNode?.unfocus(); + nextFocusNode?.requestFocus(); + if (onEditingComplete != null) { + onEditingComplete!(); + } + }, + ), + ); + } +} diff --git a/lib/src/presentation/features/onboarding/onboarding_profile_screen.dart b/lib/src/presentation/features/onboarding/onboarding_profile_screen.dart new file mode 100644 index 0000000..5b1e43f --- /dev/null +++ b/lib/src/presentation/features/onboarding/onboarding_profile_screen.dart @@ -0,0 +1,63 @@ +import 'package:flutter/material.dart'; +import 'package:go_router/go_router.dart'; +import 'package:golub/i18n/strings.g.dart'; +import 'package:golub/src/presentation/navigation/app_router.dart'; +import 'package:golub/src/presentation/ui_kit/theme/app_colors.dart'; +import 'package:golub/src/presentation/ui_kit/ui.dart'; +import 'package:golub/src/presentation/ui_kit/widgets/input/gtext_field.dart'; +import 'package:golub/src/presentation/ui_kit/widgets/input/profile_upload_image.dart'; + +class OnboardingProfileScreen extends StatelessWidget { + const OnboardingProfileScreen({super.key}); + + @override + Widget build(BuildContext context) { + final t = Translations.of(context); + return Scaffold( + appBar: AppBar( + automaticallyImplyLeading: false, + title: Text( + t.screens.onboardingProfile.title, + style: Theme.of(context).textTheme.titleMedium?.copyWith( + color: AppColors.baseBlack, + ), + ), + centerTitle: true, + backgroundColor: Colors.transparent, + elevation: 0.0, + ), + body: SafeArea( + bottom: false, + child: Container( + padding: const EdgeInsets.only( + left: 16.0, + right: 16.0, + top: 16.0, + ), + width: double.infinity, + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisSize: MainAxisSize.max, + children: [ + const ProfileUploadImage(), + const SizedBox(height: 32.0), + GTextField( + hintText: t.common.inputs.profileHint, + fillColor: AppColors.baseGray2, + ), + const SizedBox(height: 48.0), + GButton( + buttonLabel: t.common.buttons.done, + isDisabled: false, + onPressed: () { + print('Done'); + context.goNamed(AppRoutes.chats); + } + ), + ], + ), + ), + ), + ); + } +} diff --git a/lib/src/presentation/features/splash/screens/splash_screen.dart b/lib/src/presentation/features/splash/screens/splash_screen.dart index 9ce7198..6e65993 100644 --- a/lib/src/presentation/features/splash/screens/splash_screen.dart +++ b/lib/src/presentation/features/splash/screens/splash_screen.dart @@ -1,8 +1,7 @@ import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:go_router/go_router.dart'; -import 'package:golub/src/presentation/navigation/app_routes.dart'; +import 'package:golub/src/presentation/navigation/app_router.dart'; import 'package:golub/src/presentation/ui_kit/theme/app_assets.dart'; import 'package:golub/src/presentation/ui_kit/theme/app_styles.dart'; @@ -18,7 +17,7 @@ class _SplashScreenState extends State { void initState() { super.initState(); _initialization().then((_) { - context.goNamed(AppRoutes.chats); + context.goNamed(AppRoutes.auth); }); } diff --git a/lib/src/presentation/navigation/app_bottom_navigation_bar_item_widget.dart b/lib/src/presentation/navigation/app_bottom_navigation_bar_item_widget.dart index 62ad463..abcc578 100644 --- a/lib/src/presentation/navigation/app_bottom_navigation_bar_item_widget.dart +++ b/lib/src/presentation/navigation/app_bottom_navigation_bar_item_widget.dart @@ -9,13 +9,12 @@ class AppBottomNavigationBarItemWidget extends StatelessWidget { final bool isActive; final VoidCallback onTap; - const AppBottomNavigationBarItemWidget({ - required this.image, - this.label = '', - required this.isActive, - required this.onTap, - super.key - }); + const AppBottomNavigationBarItemWidget( + {required this.image, + this.label = '', + required this.isActive, + required this.onTap, + super.key}); @override Widget build(BuildContext context) { @@ -31,33 +30,29 @@ class AppBottomNavigationBarItemWidget extends StatelessWidget { children: [ AnimatedContainer( duration: const Duration(milliseconds: 250), - width: isActive ? - AppSizes.bottomNavigationBarActiveItemSize : - AppSizes.bottomNavigationBarItemSize, - height: isActive ? - AppSizes.bottomNavigationBarActiveItemSize : - AppSizes.bottomNavigationBarItemSize, + width: isActive + ? AppSizes.bottomNavigationBarActiveItemSize + : AppSizes.bottomNavigationBarItemSize, + height: isActive + ? AppSizes.bottomNavigationBarActiveItemSize + : AppSizes.bottomNavigationBarItemSize, child: SvgPicture.asset( image, - colorFilter: ColorFilter.mode( - // isActive ? - // Theme.of(context) - // .bottomNavigationBarTheme - // .selectedItemColor! : - // Theme.of(context) - // .bottomNavigationBarTheme - // .unselectedItemColor!, - Colors.white, - BlendMode.srcIn - ), + colorFilter: const ColorFilter.mode( + // isActive ? + // Theme.of(context) + // .bottomNavigationBarTheme + // .selectedItemColor! : + // Theme.of(context) + // .bottomNavigationBarTheme + // .unselectedItemColor!, + Colors.white, + BlendMode.srcIn), fit: BoxFit.cover, placeholderBuilder: (BuildContext context) => Container( width: AppSizes.bottomNavigationBarItemSize, height: AppSizes.bottomNavigationBarItemSize, - decoration: const BoxDecoration( - color: Colors.grey, - shape: BoxShape.circle - ), + decoration: const BoxDecoration(color: Colors.grey, shape: BoxShape.circle), ), ), ), @@ -70,11 +65,8 @@ class AppBottomNavigationBarItemWidget extends StatelessWidget { // Theme.of(context) // .bottomNavigationBarTheme // .unselectedLabelStyle!, - style: TextStyle( - color: Colors.white, - fontSize: 12.0, - fontWeight: FontWeight.w600 - ), + style: + const TextStyle(color: Colors.white, fontSize: 12.0, fontWeight: FontWeight.w600), child: Text( label, style: const TextStyle( diff --git a/lib/src/presentation/navigation/app_bottom_navigation_bar_widget.dart b/lib/src/presentation/navigation/app_bottom_navigation_bar_widget.dart index 01cfa57..4353364 100644 --- a/lib/src/presentation/navigation/app_bottom_navigation_bar_widget.dart +++ b/lib/src/presentation/navigation/app_bottom_navigation_bar_widget.dart @@ -1,5 +1,5 @@ import 'package:flutter/material.dart'; -import 'package:golub/generated/l10n.dart'; +import 'package:golub/i18n/strings.g.dart'; import 'package:golub/src/presentation/navigation/app_bottom_navigation_bar_item_widget.dart'; import 'package:golub/src/presentation/ui_kit/ui.dart'; @@ -7,22 +7,15 @@ class AppBottomNavigationBarWidget extends StatefulWidget { final int index; final Function(int) onTap; - const AppBottomNavigationBarWidget({ - required this.index, - required this.onTap, - super.key - }); + const AppBottomNavigationBarWidget({required this.index, required this.onTap, super.key}); @override - State createState() => - _AppBottomNavigationBarWidgetState(); + State createState() => _AppBottomNavigationBarWidgetState(); } -class _AppBottomNavigationBarWidgetState extends - State { - +class _AppBottomNavigationBarWidgetState extends State { final List<({String icon, String label})> _bottomNavigationBarItem = [ - (icon: AppAssets.contactIcon, label: S.current.labelContacts), + (icon: AppAssets.contactIcon, label: t.screens.auth.title), (icon: AppAssets.chatIcon, label: 'Chats'), (icon: AppAssets.profileIcon, label: 'Profile'), ]; @@ -40,14 +33,18 @@ class _AppBottomNavigationBarWidgetState extends mainAxisAlignment: MainAxisAlignment.spaceAround, crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.max, - children: _bottomNavigationBarItem.asMap().entries.map((e) => - AppBottomNavigationBarItemWidget( - image: e.value.icon, - label: e.value.label, - isActive: e.key == widget.index, - onTap: () => widget.onTap(e.key), - ), - ).toList(), + children: _bottomNavigationBarItem + .asMap() + .entries + .map( + (e) => AppBottomNavigationBarItemWidget( + image: e.value.icon, + label: e.value.label, + isActive: e.key == widget.index, + onTap: () => widget.onTap(e.key), + ), + ) + .toList(), ), ); } diff --git a/lib/src/presentation/navigation/app_router.dart b/lib/src/presentation/navigation/app_router.dart index 70f028b..68d1815 100644 --- a/lib/src/presentation/navigation/app_router.dart +++ b/lib/src/presentation/navigation/app_router.dart @@ -1,11 +1,15 @@ import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; +import 'package:golub/src/presentation/features/auth/screens/auth_screen.dart'; +import 'package:golub/src/presentation/features/auth/screens/verification_screen.dart'; import 'package:golub/src/presentation/features/chats/screens/chats_screen.dart'; import 'package:golub/src/presentation/features/contacts/screens/contacts_screen.dart'; +import 'package:golub/src/presentation/features/onboarding/onboarding_profile_screen.dart'; import 'package:golub/src/presentation/features/profile/screens/profile_screen.dart'; import 'package:golub/src/presentation/features/splash/screens/splash_screen.dart'; import 'package:golub/src/presentation/navigation/app_navigation_shell.dart'; -import 'package:golub/src/presentation/navigation/app_routes.dart'; + +part 'app_routes.dart'; final _rootNavigatorKey = GlobalKey(); @@ -24,6 +28,27 @@ final routerConfig = GoRouter( return const SplashScreen(); }, ), + GoRoute( + name: AppRoutes.auth, + path: AppRoutes.getPath(AppRoutes.auth), + builder: (BuildContext context, GoRouterState state) { + return const AuthScreen(); + }, + ), + GoRoute( + name: AppRoutes.verification, + path: AppRoutes.getPath(AppRoutes.verification), + builder: (BuildContext context, GoRouterState state) { + return const VerificationScreen(); + }, + ), + GoRoute( + name: AppRoutes.onboardingProfile, + path: AppRoutes.getPath(AppRoutes.onboardingProfile), + builder: (BuildContext context, GoRouterState state) { + return const OnboardingProfileScreen(); + }, + ), StatefulShellRoute.indexedStack( builder: (BuildContext context, GoRouterState state, StatefulNavigationShell navigationShell) { diff --git a/lib/src/presentation/navigation/app_routes.dart b/lib/src/presentation/navigation/app_routes.dart index 3fde44b..02dceb4 100644 --- a/lib/src/presentation/navigation/app_routes.dart +++ b/lib/src/presentation/navigation/app_routes.dart @@ -1,9 +1,13 @@ -/// App routes +part of 'app_router.dart'; + class AppRoutes { static const String splash = 'splash'; + static const String auth = 'auth'; static const String contacts = 'contacts'; static const String chats = 'chats'; static const String profile = 'profile'; + static const String verification = 'verification'; + static const String onboardingProfile = 'onboardingProfile'; static String getPath(String routeName) => '/$routeName'; } diff --git a/lib/src/presentation/ui_kit/theme/app_assets.dart b/lib/src/presentation/ui_kit/theme/app_assets.dart index 108189d..e593517 100644 --- a/lib/src/presentation/ui_kit/theme/app_assets.dart +++ b/lib/src/presentation/ui_kit/theme/app_assets.dart @@ -2,9 +2,16 @@ class AppAssets { static const String _base = 'assets'; static const String _icons = '$_base/icons'; static const String _logo = '$_base/logo'; + static const String _images = '$_base/images'; static const String splashLogo = '$_logo/logo.svg'; static const String contactIcon = '$_icons/contacts.svg'; static const String chatIcon = '$_icons/chat.svg'; static const String profileIcon = '$_icons/profile.svg'; + static const String userProfileIcon = '$_icons/user_profile.svg'; + static const String cameraIcon = '$_icons/camera.svg'; + + static const String arrowBackIcon = '$_icons/arrow_back.svg'; + + static const String chatsIcon = '$_images/chats.svg'; } diff --git a/lib/src/presentation/ui_kit/theme/app_colors.dart b/lib/src/presentation/ui_kit/theme/app_colors.dart index 5caf679..f78a177 100644 --- a/lib/src/presentation/ui_kit/theme/app_colors.dart +++ b/lib/src/presentation/ui_kit/theme/app_colors.dart @@ -21,7 +21,12 @@ class AppColors { static const Color brightBlue = Color(0xFF3571CF); static const Color brightIndigo = Color(0xFF7F92DC); static const Color brightPurple = Color(0xFF9052DE); + static const Color brightLightPurple = Color(0xFF7F8DE9); static const Color mixBlueGray = Color(0xFFF3F2F8); static const Color mixBlueGray1 = Color(0xFFD6DAEB); + + static const Color gradientIndigo = Color(0xFF92A0DE); + static const Color gradientBlue = Color(0xFF2D6DCD); + static const Color gradientWhite = Color(0XFFE0EDFF); } diff --git a/lib/src/presentation/ui_kit/theme/app_sizes.dart b/lib/src/presentation/ui_kit/theme/app_sizes.dart index 242ee73..8614d9b 100644 --- a/lib/src/presentation/ui_kit/theme/app_sizes.dart +++ b/lib/src/presentation/ui_kit/theme/app_sizes.dart @@ -4,4 +4,8 @@ class AppSizes { static double bottomNavigationBarItemSize = 32.0; static double bottomNavigationBarActiveItemSize = 36.0; static double bottomNavigationBarItemContainerSize = 72.0; + + /// Elevated button + static double elevatedButtonHeight = 48.0; + static double elevatedButtonBorderRadius = 16.0; } diff --git a/lib/src/presentation/ui_kit/theme/app_styles.dart b/lib/src/presentation/ui_kit/theme/app_styles.dart index 56d830e..68253f0 100644 --- a/lib/src/presentation/ui_kit/theme/app_styles.dart +++ b/lib/src/presentation/ui_kit/theme/app_styles.dart @@ -23,6 +23,7 @@ class AppTextStyles { static final TextStyle titleLarge = _ubuntu.copyWith( fontSize: 28.0, fontWeight: FontWeight.w500, + height: 1.25, ); static final TextStyle displaySmall = _ubuntu.copyWith( fontSize: 11.0, @@ -31,6 +32,7 @@ class AppTextStyles { static final TextStyle displayMedium = _roboto.copyWith( fontSize: 12.0, fontWeight: FontWeight.w400, + height: 1.25, ); static final TextStyle bodyMedium = _roboto.copyWith( fontSize: 17.0, @@ -39,6 +41,7 @@ class AppTextStyles { static final TextStyle bodySmall = _roboto.copyWith( fontSize: 15.0, fontWeight: FontWeight.w400, + height: 1.33, ); } @@ -50,4 +53,22 @@ class AppStyles { ], radius: 1.5, ); -} \ No newline at end of file + + static const LinearGradient bluePurpleGradient = LinearGradient( + begin: Alignment.centerLeft, + end: Alignment.centerRight, + colors: [ + AppColors.gradientIndigo, + AppColors.gradientBlue, + ], + ); + + static const LinearGradient blueWhiteGradient = LinearGradient( + begin: Alignment.centerLeft, + end: Alignment.bottomRight, + colors: [ + AppColors.gradientWhite, + AppColors.baseWhite, + ], + ); +} diff --git a/lib/src/presentation/ui_kit/theme/themes/dark_theme.dart b/lib/src/presentation/ui_kit/theme/themes/dark_theme.dart index d262d0c..66ccf1d 100644 --- a/lib/src/presentation/ui_kit/theme/themes/dark_theme.dart +++ b/lib/src/presentation/ui_kit/theme/themes/dark_theme.dart @@ -1,8 +1,13 @@ import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import 'package:golub/src/presentation/ui_kit/theme/app_colors.dart'; import 'package:golub/src/presentation/ui_kit/theme/app_styles.dart'; final darkTheme = ThemeData( + appBarTheme: const AppBarTheme( + systemOverlayStyle: SystemUiOverlayStyle.light, + ), + scaffoldBackgroundColor: Colors.white, textTheme: TextTheme( titleLarge: AppTextStyles.titleLarge.copyWith(color: AppColors.baseWhite), titleMedium: AppTextStyles.titleMedium.copyWith(color: AppColors.baseWhite), diff --git a/lib/src/presentation/ui_kit/theme/themes/light_theme.dart b/lib/src/presentation/ui_kit/theme/themes/light_theme.dart index bb727df..221ec71 100644 --- a/lib/src/presentation/ui_kit/theme/themes/light_theme.dart +++ b/lib/src/presentation/ui_kit/theme/themes/light_theme.dart @@ -1,15 +1,56 @@ import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:golub/src/presentation/ui_kit/theme/app_colors.dart'; import 'package:golub/src/presentation/ui_kit/theme/app_styles.dart'; final lightTheme = ThemeData( + scaffoldBackgroundColor: Colors.white, + appBarTheme: const AppBarTheme( + systemOverlayStyle: SystemUiOverlayStyle.light, + ), textTheme: TextTheme( - titleLarge: AppTextStyles.titleLarge, + titleLarge: AppTextStyles.titleLarge.copyWith( + color: AppColors.baseBlack, + ), titleMedium: AppTextStyles.titleMedium, titleSmall: AppTextStyles.titleSmall, displayLarge: AppTextStyles.displayLarge, displayMedium: AppTextStyles.displayMedium, displaySmall: AppTextStyles.displaySmall, - bodyMedium: AppTextStyles.bodyMedium, - bodySmall: AppTextStyles.bodySmall, + bodyMedium: AppTextStyles.bodyMedium.copyWith( + color: AppColors.baseGray6, + ), + bodySmall: AppTextStyles.bodySmall.copyWith( + color: AppColors.baseBlack, + ), + ), + inputDecorationTheme: InputDecorationTheme( + labelStyle: AppTextStyles.bodyMedium.copyWith( + color: AppColors.baseGray5, + ), + floatingLabelStyle: AppTextStyles.bodyMedium.copyWith( + color: AppColors.baseGray5, + ), + floatingLabelAlignment: FloatingLabelAlignment.start, + helperStyle: AppTextStyles.displaySmall.copyWith( + color: AppColors.brightRed, + ), + fillColor: AppColors.baseWhite, + filled: true, + focusedBorder: OutlineInputBorder( + borderSide: const BorderSide(color: AppColors.baseWhite), + borderRadius: BorderRadius.circular(16.0), + ), + enabledBorder: OutlineInputBorder( + borderSide: const BorderSide(color: AppColors.baseWhite), + borderRadius: BorderRadius.circular(16.0), + ), + disabledBorder: OutlineInputBorder( + borderSide: const BorderSide(color: AppColors.baseWhite), + borderRadius: BorderRadius.circular(16.0), + ), + ), + progressIndicatorTheme: const ProgressIndicatorThemeData( + color: AppColors.baseWhite, ), ); diff --git a/lib/src/presentation/ui_kit/ui.dart b/lib/src/presentation/ui_kit/ui.dart index 399ba2b..50cf4b9 100644 --- a/lib/src/presentation/ui_kit/ui.dart +++ b/lib/src/presentation/ui_kit/ui.dart @@ -1,4 +1,5 @@ library ui_kit; export 'theme/app_assets.dart'; -export 'theme/app_sizes.dart'; \ No newline at end of file +export 'theme/app_sizes.dart'; +export 'widgets/buttons/gbutton.dart'; \ No newline at end of file diff --git a/lib/src/presentation/ui_kit/widgets/buttons/gbutton.dart b/lib/src/presentation/ui_kit/widgets/buttons/gbutton.dart new file mode 100644 index 0000000..0acefc9 --- /dev/null +++ b/lib/src/presentation/ui_kit/widgets/buttons/gbutton.dart @@ -0,0 +1,68 @@ +import 'package:flutter/material.dart'; +import 'package:golub/src/presentation/ui_kit/theme/app_colors.dart'; +import 'package:golub/src/presentation/ui_kit/theme/app_sizes.dart'; +import 'package:golub/src/presentation/ui_kit/theme/app_styles.dart'; + +class GButton extends StatelessWidget { + final double? width; + final double? height; + final double? borderRadius; + final VoidCallback onPressed; + final String buttonLabel; + final bool isLoading; + final bool isDisabled; + + const GButton({ + required this.onPressed, + this.width, + this.height, + this.borderRadius, + this.buttonLabel = '', + this.isLoading = false, + this.isDisabled = false, + super.key + }); + + @override + Widget build(BuildContext context) { + return Container( + constraints: BoxConstraints( + minWidth: width ?? double.infinity, + minHeight: height ?? AppSizes.elevatedButtonHeight, + ), + decoration: BoxDecoration( + gradient: !isDisabled ? AppStyles.bluePurpleGradient : null, + color: isDisabled ? AppColors.baseGray3 : null, + borderRadius: BorderRadius.circular( + borderRadius ?? AppSizes.elevatedButtonBorderRadius + ), + ), + child: ElevatedButton( + onPressed: !isDisabled ? onPressed : null, + style: ButtonStyle( + backgroundColor: MaterialStateProperty.all(Colors.transparent), + shadowColor: MaterialStateProperty.all(Colors.transparent), + shape: MaterialStateProperty.all( + RoundedRectangleBorder( + borderRadius: BorderRadius.circular( + borderRadius ?? AppSizes.elevatedButtonBorderRadius + ), + ), + ), + ), + child: isLoading ? Center( + child: CircularProgressIndicator( + color: Theme.of(context).progressIndicatorTheme.color, + strokeWidth: 2.0, + ), + ) : + Text( + buttonLabel, + style: Theme.of(context).textTheme.titleSmall?.copyWith( + color: isDisabled ? AppColors.baseGray4 : AppColors.baseWhite, + ), + ), + ), + ); + } +} diff --git a/lib/src/presentation/ui_kit/widgets/input/gtext_field.dart b/lib/src/presentation/ui_kit/widgets/input/gtext_field.dart new file mode 100644 index 0000000..149e0eb --- /dev/null +++ b/lib/src/presentation/ui_kit/widgets/input/gtext_field.dart @@ -0,0 +1,80 @@ +import 'package:flutter/material.dart'; +import 'package:golub/src/presentation/ui_kit/theme/app_colors.dart'; + +class GTextField extends StatelessWidget { + final String hintText; + final String? error; + final TextEditingController? textEditingController; + final FocusNode? focusNode; + final VoidCallback? onEditingComplete; + final Color? fillColor; + + const GTextField({ + this.textEditingController, + this.focusNode, + this.onEditingComplete, + this.hintText = '', + this.error, + this.fillColor, + super.key + }); + + @override + Widget build(BuildContext context) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + constraints: const BoxConstraints( + minWidth: double.infinity, + minHeight: 68.0, + ), + decoration: BoxDecoration( + borderRadius: const BorderRadius.all(Radius.circular(16.0)), + color: fillColor ?? AppColors.baseWhite, + ), + alignment: Alignment.center, + child: TextField( + controller: textEditingController, + focusNode: focusNode, + onEditingComplete: onEditingComplete, + style: Theme.of(context).textTheme.bodyMedium?.copyWith( + color: Theme.of(context).brightness == Brightness.light ? + AppColors.baseBlack : AppColors.baseWhite, + ), + decoration: InputDecoration( + contentPadding: const EdgeInsets.symmetric(horizontal: 16.0), + labelText: hintText, + fillColor: fillColor, + filled: true, + focusedBorder: OutlineInputBorder( + borderSide: BorderSide(color: fillColor ?? AppColors.baseWhite), + borderRadius: BorderRadius.circular(16.0), + ), + enabledBorder: OutlineInputBorder( + borderSide: BorderSide(color: fillColor ?? AppColors.baseWhite), + borderRadius: BorderRadius.circular(16.0), + ), + disabledBorder: OutlineInputBorder( + borderSide: BorderSide(color: fillColor ?? AppColors.baseWhite), + borderRadius: BorderRadius.circular(16.0), + ), + ), + ), + ), + Padding( + padding: const EdgeInsets.only( + top: 8.0, + left: 12.0, + ), + child: Text( + error ?? '', + style: Theme.of(context).textTheme.displaySmall?.copyWith( + color: error == null ? Colors.transparent : AppColors.brightRed, + ), + ), + ), + ], + ); + } +} diff --git a/lib/src/presentation/ui_kit/widgets/input/profile_upload_image.dart b/lib/src/presentation/ui_kit/widgets/input/profile_upload_image.dart new file mode 100644 index 0000000..774b7a9 --- /dev/null +++ b/lib/src/presentation/ui_kit/widgets/input/profile_upload_image.dart @@ -0,0 +1,61 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_svg/svg.dart'; +import 'package:golub/src/presentation/ui_kit/theme/app_assets.dart'; +import 'package:golub/src/presentation/ui_kit/theme/app_colors.dart'; + +class ProfileUploadImage extends StatelessWidget { + final EdgeInsets padding; + + const ProfileUploadImage({ + this.padding = const EdgeInsets.all(16.0), + super.key + }); + + @override + Widget build(BuildContext context) { + return Padding( + padding: padding, + child: Stack( + alignment: Alignment.center, + clipBehavior: Clip.none, + children: [ + Container( + width: 144.0, + height: 144.0, + decoration: BoxDecoration( + color: AppColors.baseGray2, + borderRadius: BorderRadius.circular(16.0), + ), + child: Center( + child: SvgPicture.asset( + AppAssets.userProfileIcon, + fit: BoxFit.cover, + width: 76.0, + height: 84.0, + ), + ), + ), + Positioned( + bottom: -16.0, + child: Container( + width: 32.0, + height: 32.0, + decoration: BoxDecoration( + color: AppColors.baseWhite, + borderRadius: BorderRadius.circular(8.0), + ), + child: Center( + child: SvgPicture.asset( + AppAssets.cameraIcon, + fit: BoxFit.cover, + width: 16.0, + height: 16.0, + ), + ), + ), + ), + ], + ), + ); + } +} diff --git a/lib/src/presentation/ui_kit/widgets/widgets.dart b/lib/src/presentation/ui_kit/widgets/widgets.dart new file mode 100644 index 0000000..1f6b86c --- /dev/null +++ b/lib/src/presentation/ui_kit/widgets/widgets.dart @@ -0,0 +1 @@ +export 'buttons/gbutton.dart'; \ No newline at end of file diff --git a/lib/src/presentation/utils/link_contants.dart b/lib/src/presentation/utils/link_contants.dart new file mode 100644 index 0000000..71378ac --- /dev/null +++ b/lib/src/presentation/utils/link_contants.dart @@ -0,0 +1,5 @@ +class LinkConstants { + static const String _web = 'https:'; + static const String termAndCondition = '$_web//doc-hosting.flycricket.io/golub-terms-of-use/c3b635a9-679b-43a0-8d60-f209ba2d5a8c/terms'; + static const String privacy = '$_web//doc-hosting.flycricket.io/golub-privacy-policy/7a3f0325-e980-4da9-a87b-5303de81861e/privacy'; +} diff --git a/lib/src/presentation/utils/link_launcher.dart b/lib/src/presentation/utils/link_launcher.dart new file mode 100644 index 0000000..6784904 --- /dev/null +++ b/lib/src/presentation/utils/link_launcher.dart @@ -0,0 +1,8 @@ +import 'package:url_launcher/url_launcher.dart'; + +Future launchLink(String link) async { + final Uri url = Uri.parse(link); + if (!await launchUrl(url)) { + throw Exception('Could not launch $url'); + } +} diff --git a/linux/flutter/generated_plugin_registrant.cc b/linux/flutter/generated_plugin_registrant.cc index e71a16d..f6f23bf 100644 --- a/linux/flutter/generated_plugin_registrant.cc +++ b/linux/flutter/generated_plugin_registrant.cc @@ -6,6 +6,10 @@ #include "generated_plugin_registrant.h" +#include void fl_register_plugins(FlPluginRegistry* registry) { + g_autoptr(FlPluginRegistrar) url_launcher_linux_registrar = + fl_plugin_registry_get_registrar_for_plugin(registry, "UrlLauncherPlugin"); + url_launcher_plugin_register_with_registrar(url_launcher_linux_registrar); } diff --git a/linux/flutter/generated_plugins.cmake b/linux/flutter/generated_plugins.cmake index 2e1de87..f16b4c3 100644 --- a/linux/flutter/generated_plugins.cmake +++ b/linux/flutter/generated_plugins.cmake @@ -3,6 +3,7 @@ # list(APPEND FLUTTER_PLUGIN_LIST + url_launcher_linux ) list(APPEND FLUTTER_FFI_PLUGIN_LIST diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index cccf817..8236f57 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -5,6 +5,8 @@ import FlutterMacOS import Foundation +import url_launcher_macos func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { + UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin")) } diff --git a/pubspec.lock b/pubspec.lock index 0ed7ea0..a9aad08 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -21,10 +21,10 @@ packages: dependency: transitive description: name: archive - sha256: "7e0d52067d05f2e0324268097ba723b71cb41ac8a6a2b24d1edf9c536b987b03" + sha256: "22600aa1e926be775fa5fe7e6894e7fb3df9efda8891c73f70fb3262399a432d" url: "https://pub.dev" source: hosted - version: "3.4.6" + version: "3.4.10" args: dependency: transitive description: @@ -41,6 +41,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.11.0" + bloc: + dependency: transitive + description: + name: bloc + sha256: "3820f15f502372d979121de1f6b97bfcf1630ebff8fe1d52fb2b0bfa49be5b49" + url: "https://pub.dev" + source: hosted + version: "8.1.2" boolean_selector: dependency: transitive description: @@ -69,18 +77,18 @@ packages: dependency: transitive description: name: build_daemon - sha256: "5f02d73eb2ba16483e693f80bee4f088563a820e47d1027d4cdfe62b5bb43e65" + sha256: "0343061a33da9c5810b2d6cee51945127d8f4c060b7fbdd9d54917f0a3feaaa1" url: "https://pub.dev" source: hosted - version: "4.0.0" + version: "4.0.1" build_resolvers: dependency: transitive description: name: build_resolvers - sha256: "64e12b0521812d1684b1917bc80945625391cb9bdd4312536b1d69dcb6133ed8" + sha256: "339086358431fa15d7eca8b6a36e5d783728cf025e559b834f4609a1fcfb7b0a" url: "https://pub.dev" source: hosted - version: "2.4.1" + version: "2.4.2" build_runner: dependency: "direct dev" description: @@ -93,10 +101,10 @@ packages: dependency: transitive description: name: build_runner_core - sha256: c9e32d21dd6626b5c163d48b037ce906bbe428bc23ab77bcd77bb21e593b6185 + sha256: "4ae8ffe5ac758da294ecf1802f2aff01558d8b1b00616aa7538ea9a8a5d50799" url: "https://pub.dev" source: hosted - version: "7.2.11" + version: "7.3.0" built_collection: dependency: transitive description: @@ -109,10 +117,10 @@ packages: dependency: transitive description: name: built_value - sha256: "723b4021e903217dfc445ec4cf5b42e27975aece1fc4ebbc1ca6329c2d9fb54e" + sha256: a3ec2e0f967bc47f69f95009bb93db936288d61d5343b9436e378b28a2f830c6 url: "https://pub.dev" source: hosted - version: "8.7.0" + version: "8.9.0" characters: dependency: transitive description: @@ -129,6 +137,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.0.3" + cli_util: + dependency: transitive + description: + name: cli_util + sha256: c05b7406fdabc7a49a3929d4af76bcaccbbffcbcdcf185b082e1ae07da323d19 + url: "https://pub.dev" + source: hosted + version: "0.4.1" clock: dependency: transitive description: @@ -141,18 +157,18 @@ packages: dependency: transitive description: name: code_builder - sha256: "1be9be30396d7e4c0db42c35ea6ccd7cc6a1e19916b5dc64d6ac216b5544d677" + sha256: f692079e25e7869c14132d39f223f8eec9830eb76131925143b2129c4bb01b37 url: "https://pub.dev" source: hosted - version: "4.7.0" + version: "4.10.0" collection: dependency: transitive description: name: collection - sha256: f092b211a4319e98e5ff58223576de6c2803db36221657b46c82574721240687 + sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a url: "https://pub.dev" source: hosted - version: "1.17.2" + version: "1.18.0" convert: dependency: transitive description: @@ -169,6 +185,14 @@ packages: url: "https://pub.dev" source: hosted version: "3.0.3" + csv: + dependency: transitive + description: + name: csv + sha256: "63ed2871dd6471193dffc52c0e6c76fb86269c00244d244297abbb355c84a86e" + url: "https://pub.dev" + source: hosted + version: "5.1.1" cupertino_icons: dependency: "direct main" description: @@ -185,6 +209,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.3.2" + dio: + dependency: "direct main" + description: + name: dio + sha256: "01870acd87986f768e0c09cc4d7a19a59d814af7b34cbeb0b437d2c33bdfea4c" + url: "https://pub.dev" + source: hosted + version: "5.3.4" fake_async: dependency: transitive description: @@ -214,6 +246,30 @@ packages: description: flutter source: sdk version: "0.0.0" + flutter_bloc: + dependency: "direct main" + description: + name: flutter_bloc + sha256: e74efb89ee6945bcbce74a5b3a5a3376b088e5f21f55c263fc38cbdc6237faae + url: "https://pub.dev" + source: hosted + version: "8.1.3" + flutter_dotenv: + dependency: "direct main" + description: + name: flutter_dotenv + sha256: "9357883bdd153ab78cbf9ffa07656e336b8bbb2b5a3ca596b0b27e119f7c7d77" + url: "https://pub.dev" + source: hosted + version: "5.1.0" + flutter_launcher_icons: + dependency: "direct dev" + description: + name: flutter_launcher_icons + sha256: "526faf84284b86a4cb36d20a5e45147747b7563d921373d4ee0559c54fcdbcea" + url: "https://pub.dev" + source: hosted + version: "0.13.1" flutter_lints: dependency: "direct dev" description: @@ -254,7 +310,7 @@ packages: source: hosted version: "2.4.5" freezed_annotation: - dependency: transitive + dependency: "direct main" description: name: freezed_annotation sha256: c3fd9336eb55a38cc1bbd79ab17573113a8deccd0ecbbf926cca3c62803b5c2d @@ -269,6 +325,14 @@ packages: url: "https://pub.dev" source: hosted version: "3.2.0" + get_it: + dependency: "direct main" + description: + name: get_it + sha256: f79870884de16d689cf9a7d15eedf31ed61d750e813c538a6efb92660fea83c3 + url: "https://pub.dev" + source: hosted + version: "7.6.4" glob: dependency: transitive description: @@ -297,10 +361,10 @@ packages: dependency: transitive description: name: http - sha256: "759d1a329847dd0f39226c688d3e06a6b8679668e350e2891a6474f8b4bb8525" + sha256: a2bbf9d017fcced29139daa8ed2bba4ece450ab222871df93ca9eec6f80c34ba url: "https://pub.dev" source: hosted - version: "1.1.0" + version: "1.2.0" http_multi_server: dependency: transitive description: @@ -317,6 +381,30 @@ packages: url: "https://pub.dev" source: hosted version: "4.0.2" + image: + dependency: transitive + description: + name: image + sha256: "49a0d4b0c12402853d3f227fe7c315601b238d126aa4caa5dbb2dcf99421aa4a" + url: "https://pub.dev" + source: hosted + version: "4.1.6" + injectable: + dependency: "direct main" + description: + name: injectable + sha256: cd3c422e13270c81f64ab73c80406b2b2ed563fe59d0ff2093eb7eee63d0bbeb + url: "https://pub.dev" + source: hosted + version: "2.3.2" + injectable_generator: + dependency: "direct dev" + description: + name: injectable_generator + sha256: f9d3c05f0938403f79ad6c6d23ec8e37a7a05ad980b1bf9399493f3e41845788 + url: "https://pub.dev" + source: hosted + version: "2.4.1" intl: dependency: "direct main" description: @@ -349,6 +437,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.6.7" + json2yaml: + dependency: transitive + description: + name: json2yaml + sha256: da94630fbc56079426fdd167ae58373286f603371075b69bf46d848d63ba3e51 + url: "https://pub.dev" + source: hosted + version: "3.0.1" json_annotation: dependency: transitive description: @@ -393,18 +489,26 @@ packages: dependency: transitive description: name: meta - sha256: "3c74dbf8763d36539f114c799d8a2d87343b5067e9d796ca22b5eb8437090ee3" + sha256: a6e590c838b18133bb482a2745ad77c5bb7715fb0451209e1a7567d416678b8e url: "https://pub.dev" source: hosted - version: "1.9.1" + version: "1.10.0" mime: dependency: transitive description: name: mime - sha256: e4ff8e8564c03f255408decd16e7899da1733852a9110a58fe6d1b817684a63e + sha256: "2e123074287cc9fd6c09de8336dae606d1ddb88d9ac47358826db698c176a1f2" url: "https://pub.dev" source: hosted - version: "1.0.4" + version: "1.0.5" + nested: + dependency: transitive + description: + name: nested + sha256: "03bac4c528c64c95c722ec99280375a6f2fc708eec17c7b3f07253b626cd2a20" + url: "https://pub.dev" + source: hosted + version: "1.0.0" package_config: dependency: transitive description: @@ -433,18 +537,26 @@ packages: dependency: transitive description: name: petitparser - sha256: cb3798bef7fc021ac45b308f4b51208a152792445cce0448c9a4ba5879dd8750 + sha256: c15605cd28af66339f8eb6fbe0e541bfe2d1b72d5825efc6598f3e0a31b9ad27 + url: "https://pub.dev" + source: hosted + version: "6.0.2" + plugin_platform_interface: + dependency: transitive + description: + name: plugin_platform_interface + sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02" url: "https://pub.dev" source: hosted - version: "5.4.0" + version: "2.1.8" pointycastle: dependency: transitive description: name: pointycastle - sha256: "7c1e5f0d23c9016c5bbd8b1473d0d3fb3fc851b876046039509e18e0c7485f2c" + sha256: "43ac87de6e10afabc85c445745a7b799e04de84cebaa4fd7bf55a5e1e9604d29" url: "https://pub.dev" source: hosted - version: "3.7.3" + version: "3.7.4" pool: dependency: transitive description: @@ -453,6 +565,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.5.1" + provider: + dependency: transitive + description: + name: provider + sha256: "9a96a0a19b594dbc5bf0f1f27d2bc67d5f95957359b461cd9feb44ed6ae75096" + url: "https://pub.dev" + source: hosted + version: "6.1.1" pub_semver: dependency: transitive description: @@ -469,6 +589,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.2.3" + recase: + dependency: transitive + description: + name: recase + sha256: e4eb4ec2dcdee52dcf99cb4ceabaffc631d7424ee55e56f280bc039737f89213 + url: "https://pub.dev" + source: hosted + version: "4.1.0" shelf: dependency: transitive description: @@ -490,14 +618,38 @@ packages: description: flutter source: sdk version: "0.0.99" + slang: + dependency: "direct main" + description: + name: slang + sha256: "77fd99f7b0da15e671ef0289b24a0a63e74f693c58a0ca54111388e4c0ddb1dd" + url: "https://pub.dev" + source: hosted + version: "3.28.0" + slang_build_runner: + dependency: "direct dev" + description: + name: slang_build_runner + sha256: "387a3d569da4490b1fffbf31f203021fbfd34f15228d83e14a0f40bc940966fa" + url: "https://pub.dev" + source: hosted + version: "3.28.0" + slang_flutter: + dependency: "direct main" + description: + name: slang_flutter + sha256: "57817bb15553bb5df37aed3bac497286bdd8c2eab6763f4de6815efe2c0becee" + url: "https://pub.dev" + source: hosted + version: "3.28.0" source_gen: dependency: transitive description: name: source_gen - sha256: fc0da689e5302edb6177fdd964efcb7f58912f43c28c2047a808f5bfff643d16 + sha256: "14658ba5f669685cd3d63701d01b31ea748310f7ab854e471962670abcf57832" url: "https://pub.dev" source: hosted - version: "1.4.0" + version: "1.5.0" source_span: dependency: transitive description: @@ -510,18 +662,18 @@ packages: dependency: transitive description: name: stack_trace - sha256: c3c7d8edb15bee7f0f74debd4b9c5f3c2ea86766fe4178eb2a18eb30a0bdaed5 + sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b" url: "https://pub.dev" source: hosted - version: "1.11.0" + version: "1.11.1" stream_channel: dependency: transitive description: name: stream_channel - sha256: "83615bee9045c1d322bbbd1ba209b7a749c2cbcdcb3fdd1df8eb488b3279c1c8" + sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7 url: "https://pub.dev" source: hosted - version: "2.1.1" + version: "2.1.2" stream_transform: dependency: transitive description: @@ -550,10 +702,10 @@ packages: dependency: transitive description: name: test_api - sha256: "75760ffd7786fffdfb9597c35c5b27eaeec82be8edfb6d71d32651128ed7aab8" + sha256: "5c2f730018264d276c20e4f1503fd1308dfbbae39ec8ee63c5236311ac06954b" url: "https://pub.dev" source: hosted - version: "0.6.0" + version: "0.6.1" timing: dependency: transitive description: @@ -570,30 +722,94 @@ packages: url: "https://pub.dev" source: hosted version: "1.3.2" + url_launcher: + dependency: "direct main" + description: + name: url_launcher + sha256: b1c9e98774adf8820c96fbc7ae3601231d324a7d5ebd8babe27b6dfac91357ba + url: "https://pub.dev" + source: hosted + version: "6.2.1" + url_launcher_android: + dependency: transitive + description: + name: url_launcher_android + sha256: "507dc655b1d9cb5ebc756032eb785f114e415f91557b73bf60b7e201dfedeb2f" + url: "https://pub.dev" + source: hosted + version: "6.2.2" + url_launcher_ios: + dependency: transitive + description: + name: url_launcher_ios + sha256: "75bb6fe3f60070407704282a2d295630cab232991eb52542b18347a8a941df03" + url: "https://pub.dev" + source: hosted + version: "6.2.4" + url_launcher_linux: + dependency: transitive + description: + name: url_launcher_linux + sha256: ab360eb661f8879369acac07b6bb3ff09d9471155357da8443fd5d3cf7363811 + url: "https://pub.dev" + source: hosted + version: "3.1.1" + url_launcher_macos: + dependency: transitive + description: + name: url_launcher_macos + sha256: b7244901ea3cf489c5335bdacda07264a6e960b1c1b1a9f91e4bc371d9e68234 + url: "https://pub.dev" + source: hosted + version: "3.1.0" + url_launcher_platform_interface: + dependency: transitive + description: + name: url_launcher_platform_interface + sha256: a932c3a8082e118f80a475ce692fde89dc20fddb24c57360b96bc56f7035de1f + url: "https://pub.dev" + source: hosted + version: "2.3.1" + url_launcher_web: + dependency: transitive + description: + name: url_launcher_web + sha256: fff0932192afeedf63cdd50ecbb1bc825d31aed259f02bb8dba0f3b729a5e88b + url: "https://pub.dev" + source: hosted + version: "2.2.3" + url_launcher_windows: + dependency: transitive + description: + name: url_launcher_windows + sha256: ecf9725510600aa2bb6d7ddabe16357691b6d2805f66216a97d1b881e21beff7 + url: "https://pub.dev" + source: hosted + version: "3.1.1" vector_graphics: dependency: transitive description: name: vector_graphics - sha256: "0f0c746dd2d6254a0057218ff980fc7f5670fd0fcf5e4db38a490d31eed4ad43" + sha256: "18f6690295af52d081f6808f2f7c69f0eed6d7e23a71539d75f4aeb8f0062172" url: "https://pub.dev" source: hosted - version: "1.1.9+1" + version: "1.1.9+2" vector_graphics_codec: dependency: transitive description: name: vector_graphics_codec - sha256: "0edf6d630d1bfd5589114138ed8fada3234deacc37966bec033d3047c29248b7" + sha256: "531d20465c10dfac7f5cd90b60bbe4dd9921f1ec4ca54c83ebb176dbacb7bb2d" url: "https://pub.dev" source: hosted - version: "1.1.9+1" + version: "1.1.9+2" vector_graphics_compiler: dependency: transitive description: name: vector_graphics_compiler - sha256: d24333727332d9bd20990f1483af4e09abdb9b1fc7c3db940b56ab5c42790c26 + sha256: "03012b0a33775c5530576b70240308080e1d5050f0faf000118c20e6463bc0ad" url: "https://pub.dev" source: hosted - version: "1.1.9+1" + version: "1.1.9+2" vector_math: dependency: transitive description: @@ -614,10 +830,10 @@ packages: dependency: transitive description: name: web - sha256: dc8ccd225a2005c1be616fe02951e2e342092edf968cf0844220383757ef8f10 + sha256: afe077240a270dcfd2aafe77602b4113645af95d0ad31128cc02bce5ac5d5152 url: "https://pub.dev" source: hosted - version: "0.1.4-beta" + version: "0.3.0" web_socket_channel: dependency: transitive description: @@ -630,10 +846,10 @@ packages: dependency: transitive description: name: xml - sha256: "5bc72e1e45e941d825fd7468b9b4cc3b9327942649aeb6fc5cdbf135f0a86e84" + sha256: b015a8ad1c488f66851d762d3090a21c600e479dc75e68328c52774040cf9226 url: "https://pub.dev" source: hosted - version: "6.3.0" + version: "6.5.0" yaml: dependency: transitive description: @@ -643,5 +859,5 @@ packages: source: hosted version: "3.1.2" sdks: - dart: ">=3.1.3 <4.0.0" - flutter: ">=3.7.0" + dart: ">=3.2.0 <4.0.0" + flutter: ">=3.16.0" diff --git a/pubspec.yaml b/pubspec.yaml index 859385b..df9db7b 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -18,7 +18,15 @@ dependencies: flutter_svg: 2.0.9 intl: 0.18.1 freezed: 2.4.5 - + freezed_annotation: 2.4.1 + flutter_bloc: 8.1.3 + url_launcher: 6.2.1 + dio: 5.3.4 + flutter_dotenv: 5.1.0 + injectable: 2.3.2 + get_it: 7.6.4 + slang: 3.28.0 + slang_flutter: 3.28.0 dev_dependencies: flutter_test: @@ -26,9 +34,9 @@ dev_dependencies: flutter_lints: 2.0.0 intl_utils: 2.8.5 build_runner: 2.4.6 - -flutter_intl: - enabled: true + injectable_generator: 2.4.1 + slang_build_runner: 3.28.0 + flutter_launcher_icons: 0.13.1 flutter: uses-material-design: true @@ -36,6 +44,7 @@ flutter: assets: - assets/icons/ - assets/logo/ + - assets/images/ fonts: - family: Ubuntu @@ -47,3 +56,8 @@ flutter: fonts: - asset: assets/fonts/roboto/roboto-regular.ttf +flutter_launcher_icons: + android: "launcher_icon" + ios: true + image_path: "assets/logo/logo.png" + remove_alpha_ios: true \ No newline at end of file diff --git a/test/widget_test.dart b/test/widget_test.dart index 2a2b819..4647042 100644 --- a/test/widget_test.dart +++ b/test/widget_test.dart @@ -5,4 +5,6 @@ // gestures. You can also use WidgetTester to find child widgets in the widget // tree, read text, and verify that the values of widget properties are correct. -void main() {} +void main() { + +} diff --git a/windows/flutter/generated_plugin_registrant.cc b/windows/flutter/generated_plugin_registrant.cc index 8b6d468..4f78848 100644 --- a/windows/flutter/generated_plugin_registrant.cc +++ b/windows/flutter/generated_plugin_registrant.cc @@ -6,6 +6,9 @@ #include "generated_plugin_registrant.h" +#include void RegisterPlugins(flutter::PluginRegistry* registry) { + UrlLauncherWindowsRegisterWithRegistrar( + registry->GetRegistrarForPlugin("UrlLauncherWindows")); } diff --git a/windows/flutter/generated_plugins.cmake b/windows/flutter/generated_plugins.cmake index b93c4c3..88b22e5 100644 --- a/windows/flutter/generated_plugins.cmake +++ b/windows/flutter/generated_plugins.cmake @@ -3,6 +3,7 @@ # list(APPEND FLUTTER_PLUGIN_LIST + url_launcher_windows ) list(APPEND FLUTTER_FFI_PLUGIN_LIST