diff --git a/.github/workflows/android_release_staging.yml b/.github/workflows/android_release_staging.yml
new file mode 100644
index 0000000..a1bd77f
--- /dev/null
+++ b/.github/workflows/android_release_staging.yml
@@ -0,0 +1,41 @@
+# This is a lint and test workflow for Flutter CI
+
+name: Relase (staging)
+
+on:
+ push:
+ branches:
+ - release/android-*
+ pull_request:
+ branches:
+ - release/android-*
+jobs:
+ deploy:
+ name: Build android
+ runs-on: ubuntu-latest
+ steps:
+ # Setup Java environment in order to build the Android app.
+ - uses: ruby/setup-ruby@v1.71.0
+ with:
+ ruby-version: '2.7.2'
+ - uses: actions/checkout@v2.3.2
+ - uses: actions/setup-java@v1
+ with:
+ java-version: '12.x'
+
+ # Setup the Flutter environment.
+ - uses: subosito/flutter-action@v1
+ with:
+ channel: 'stable' # 'dev', 'alpha', default to: 'stable'
+
+ - name: Get Flutter dependencies.
+ run: flutter pub get
+
+ - name: Build app
+ run: flutter build apk --flavor staging -t lib/main-staging.dart --debug
+
+ - name: Upload to firebase
+ uses: maierj/fastlane-action@v2.0.1
+ with:
+ lane: "android upload_firebase"
+ options: '{"token": "${{secrets.FIREBASE_TOKEN}}", "app_id": "${{secrets.FIREBASE_ANDROID_APP_ID}}", "apk_path": "build/app/outputs/flutter-apk/app-staging-debug.apk"}'
diff --git a/.github/workflows/ios_release_staging.yml b/.github/workflows/ios_release_staging.yml
new file mode 100644
index 0000000..5f22a16
--- /dev/null
+++ b/.github/workflows/ios_release_staging.yml
@@ -0,0 +1,67 @@
+# This is a lint and test workflow for Flutter CI
+
+name: Relase (staging)
+
+on:
+ push:
+ branches:
+ - release/ios-*
+ pull_request:
+ branches:
+ - release/ios-*
+jobs:
+ deploy:
+ name: Build iOS
+ runs-on: macOS-latest
+ steps:
+ # Setup environment
+ - uses: ruby/setup-ruby@v1.71.0
+ with:
+ ruby-version: '2.7.2'
+ - uses: actions/checkout@v2.3.2
+
+ # Setup the Flutter environment.
+ - uses: subosito/flutter-action@v1
+ with:
+ channel: 'stable' # 'dev', 'alpha', default to: 'stable'
+
+ - name: Get Flutter dependencies.
+ run: flutter pub get
+
+ - name: Build app
+ run: flutter build ios --flavor staging -t lib/main-staging.dart --no-codesign
+
+ - name: Install the Apple certificate and provisioning profile
+ env:
+ BUILD_CERTIFICATE_BASE64: ${{ secrets.DEVELOPMENT_CERTIFICATE_DATA }}
+ P12_PASSWORD: ${{ secrets.DEVELOPMENT_CERTIFICATE_PASSPHRASE }}
+ BUILD_PROVISION_PROFILE_BASE64: ${{ secrets.BUILD_PROVISION_PROFILE }}
+ KEYCHAIN_PASSWORD: ${{ secrets.KEYCHAIN_PASSWORD }}
+ run: |
+ # create variables
+ CERTIFICATE_PATH=$RUNNER_TEMP/build_certificate.p12
+ PP_PATH=$RUNNER_TEMP/build_pp.mobileprovision
+ KEYCHAIN_PATH=$RUNNER_TEMP/app-signing.keychain-db
+
+ # import certificate and provisioning profile from secrets
+ echo -n "$BUILD_CERTIFICATE_BASE64" | base64 --decode --output $CERTIFICATE_PATH
+ echo -n "$BUILD_PROVISION_PROFILE_BASE64" | base64 --decode --output $PP_PATH
+
+ # create temporary keychain
+ security create-keychain -p $KEYCHAIN_PASSWORD $KEYCHAIN_PATH
+ security set-keychain-settings -lut 21600 $KEYCHAIN_PATH
+ security unlock-keychain -p $KEYCHAIN_PASSWORD $KEYCHAIN_PATH
+
+ # import certificate to keychain
+ security import $CERTIFICATE_PATH -P $P12_PASSWORD -A -t cert -f pkcs12 -k $KEYCHAIN_PATH
+ security list-keychain -d user -s $KEYCHAIN_PATH
+
+ # apply provisioning profile
+ mkdir -p ~/Library/MobileDevice/Provisioning\ Profiles
+ cp $PP_PATH ~/Library/MobileDevice/Provisioning\ Profiles
+
+ - name: Export ipa and upload to firebase
+ uses: maierj/fastlane-action@v2.0.1
+ with:
+ lane: "ios release_staging"
+ options: '{"token": "${{secrets.FIREBASE_TOKEN}}", "app_id": "${{secrets.FIREBASE_IOS_APP_ID}}"}'
diff --git a/Gemfile b/Gemfile
new file mode 100644
index 0000000..b734015
--- /dev/null
+++ b/Gemfile
@@ -0,0 +1,10 @@
+# Autogenerated by fastlane
+#
+# Ensure this file is checked in to source control!
+
+source "https://rubygems.org"
+
+gem 'fastlane'
+
+plugins_path = File.join(File.dirname(__FILE__), 'fastlane', 'Pluginfile')
+eval_gemfile(plugins_path) if File.exist?(plugins_path)
diff --git a/Gemfile.lock b/Gemfile.lock
new file mode 100644
index 0000000..86c0f73
--- /dev/null
+++ b/Gemfile.lock
@@ -0,0 +1,207 @@
+GEM
+ remote: https://rubygems.org/
+ specs:
+ CFPropertyList (3.0.3)
+ addressable (2.7.0)
+ public_suffix (>= 2.0.2, < 5.0)
+ artifactory (3.0.15)
+ atomos (0.1.3)
+ aws-eventstream (1.1.1)
+ aws-partitions (1.462.0)
+ aws-sdk-core (3.114.0)
+ aws-eventstream (~> 1, >= 1.0.2)
+ aws-partitions (~> 1, >= 1.239.0)
+ aws-sigv4 (~> 1.1)
+ jmespath (~> 1.0)
+ aws-sdk-kms (1.43.0)
+ aws-sdk-core (~> 3, >= 3.112.0)
+ aws-sigv4 (~> 1.1)
+ aws-sdk-s3 (1.95.1)
+ aws-sdk-core (~> 3, >= 3.112.0)
+ aws-sdk-kms (~> 1)
+ aws-sigv4 (~> 1.1)
+ aws-sigv4 (1.2.3)
+ aws-eventstream (~> 1, >= 1.0.2)
+ babosa (1.0.4)
+ claide (1.0.3)
+ colored (1.2)
+ colored2 (3.1.2)
+ commander (4.6.0)
+ highline (~> 2.0.0)
+ declarative (0.0.20)
+ digest-crc (0.6.3)
+ rake (>= 12.0.0, < 14.0.0)
+ domain_name (0.5.20190701)
+ unf (>= 0.0.5, < 1.0.0)
+ dotenv (2.7.6)
+ emoji_regex (3.2.2)
+ excon (0.81.0)
+ faraday (1.4.2)
+ faraday-em_http (~> 1.0)
+ faraday-em_synchrony (~> 1.0)
+ faraday-excon (~> 1.1)
+ faraday-net_http (~> 1.0)
+ faraday-net_http_persistent (~> 1.1)
+ multipart-post (>= 1.2, < 3)
+ ruby2_keywords (>= 0.0.4)
+ faraday-cookie_jar (0.0.7)
+ faraday (>= 0.8.0)
+ http-cookie (~> 1.0.0)
+ faraday-em_http (1.0.0)
+ faraday-em_synchrony (1.0.0)
+ faraday-excon (1.1.0)
+ faraday-net_http (1.0.1)
+ faraday-net_http_persistent (1.1.0)
+ faraday_middleware (1.0.0)
+ faraday (~> 1.0)
+ fastimage (2.2.3)
+ fastlane (2.184.0)
+ CFPropertyList (>= 2.3, < 4.0.0)
+ addressable (>= 2.3, < 3.0.0)
+ artifactory (~> 3.0)
+ aws-sdk-s3 (~> 1.0)
+ babosa (>= 1.0.3, < 2.0.0)
+ bundler (>= 1.12.0, < 3.0.0)
+ colored
+ commander (~> 4.6)
+ dotenv (>= 2.1.1, < 3.0.0)
+ emoji_regex (>= 0.1, < 4.0)
+ excon (>= 0.71.0, < 1.0.0)
+ faraday (~> 1.0)
+ faraday-cookie_jar (~> 0.0.6)
+ faraday_middleware (~> 1.0)
+ fastimage (>= 2.1.0, < 3.0.0)
+ gh_inspector (>= 1.1.2, < 2.0.0)
+ google-apis-androidpublisher_v3 (~> 0.1)
+ google-apis-playcustomapp_v1 (~> 0.1)
+ google-cloud-storage (~> 1.31)
+ highline (~> 2.0)
+ json (< 3.0.0)
+ jwt (>= 2.1.0, < 3)
+ mini_magick (>= 4.9.4, < 5.0.0)
+ multipart-post (~> 2.0.0)
+ naturally (~> 2.2)
+ plist (>= 3.1.0, < 4.0.0)
+ rubyzip (>= 2.0.0, < 3.0.0)
+ security (= 0.1.3)
+ simctl (~> 1.6.3)
+ terminal-notifier (>= 2.0.0, < 3.0.0)
+ terminal-table (>= 1.4.5, < 2.0.0)
+ tty-screen (>= 0.6.3, < 1.0.0)
+ tty-spinner (>= 0.8.0, < 1.0.0)
+ word_wrap (~> 1.0.0)
+ xcodeproj (>= 1.13.0, < 2.0.0)
+ xcpretty (~> 0.3.0)
+ xcpretty-travis-formatter (>= 0.0.3)
+ fastlane-plugin-firebase_app_distribution (0.2.9)
+ gh_inspector (1.1.3)
+ google-apis-androidpublisher_v3 (0.4.0)
+ google-apis-core (~> 0.1)
+ google-apis-core (0.3.0)
+ addressable (~> 2.5, >= 2.5.1)
+ googleauth (~> 0.14)
+ httpclient (>= 2.8.1, < 3.0)
+ mini_mime (~> 1.0)
+ representable (~> 3.0)
+ retriable (>= 2.0, < 4.0)
+ rexml
+ signet (~> 0.14)
+ webrick
+ google-apis-iamcredentials_v1 (0.4.0)
+ google-apis-core (~> 0.1)
+ google-apis-playcustomapp_v1 (0.3.0)
+ google-apis-core (~> 0.1)
+ google-apis-storage_v1 (0.4.0)
+ google-apis-core (~> 0.1)
+ google-cloud-core (1.6.0)
+ google-cloud-env (~> 1.0)
+ google-cloud-errors (~> 1.0)
+ google-cloud-env (1.5.0)
+ faraday (>= 0.17.3, < 2.0)
+ google-cloud-errors (1.1.0)
+ google-cloud-storage (1.31.1)
+ addressable (~> 2.5)
+ digest-crc (~> 0.4)
+ google-apis-iamcredentials_v1 (~> 0.1)
+ google-apis-storage_v1 (~> 0.1)
+ google-cloud-core (~> 1.2)
+ googleauth (~> 0.9)
+ mini_mime (~> 1.0)
+ googleauth (0.16.2)
+ faraday (>= 0.17.3, < 2.0)
+ jwt (>= 1.4, < 3.0)
+ memoist (~> 0.16)
+ multi_json (~> 1.11)
+ os (>= 0.9, < 2.0)
+ signet (~> 0.14)
+ highline (2.0.3)
+ http-cookie (1.0.3)
+ domain_name (~> 0.5)
+ httpclient (2.8.3)
+ jmespath (1.4.0)
+ json (2.5.1)
+ jwt (2.2.3)
+ memoist (0.16.2)
+ mini_magick (4.11.0)
+ mini_mime (1.1.0)
+ multi_json (1.15.0)
+ multipart-post (2.0.0)
+ nanaimo (0.3.0)
+ naturally (2.2.1)
+ os (1.1.1)
+ plist (3.6.0)
+ public_suffix (4.0.6)
+ rake (13.0.3)
+ representable (3.1.1)
+ declarative (< 0.1.0)
+ trailblazer-option (>= 0.1.1, < 0.2.0)
+ uber (< 0.2.0)
+ retriable (3.1.2)
+ rexml (3.2.5)
+ rouge (2.0.7)
+ ruby2_keywords (0.0.4)
+ rubyzip (2.3.0)
+ security (0.1.3)
+ signet (0.15.0)
+ addressable (~> 2.3)
+ faraday (>= 0.17.3, < 2.0)
+ jwt (>= 1.5, < 3.0)
+ multi_json (~> 1.10)
+ simctl (1.6.8)
+ CFPropertyList
+ naturally
+ terminal-notifier (2.0.0)
+ terminal-table (1.8.0)
+ unicode-display_width (~> 1.1, >= 1.1.1)
+ trailblazer-option (0.1.1)
+ tty-cursor (0.7.1)
+ tty-screen (0.8.1)
+ tty-spinner (0.9.3)
+ tty-cursor (~> 0.7)
+ uber (0.1.0)
+ unf (0.1.4)
+ unf_ext
+ unf_ext (0.0.7.7)
+ unicode-display_width (1.7.0)
+ webrick (1.7.0)
+ word_wrap (1.0.0)
+ xcodeproj (1.19.0)
+ CFPropertyList (>= 2.3.3, < 4.0)
+ atomos (~> 0.1.3)
+ claide (>= 1.0.2, < 2.0)
+ colored2 (~> 3.1)
+ nanaimo (~> 0.3.0)
+ xcpretty (0.3.0)
+ rouge (~> 2.0.7)
+ xcpretty-travis-formatter (1.0.1)
+ xcpretty (~> 0.2, >= 0.0.7)
+
+PLATFORMS
+ x86_64-darwin-20
+
+DEPENDENCIES
+ fastlane
+ fastlane-plugin-firebase_app_distribution
+
+BUNDLED WITH
+ 2.2.15
diff --git a/assets/l10n/app_en.arb b/assets/l10n/app_en.arb
index e63b9ff..4e68bd8 100644
--- a/assets/l10n/app_en.arb
+++ b/assets/l10n/app_en.arb
@@ -1,6 +1,9 @@
{
- "helloWorld": "Hello World!",
"alertErrorTitle": "Error",
"alertErrorOKLabel": "OK",
- "alertErrorFallbackMessage": "Unknown error"
+ "alertErrorFallbackMessage": "Unknown error",
+ "loginScreenLoginButtonText": "Login",
+ "loginScreenForgotButtonText": "Forgot?",
+ "loginScreenEmailTextFieldPlaceholderText": "Email",
+ "loginScreenPasswordTextFieldPlaceholderText": "Password"
}
diff --git a/assets/l10n/app_vi.arb b/assets/l10n/app_vi.arb
index bab5152..9a0fb9d 100644
--- a/assets/l10n/app_vi.arb
+++ b/assets/l10n/app_vi.arb
@@ -2,5 +2,9 @@
"helloWorld": "Xin chào Thế giới!",
"alertErrorTitle": "Lỗi",
"alertErrorOKLabel": "OK",
- "alertErrorFallbackMessage": "Lỗi không xác định"
+ "alertErrorFallbackMessage": "Lỗi không xác định",
+ "loginScreenLoginButtonText": "Đăng nhập",
+ "loginScreenForgotButtonText": "Quên?",
+ "loginScreenEmailTextFieldPlaceholderText": "Email",
+ "loginScreenPasswordTextFieldPlaceholderText": "Mật khẩu"
}
diff --git a/coverage/lcov.info b/coverage/lcov.info
index 64e6a2a..8d6c3cb 100644
--- a/coverage/lcov.info
+++ b/coverage/lcov.info
@@ -4,33 +4,33 @@ LF:1
LH:0
end_of_record
SF:lib/modules/landing/landing_view.dart
-DA:19,6
-DA:21,1
+DA:20,11
DA:22,1
-DA:28,2
-DA:33,2
-DA:34,1
-DA:38,1
+DA:23,1
+DA:30,2
+DA:35,2
+DA:36,1
DA:40,1
-DA:41,3
-DA:42,3
-DA:43,1
+DA:42,1
+DA:43,3
DA:44,3
-DA:48,1
+DA:45,1
+DA:46,3
DA:50,1
-DA:51,1
+DA:52,1
DA:53,1
-DA:54,1
-DA:57,2
-DA:61,1
-DA:62,1
+DA:55,1
+DA:56,1
+DA:59,2
+DA:63,1
DA:64,1
-DA:65,2
-DA:73,1
-DA:75,2
-DA:78,1
-DA:80,2
-DA:81,1
+DA:66,1
+DA:67,2
+DA:75,1
+DA:77,2
+DA:80,1
+DA:82,2
+DA:83,1
LF:27
LH:27
end_of_record
@@ -82,36 +82,184 @@ LF:1
LH:1
end_of_record
SF:lib/components/alert/alert.dart
+DA:9,2
+DA:15,2
+DA:23,2
+DA:28,4
+DA:29,2
+DA:33,2
+DA:34,0
+DA:37,2
+DA:39,4
+DA:41,4
+DA:50,2
+DA:51,2
+DA:52,2
+DA:54,6
+LF:14
+LH:13
+end_of_record
+SF:lib/modules/login/login_view.dart
+DA:37,1
+DA:38,1
+DA:48,2
+DA:53,2
+DA:54,1
+DA:58,1
+DA:60,1
+DA:61,3
+DA:62,5
+DA:65,3
+DA:66,5
+DA:68,3
+DA:71,1
+DA:73,1
+DA:74,1
+DA:75,1
+DA:76,1
+DA:82,1
+DA:83,1
+DA:85,1
+DA:86,1
+DA:87,1
+DA:88,1
+DA:89,1
+DA:90,3
+DA:91,3
+DA:94,2
+DA:98,1
+DA:100,1
+DA:102,1
+DA:104,1
+DA:105,1
+DA:106,1
+DA:107,1
+DA:108,1
+DA:110,2
+DA:111,1
+DA:117,1
+DA:118,1
+DA:119,2
+DA:123,4
+DA:124,2
+DA:136,1
+DA:138,2
+DA:139,1
+DA:142,1
+DA:144,2
+DA:147,1
+DA:149,2
+DA:152,1
+DA:154,2
+DA:157,1
+DA:159,2
+LF:53
+LH:53
+end_of_record
+SF:lib/modules/login/login_presenter.dart
+DA:7,1
+DA:8,3
+DA:9,3
+DA:10,3
+DA:11,3
+DA:12,1
+DA:13,2
+DA:15,3
+DA:16,3
+DA:43,1
+DA:44,2
+DA:45,2
+DA:48,1
+DA:49,4
+DA:52,1
+DA:53,2
+DA:54,4
+DA:57,1
+DA:58,4
+DA:59,2
+DA:62,1
+DA:63,4
+DA:66,1
+DA:67,2
+DA:68,2
+LF:25
+LH:25
+end_of_record
+SF:lib/modules/login/login_interactor.dart
+DA:16,1
+DA:18,2
+DA:19,1
+DA:25,3
+DA:26,3
+DA:27,1
+DA:28,3
+LF:7
+LH:7
+end_of_record
+SF:lib/modules/login/login_router.dart
DA:9,1
-DA:15,1
+DA:11,2
+DA:14,1
+DA:16,2
+LF:4
+LH:4
+end_of_record
+SF:lib/modules/login/components/form.dart
+DA:4,1
+DA:5,1
+DA:9,3
+DA:11,1
+DA:13,1
+DA:16,1
+DA:18,1
+DA:20,1
+DA:22,1
DA:23,1
-DA:28,2
-DA:29,1
+DA:27,1
+DA:31,1
+DA:32,2
DA:33,1
-DA:34,0
-DA:37,1
-DA:39,2
-DA:41,2
+DA:34,1
+DA:36,2
+DA:38,6
+DA:39,3
+DA:40,3
DA:50,1
DA:51,1
-DA:52,1
-DA:54,5
-LF:14
-LH:13
+DA:53,1
+DA:55,2
+DA:56,1
+DA:58,2
+DA:63,1
+DA:64,1
+DA:66,1
+DA:68,2
+DA:69,1
+DA:71,2
+DA:72,1
+DA:74,5
+DA:75,2
+DA:78,1
+DA:79,3
+DA:80,1
+DA:81,1
+LF:38
+LH:38
end_of_record
SF:lib/modules/login/login_module.dart
-DA:8,0
-LF:1
-LH:0
+DA:34,1
+DA:36,1
+LF:2
+LH:2
end_of_record
SF:lib/app.dart
-DA:9,1
-DA:11,1
-DA:13,1
-DA:14,1
-DA:15,3
-DA:18,2
-DA:19,1
+DA:9,2
+DA:11,2
+DA:13,2
+DA:14,2
+DA:15,6
+DA:18,4
+DA:19,2
DA:26,0
LF:8
LH:7
@@ -121,31 +269,32 @@ DA:3,0
LF:1
LH:0
end_of_record
-SF:lib/configs/app.dart
-DA:4,5
-DA:6,0
-DA:10,5
-DA:12,0
-DA:14,0
-DA:22,0
-DA:24,0
-DA:32,0
-DA:34,0
-LF:9
-LH:2
-end_of_record
SF:lib/configs/routes.dart
-DA:3,6
+DA:3,9
DA:4,0
DA:5,2
DA:6,2
-LF:4
+DA:7,0
+LF:5
LH:3
end_of_record
+SF:lib/configs/app.dart
+DA:4,10
+DA:6,1
+DA:10,10
+DA:12,0
+DA:14,0
+DA:22,1
+DA:24,1
+DA:32,1
+DA:34,1
+LF:9
+LH:7
+end_of_record
SF:lib/gen/configs.gen.dart
-DA:15,0
-DA:16,6
-DA:18,0
+DA:16,0
+DA:17,9
+DA:19,0
LF:3
LH:1
end_of_record
@@ -164,23 +313,87 @@ LF:5
LH:0
end_of_record
SF:lib/core/viper/view.dart
-DA:16,1
-DA:18,1
-DA:20,2
-DA:22,1
+DA:16,2
+DA:18,2
+DA:20,4
+DA:22,2
LF:4
LH:4
end_of_record
SF:lib/core/viper/presenter.dart
+DA:9,2
+DA:14,2
+DA:15,2
+DA:16,2
+LF:4
+LH:4
+end_of_record
+SF:lib/components/button/button.dart
DA:9,1
DA:14,1
-DA:15,1
DA:16,1
-LF:4
-LH:4
+DA:18,1
+DA:19,1
+DA:20,1
+DA:21,1
+DA:23,1
+DA:24,2
+DA:25,2
+DA:26,1
+DA:28,1
+DA:29,1
+DA:30,1
+DA:31,1
+LF:15
+LH:15
+end_of_record
+SF:lib/components/progress_hud/progress_hud.dart
+DA:10,1
+DA:14,1
+DA:16,1
+DA:20,1
+DA:21,1
+DA:22,1
+DA:25,0
+DA:32,0
+DA:33,0
+DA:38,1
+DA:39,1
+DA:41,1
+LF:12
+LH:9
+end_of_record
+SF:lib/components/translucent_text_field/translucent_text_field.dart
+DA:7,1
+DA:14,1
+DA:22,1
+DA:24,1
+DA:26,1
+DA:29,1
+DA:31,1
+DA:32,1
+DA:33,1
+DA:35,1
+DA:36,1
+DA:37,1
+DA:38,1
+DA:39,1
+DA:40,1
+DA:42,2
+DA:43,1
+DA:44,1
+DA:45,1
+DA:49,1
+DA:51,0
+DA:52,0
+DA:53,0
+DA:56,0
+DA:59,3
+LF:25
+LH:21
end_of_record
SF:lib/core/extensions/build_context.dart
-DA:5,2
+DA:5,4
LF:1
LH:1
end_of_record
@@ -200,9 +413,9 @@ DA:8,0
DA:9,0
DA:10,0
DA:17,0
-DA:22,1
-DA:23,2
-DA:24,1
+DA:22,2
+DA:23,4
+DA:24,2
LF:11
LH:3
end_of_record
@@ -218,34 +431,37 @@ DA:12,0
DA:15,0
DA:16,0
DA:17,0
-LF:11
+DA:20,0
+DA:21,0
+DA:22,0
+LF:14
LH:0
end_of_record
SF:lib/services/locator/locator_service.dart
-DA:12,8
+DA:13,12
LF:1
LH:1
end_of_record
SF:lib/gen/assets.gen.dart
-DA:11,5
+DA:11,10
DA:13,0
-DA:15,1
-DA:17,1
+DA:15,2
+DA:17,2
DA:22,0
-DA:28,5
-DA:30,1
-DA:50,1
+DA:28,10
+DA:30,2
+DA:50,2
DA:73,0
-DA:77,5
-DA:81,1
-DA:98,1
-DA:99,1
+DA:77,10
+DA:81,2
+DA:98,2
+DA:99,2
DA:118,0
LF:14
LH:10
end_of_record
SF:lib/gen/flavors.gen.dart
-DA:1,5
+DA:1,10
LF:1
LH:1
end_of_record
@@ -267,17 +483,22 @@ DA:10,0
LF:3
LH:0
end_of_record
+SF:lib/modules/forgot_password/forgot_password_module.dart
+DA:8,0
+LF:1
+LH:0
+end_of_record
SF:lib/modules/screen.dart
-DA:11,11
-DA:16,1
-DA:20,1
-DA:21,1
-DA:22,1
-DA:23,1
+DA:11,22
+DA:16,2
+DA:20,2
+DA:21,2
+DA:22,2
+DA:23,2
DA:27,0
DA:28,0
-DA:32,1
-DA:33,1
+DA:32,2
+DA:33,2
LF:10
LH:8
end_of_record
@@ -326,11 +547,11 @@ LF:40
LH:0
end_of_record
SF:lib/services/auth/params/auth_login_params.dart
-DA:4,0
-DA:10,0
-DA:13,0
-DA:14,0
-DA:18,0
+DA:4,1
+DA:10,1
+DA:13,2
+DA:14,2
+DA:18,1
DA:31,0
DA:33,0
DA:34,0
@@ -339,7 +560,7 @@ DA:36,0
DA:37,0
DA:38,0
LF:12
-LH:0
+LH:5
end_of_record
SF:lib/services/api/api_exception.dart
DA:4,0
@@ -381,13 +602,13 @@ LH:0
end_of_record
SF:lib/services/http/http_method.dart
DA:4,0
-DA:5,5
+DA:5,10
DA:13,0
LF:3
LH:1
end_of_record
SF:lib/services/http/http_exception.dart
-DA:3,5
+DA:3,10
DA:25,0
DA:31,0
DA:33,0
diff --git a/fastlane/Appfile b/fastlane/Appfile
new file mode 100644
index 0000000..1803063
--- /dev/null
+++ b/fastlane/Appfile
@@ -0,0 +1,6 @@
+# app_identifier("[[APP_IDENTIFIER]]") # The bundle identifier of your app
+# apple_id("[[APPLE_ID]]") # Your Apple email address
+
+
+# For more information about the Appfile, see:
+# https://docs.fastlane.tools/advanced/#appfile
diff --git a/fastlane/Fastfile b/fastlane/Fastfile
new file mode 100644
index 0000000..8014908
--- /dev/null
+++ b/fastlane/Fastfile
@@ -0,0 +1,48 @@
+# This file contains the fastlane.tools configuration
+# You can find the documentation at https://docs.fastlane.tools
+#
+# For a list of all available actions, check out
+#
+# https://docs.fastlane.tools/actions
+#
+# For a list of all available plugins, check out
+#
+# https://docs.fastlane.tools/plugins/available-plugins
+#
+
+# Uncomment the line if you want fastlane to automatically update itself
+# update_fastlane
+
+# default_platform(:ios)
+
+platform :ios do
+ desc "Export ipa and upload to Firebase"
+ lane :release_staging do |options|
+
+ build_app(
+ workspace: "ios/Runner.xcworkspace",
+ scheme: "staging",
+ silent: true,
+ configuration: "Release-staging",
+ export_options: {
+ method: "development"
+ }
+ )
+
+ firebase_app_distribution(
+ app: options[:app_id],
+ firebase_cli_token: options[:token],
+ )
+ end
+end
+
+platform :android do
+ lane :upload_firebase do |options|
+ firebase_app_distribution(
+ app: options[:app_id],
+ android_artifact_type: "APK",
+ android_artifact_path: options[:apk_path],
+ firebase_cli_token: options[:token],
+ )
+end
+end
diff --git a/fastlane/Pluginfile b/fastlane/Pluginfile
new file mode 100644
index 0000000..a812a01
--- /dev/null
+++ b/fastlane/Pluginfile
@@ -0,0 +1,6 @@
+# Autogenerated by fastlane
+#
+# Ensure this file is checked in to source control!
+
+gem 'fastlane-plugin-firebase_app_distribution'
+gem 'fastlane-plugin-firebase_app_distribution'
diff --git a/fastlane/README.md b/fastlane/README.md
new file mode 100644
index 0000000..1d028d2
--- /dev/null
+++ b/fastlane/README.md
@@ -0,0 +1,38 @@
+fastlane documentation
+================
+# Installation
+
+Make sure you have the latest version of the Xcode command line tools installed:
+
+```
+xcode-select --install
+```
+
+Install _fastlane_ using
+```
+[sudo] gem install fastlane -NV
+```
+or alternatively using `brew install fastlane`
+
+# Available Actions
+## iOS
+### ios release_staging
+```
+fastlane ios release_staging
+```
+Export ipa and upload to Firebase
+
+----
+
+## Android
+### android release_staging
+```
+fastlane android release_staging
+```
+Export ipa and upload to Firebase
+
+----
+
+This README.md is auto-generated and will be re-generated every time [fastlane](https://fastlane.tools) is run.
+More information about fastlane can be found on [fastlane.tools](https://fastlane.tools).
+The documentation of fastlane can be found on [docs.fastlane.tools](https://docs.fastlane.tools).
diff --git a/fastlane/report.xml b/fastlane/report.xml
new file mode 100644
index 0000000..e1978a6
--- /dev/null
+++ b/fastlane/report.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj
index 87b09a4..b632a02 100644
--- a/ios/Runner.xcodeproj/project.pbxproj
+++ b/ios/Runner.xcodeproj/project.pbxproj
@@ -476,6 +476,7 @@
isa = XCBuildConfiguration;
baseConfigurationReference = 723E2E286422E7355D82CD8E /* Pods-Runner.debug-production.xcconfig */;
buildSettings = {
+ DEVELOPMENT_TEAM = FM5NT9MLCT;
PRODUCT_NAME = Runner;
};
name = "Debug-production";
@@ -484,6 +485,7 @@
isa = XCBuildConfiguration;
baseConfigurationReference = A5EBA3F1CC940667DF79EE46 /* Pods-Runner.profile-production.xcconfig */;
buildSettings = {
+ DEVELOPMENT_TEAM = FM5NT9MLCT;
PRODUCT_NAME = Runner;
};
name = "Profile-production";
@@ -492,6 +494,7 @@
isa = XCBuildConfiguration;
baseConfigurationReference = BD4D2DD9EF09CB23F1BFE775 /* Pods-Runner.release-production.xcconfig */;
buildSettings = {
+ DEVELOPMENT_TEAM = FM5NT9MLCT;
PRODUCT_NAME = Runner;
};
name = "Release-production";
@@ -500,6 +503,7 @@
isa = XCBuildConfiguration;
baseConfigurationReference = 527D168B5B717A9D51E5BDE8 /* Pods-Runner.debug-staging.xcconfig */;
buildSettings = {
+ DEVELOPMENT_TEAM = FM5NT9MLCT;
PRODUCT_NAME = Runner;
};
name = "Debug-staging";
@@ -508,6 +512,7 @@
isa = XCBuildConfiguration;
baseConfigurationReference = C2F4412A61D42AC96F3D7D8A /* Pods-Runner.profile-staging.xcconfig */;
buildSettings = {
+ DEVELOPMENT_TEAM = FM5NT9MLCT;
PRODUCT_NAME = Runner;
};
name = "Profile-staging";
@@ -516,6 +521,7 @@
isa = XCBuildConfiguration;
baseConfigurationReference = E2E32C4D4960E4DC124372C0 /* Pods-Runner.release-staging.xcconfig */;
buildSettings = {
+ DEVELOPMENT_TEAM = FM5NT9MLCT;
PRODUCT_NAME = Runner;
};
name = "Release-staging";
diff --git a/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
index a28140c..fb2dffc 100644
--- a/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
+++ b/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
@@ -27,8 +27,6 @@
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
-
-
-
-
+
+
-
-
+
+
+
+
+
+
-
-
+ shouldUseLaunchSchemeArgsEnv = "YES">
+
+
-
-
-
+
-
+
-
+ debugDocumentVersioning = "YES">
+
-
+
diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist
index cda6f0c..cd93082 100644
--- a/ios/Runner/Info.plist
+++ b/ios/Runner/Info.plist
@@ -1,45 +1,45 @@
-
- CFBundleDevelopmentRegion
- $(DEVELOPMENT_LANGUAGE)
- CFBundleExecutable
- $(EXECUTABLE_NAME)
- CFBundleIdentifier
- $(PRODUCT_BUNDLE_IDENTIFIER)
- CFBundleInfoDictionaryVersion
- 6.0
- CFBundleName
- $(BUNDLE_NAME)
- CFBundlePackageType
- APPL
- CFBundleShortVersionString
- $(FLUTTER_BUILD_NAME)
- CFBundleSignature
- ????
- CFBundleVersion
- $(FLUTTER_BUILD_NUMBER)
- LSRequiresIPhoneOS
-
- UILaunchStoryboardName
- $(ASSET_PREFIX)LaunchScreen
- UIMainStoryboardFile
- Main
- UISupportedInterfaceOrientations
-
- UIInterfaceOrientationPortrait
- UIInterfaceOrientationLandscapeLeft
- UIInterfaceOrientationLandscapeRight
-
- UISupportedInterfaceOrientations~ipad
-
- UIInterfaceOrientationPortrait
- UIInterfaceOrientationPortraitUpsideDown
- UIInterfaceOrientationLandscapeLeft
- UIInterfaceOrientationLandscapeRight
-
- UIViewControllerBasedStatusBarAppearance
-
-
+
+ CFBundleDevelopmentRegion
+ $(DEVELOPMENT_LANGUAGE)
+ CFBundleExecutable
+ $(EXECUTABLE_NAME)
+ CFBundleIdentifier
+ $(PRODUCT_BUNDLE_IDENTIFIER)
+ CFBundleInfoDictionaryVersion
+ 6.0
+ CFBundleName
+ $(BUNDLE_NAME)
+ CFBundlePackageType
+ APPL
+ CFBundleShortVersionString
+ $(FLUTTER_BUILD_NAME)
+ CFBundleSignature
+ ????
+ CFBundleVersion
+ $(FLUTTER_BUILD_NUMBER)
+ LSRequiresIPhoneOS
+
+ UILaunchStoryboardName
+ $(ASSET_PREFIX)LaunchScreen
+ UIMainStoryboardFile
+ Main
+ UISupportedInterfaceOrientations
+
+ UIInterfaceOrientationPortrait
+ UIInterfaceOrientationLandscapeLeft
+ UIInterfaceOrientationLandscapeRight
+
+ UISupportedInterfaceOrientations~ipad
+
+ UIInterfaceOrientationPortrait
+ UIInterfaceOrientationPortraitUpsideDown
+ UIInterfaceOrientationLandscapeLeft
+ UIInterfaceOrientationLandscapeRight
+
+ UIViewControllerBasedStatusBarAppearance
+
+
diff --git a/lib/components/button/button.dart b/lib/components/button/button.dart
new file mode 100644
index 0000000..5c9fdeb
--- /dev/null
+++ b/lib/components/button/button.dart
@@ -0,0 +1,41 @@
+import 'package:flutter/material.dart';
+import 'package:flutter/widgets.dart';
+import 'package:flutter_platform_widgets/flutter_platform_widgets.dart';
+
+class Button extends StatelessWidget {
+ final String? title;
+ final VoidCallback? onPressed;
+ final bool isEnabled;
+ const Button({
+ Key? key,
+ this.title,
+ this.onPressed,
+ this.isEnabled = true,
+ }) : super(key: key);
+
+ @override
+ Widget build(BuildContext context) {
+ return Container(
+ decoration: BoxDecoration(
+ color: isEnabled ? Colors.white : Colors.grey,
+ borderRadius: BorderRadius.circular(12.0),
+ ),
+ child: PlatformButton(
+ onPressed: isEnabled ? onPressed : null,
+ materialFlat: (_, __) => MaterialFlatButtonData(),
+ child: Center(
+ child: title != null
+ ? Text(
+ title!,
+ style: const TextStyle(
+ color: Colors.black,
+ fontSize: 17.0,
+ fontWeight: FontWeight.bold,
+ ),
+ )
+ : null,
+ ),
+ ),
+ );
+ }
+}
diff --git a/lib/components/translucent_text_field/translucent_text_field.dart b/lib/components/translucent_text_field/translucent_text_field.dart
new file mode 100644
index 0000000..415a678
--- /dev/null
+++ b/lib/components/translucent_text_field/translucent_text_field.dart
@@ -0,0 +1,65 @@
+import 'package:flutter/cupertino.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter/widgets.dart';
+import 'package:flutter_platform_widgets/flutter_platform_widgets.dart';
+
+class TranslucentTextField extends StatelessWidget {
+ const TranslucentTextField({
+ Key? key,
+ this.placeholder,
+ this.trailing,
+ this.keyboardType = TextInputType.text,
+ this.obscureText = false,
+ this.controller,
+ }) : super(key: key);
+
+ final String? placeholder;
+ final Widget? trailing;
+ final bool obscureText;
+ final TextInputType keyboardType;
+ final TextEditingController? controller;
+
+ @override
+ Widget build(BuildContext context) {
+ final placeholderStyle = TextStyle(
+ fontSize: 17.0,
+ color: Colors.white.withAlpha(30),
+ );
+
+ return Container(
+ padding: const EdgeInsets.fromLTRB(12.0, 0.0, 12.0, 0.0),
+ decoration: BoxDecoration(
+ color: Colors.white.withAlpha(10),
+ borderRadius: BorderRadius.circular(12.0),
+ ),
+ child: Center(
+ child: Row(
+ children: [
+ Expanded(
+ child: PlatformTextField(
+ controller: controller,
+ style: const TextStyle(color: Colors.white),
+ material: (_, __) => MaterialTextFieldData(
+ keyboardType: keyboardType,
+ decoration: InputDecoration(
+ hintText: placeholder,
+ hintStyle: placeholderStyle,
+ border: InputBorder.none,
+ ),
+ obscureText: obscureText,
+ ),
+ cupertino: (_, __) => CupertinoTextFieldData(
+ keyboardType: keyboardType,
+ placeholder: placeholder,
+ placeholderStyle: placeholderStyle,
+ decoration: const BoxDecoration(border: Border()),
+ obscureText: obscureText),
+ ),
+ ),
+ if (trailing != null) trailing!
+ ],
+ ),
+ ),
+ );
+ }
+}
diff --git a/lib/configs/routes.dart b/lib/configs/routes.dart
index c436d67..216952b 100644
--- a/lib/configs/routes.dart
+++ b/lib/configs/routes.dart
@@ -4,4 +4,5 @@ final Map _routes = {
LandingModule.routePath: (_) => LandingModule(),
LoginModule.routePath: (_) => LoginModule(),
HomeModule.routePath: (_) => HomeModule(),
+ ForgotPasswordModule.routePath: (_) => ForgotPasswordModule(),
};
diff --git a/lib/gen/assets.gen.dart b/lib/gen/assets.gen.dart
index face551..2d07bd4 100644
--- a/lib/gen/assets.gen.dart
+++ b/lib/gen/assets.gen.dart
@@ -27,10 +27,7 @@ class Assets {
}
class AssetGenImage extends AssetImage {
- const AssetGenImage(String assetName)
- : _assetName = assetName,
- super(assetName);
- final String _assetName;
+ const AssetGenImage(String assetName) : super(assetName);
Image image({
Key? key,
@@ -75,7 +72,7 @@ class AssetGenImage extends AssetImage {
);
}
- String get path => _assetName;
+ String get path => assetName;
}
class SvgGenImage {
diff --git a/lib/gen/configs.gen.dart b/lib/gen/configs.gen.dart
index 25ddf7f..77d1dfc 100644
--- a/lib/gen/configs.gen.dart
+++ b/lib/gen/configs.gen.dart
@@ -2,6 +2,7 @@ import 'package:survey/gen/flavors.gen.dart';
import 'package:flutter/widgets.dart';
import 'package:survey/models/auth_token_info.dart';
import 'package:survey/models/user_info.dart';
+import 'package:survey/modules/forgot_password/forgot_password_module.dart';
import 'package:survey/modules/home/home_module.dart';
import 'package:survey/modules/landing/landing_module.dart';
import 'package:survey/modules/login/login_module.dart';
diff --git a/lib/modules/forgot_password/forgot_password_module.dart b/lib/modules/forgot_password/forgot_password_module.dart
new file mode 100644
index 0000000..4d327a9
--- /dev/null
+++ b/lib/modules/forgot_password/forgot_password_module.dart
@@ -0,0 +1,16 @@
+import 'package:flutter/widgets.dart';
+import 'package:survey/core/viper/module.dart';
+import 'package:survey/modules/screen.dart';
+
+class ForgotPasswordModule extends Module {
+ static const routePath = "/forgot-password";
+
+ @override
+ Widget build(BuildContext context) {
+ return const Screen(
+ body: Center(
+ child: Text("Forgot Password"),
+ ),
+ );
+ }
+}
diff --git a/lib/modules/landing/landing_interactor.dart b/lib/modules/landing/landing_interactor.dart
index 45a5ba5..9090486 100644
--- a/lib/modules/landing/landing_interactor.dart
+++ b/lib/modules/landing/landing_interactor.dart
@@ -2,6 +2,7 @@ part of 'landing_module.dart';
abstract class LandingInteractor extends Interactor {
void validateAuthentication();
+ void logout();
}
abstract class LandingInteractorDelegate {
@@ -21,4 +22,9 @@ class LandingInteractorImpl extends LandingInteractor {
delegate?.authenticationDidFailToValidate.add(exception);
});
}
+
+ @override
+ void logout() {
+ _authRepository.logout().then((value) => null);
+ }
}
diff --git a/lib/modules/landing/landing_module.dart b/lib/modules/landing/landing_module.dart
index 4772568..aa3a4f9 100644
--- a/lib/modules/landing/landing_module.dart
+++ b/lib/modules/landing/landing_module.dart
@@ -8,7 +8,8 @@ import 'package:survey/modules/home/home_module.dart';
import 'package:survey/modules/login/login_module.dart';
import 'package:survey/modules/screen.dart';
import 'package:survey/core/extensions/build_context.dart';
-import 'package:survey/repositories/auth_repository.dart';
+import 'package:survey/repositories/auth/auth_repository.dart';
+import 'package:survey/services/api/api_service.dart';
import 'package:survey/services/locator/locator_service.dart';
part 'landing_presenter.dart';
diff --git a/lib/modules/landing/landing_presenter.dart b/lib/modules/landing/landing_presenter.dart
index aa6951a..df2c9e9 100644
--- a/lib/modules/landing/landing_presenter.dart
+++ b/lib/modules/landing/landing_presenter.dart
@@ -42,8 +42,14 @@ class LandingPresenterImpl extends LandingPresenter
interactor.validateAuthentication();
}
- void _authenticationDidFailToValidate(Object error) {
- view.alert(error);
+ void _authenticationDidFailToValidate(Exception exception) {
+ if (exception == ApiException.invalidToken) {
+ interactor.logout();
+ router.replaceToLoginScreen(context: view.context);
+ return;
+ }
+
+ view.alert(exception);
}
void _didAllFinish(bool isAuthenticated) {
diff --git a/lib/modules/login/components/form.dart b/lib/modules/login/components/form.dart
new file mode 100644
index 0000000..9d3fa51
--- /dev/null
+++ b/lib/modules/login/components/form.dart
@@ -0,0 +1,89 @@
+part of '../login_module.dart';
+
+class _Form extends StatefulWidget {
+ @override
+ __FormState createState() => __FormState();
+}
+
+class __FormState extends State<_Form> {
+ _LoginViewImplState get _state => context.findAncestorStateOfType()!;
+
+ @override
+ void initState() {
+ super.initState();
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ return Container(
+ padding: const EdgeInsets.fromLTRB(24, 0, 24, 0),
+ child: Column(
+ mainAxisAlignment: MainAxisAlignment.center,
+ children: [
+ _emailTextField(),
+ const SizedBox(
+ height: 20,
+ ),
+ _passwordTextField(),
+ const SizedBox(
+ height: 20,
+ ),
+ StreamsSelector0.value(
+ stream: _state._isLoginButtonEnabled,
+ builder: (_, isEnabled, __) {
+ return Button(
+ key: LoginView.loginButtonKey,
+ title: AppLocalizations.of(context)!.loginScreenLoginButtonText,
+ isEnabled: isEnabled,
+ onPressed: () => _state.delegate?.loginButtonDidTap.add([
+ _state._emailController.text,
+ _state._passwordController.text
+ ]),
+ );
+ },
+ )
+ ],
+ ),
+ );
+ }
+
+ Widget _emailTextField() {
+ return SizedBox(
+ height: 56.0,
+ child: TranslucentTextField(
+ key: LoginView.emailTextFieldKey,
+ placeholder: AppLocalizations.of(context)!
+ .loginScreenEmailTextFieldPlaceholderText,
+ keyboardType: TextInputType.emailAddress,
+ controller: _state._emailController,
+ ),
+ );
+ }
+
+ Widget _passwordTextField() {
+ return SizedBox(
+ height: 56.0,
+ child: TranslucentTextField(
+ key: LoginView.passwordTextFieldKey,
+ placeholder: AppLocalizations.of(context)!
+ .loginScreenPasswordTextFieldPlaceholderText,
+ obscureText: true,
+ controller: _state._passwordController,
+ trailing: PlatformButton(
+ key: LoginView.forgotButtonKey,
+ onPressed: () => _state.delegate?.forgotButtonDidTap.add(null),
+ materialFlat: (_, __) => MaterialFlatButtonData(
+ color: Colors.transparent,
+ ),
+ child: Text(
+ AppLocalizations.of(context)!.loginScreenForgotButtonText,
+ style: TextStyle(
+ color: Colors.white.withAlpha(50),
+ fontSize: 15.0,
+ ),
+ ),
+ ),
+ ),
+ );
+ }
+}
diff --git a/lib/modules/login/login_interactor.dart b/lib/modules/login/login_interactor.dart
new file mode 100644
index 0000000..befa9f6
--- /dev/null
+++ b/lib/modules/login/login_interactor.dart
@@ -0,0 +1,27 @@
+part of 'login_module.dart';
+
+abstract class LoginInteractor extends Interactor {
+ void login(String email, String password);
+}
+
+abstract class LoginInteractorDelegate {
+ BehaviorSubject get didLogin;
+
+ BehaviorSubject