diff --git a/.github/workflows/appcenter_android.yml b/.github/workflows/appcenter_android.yml
new file mode 100644
index 0000000..a10e764
--- /dev/null
+++ b/.github/workflows/appcenter_android.yml
@@ -0,0 +1,41 @@
+name: Build and push Android
+
+on:
+ push:
+ branches:
+ - master
+
+jobs:
+ build:
+
+ runs-on: ubuntu-latest
+
+ steps:
+ - uses: actions/checkout@v1
+ - uses: actions/setup-java@v1
+ with:
+ java-version: '12.x'
+ - name: Cache Flutter Dependencies
+ uses: actions/cache@v1
+ with:
+ path: /opt/hostedtoolcache/flutter
+ key: ${{ runner.os }}-flutter
+ - uses: subosito/flutter-action@v1
+ with:
+ channel: 'dev' # or: 'dev' or 'beta'
+ - name: Install dependencies
+ run: flutter pub get
+ working-directory: ecommers
+ - name: generate files
+ run: flutter packages pub run build_runner build --delete-conflicting-outputs
+ working-directory: ecommers
+ - name: Build Android app
+ run: flutter build apk --target-platform android-arm
+ working-directory: ecommers
+ - name: upload artefact to App Center
+ uses: wzieba/AppCenter-Github-Action@v1.0.0
+ with:
+ appName: EPAM/Flutter-eCom-Android
+ token: ${{secrets.AM_android_appcenter}}
+ group: Testers
+ file: ecommers/build/app/outputs/apk/release/app-release.apk
diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
new file mode 100644
index 0000000..84f8cd9
--- /dev/null
+++ b/.github/workflows/main.yml
@@ -0,0 +1,31 @@
+name: build and check Android
+
+on: [push]
+
+jobs:
+ build:
+
+ runs-on: ubuntu-latest
+
+ steps:
+ - uses: actions/checkout@v1
+ - uses: actions/setup-java@v1
+ with:
+ java-version: '12.x'
+ - name: Cache Flutter Dependencies
+ uses: actions/cache@v1
+ with:
+ path: /opt/hostedtoolcache/flutter
+ key: ${{ runner.os }}-flutter
+ - uses: subosito/flutter-action@v1
+ with:
+ channel: 'dev' # or: 'dev' or 'beta'
+ - name: Install dependencies
+ run: flutter pub get
+ working-directory: ecommers
+ - name: generate files
+ run: flutter packages pub run build_runner build --delete-conflicting-outputs
+ working-directory: ecommers
+ - name: Build Android app
+ run: flutter build apk --target-platform android-arm
+ working-directory: ecommers
diff --git a/LICENSE b/LICENSE
index 96ee493..c9a6960 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,21 +1,201 @@
-MIT License
-
-Copyright (c) 2020 Eugeny Sampir
-
-Permission is hereby granted, free of charge, to any person obtaining a copy
-of this software and associated documentation files (the "Software"), to deal
-in the Software without restriction, including without limitation the rights
-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the Software is
-furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in all
-copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-SOFTWARE.
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright 2020 EPAM Systems, Inc.
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
diff --git a/ecommers/.vscode/launch.json b/ecommers/.vscode/launch.json
new file mode 100644
index 0000000..3287bb6
--- /dev/null
+++ b/ecommers/.vscode/launch.json
@@ -0,0 +1,13 @@
+{
+ // Use IntelliSense to learn about possible attributes.
+ // Hover to view descriptions of existing attributes.
+ // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
+ "version": "0.2.0",
+ "configurations": [
+ {
+ "name": "Flutter",
+ "request": "launch",
+ "type": "dart"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/ecommers/README.md b/ecommers/README.md
index cec9f1e..e12b13e 100644
--- a/ecommers/README.md
+++ b/ecommers/README.md
@@ -1,6 +1,6 @@
# ecommers
-A new Flutter project.
+For the first run execute terminal command: 'flutter packages pub run build_runner build'
## Getting Started
diff --git a/ecommers/analysis_options.yaml b/ecommers/analysis_options.yaml
new file mode 100644
index 0000000..3de453a
--- /dev/null
+++ b/ecommers/analysis_options.yaml
@@ -0,0 +1,14 @@
+include: package:lint/analysis_options.yaml
+
+analyzer:
+ exclude:
+ - lib/generated/i18n.dart
+ - lib/**.g.dart
+ - lib/**.chopper.dart
+ errors:
+ todo: warning
+
+linter:
+ rules:
+ prefer_single_quotes: true
+ avoid_classes_with_only_static_members: false
\ No newline at end of file
diff --git a/ecommers/android/app/build.gradle b/ecommers/android/app/build.gradle
index 609f8c7..84e9dee 100644
--- a/ecommers/android/app/build.gradle
+++ b/ecommers/android/app/build.gradle
@@ -39,7 +39,7 @@ android {
defaultConfig {
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
applicationId "com.example.ecommers"
- minSdkVersion 16
+ minSdkVersion 18
targetSdkVersion 28
versionCode flutterVersionCode.toInteger()
versionName flutterVersionName
diff --git a/ecommers/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/ecommers/android/app/src/main/res/mipmap-hdpi/ic_launcher.png
index db77bb4..6aa0471 100644
Binary files a/ecommers/android/app/src/main/res/mipmap-hdpi/ic_launcher.png and b/ecommers/android/app/src/main/res/mipmap-hdpi/ic_launcher.png differ
diff --git a/ecommers/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/ecommers/android/app/src/main/res/mipmap-mdpi/ic_launcher.png
index 17987b7..916e7fd 100644
Binary files a/ecommers/android/app/src/main/res/mipmap-mdpi/ic_launcher.png and b/ecommers/android/app/src/main/res/mipmap-mdpi/ic_launcher.png differ
diff --git a/ecommers/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/ecommers/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
index 09d4391..af93e89 100644
Binary files a/ecommers/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png and b/ecommers/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ
diff --git a/ecommers/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/ecommers/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
index d5f1c8d..ba896bb 100644
Binary files a/ecommers/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png and b/ecommers/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ
diff --git a/ecommers/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/ecommers/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
index 4d6372e..aeecd54 100644
Binary files a/ecommers/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png and b/ecommers/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ
diff --git a/ecommers/assets/add.svg b/ecommers/assets/add.svg
new file mode 100644
index 0000000..3e0c59d
--- /dev/null
+++ b/ecommers/assets/add.svg
@@ -0,0 +1,6 @@
+
diff --git a/ecommers/assets/all_order.svg b/ecommers/assets/all_order.svg
new file mode 100644
index 0000000..e2175b1
--- /dev/null
+++ b/ecommers/assets/all_order.svg
@@ -0,0 +1,12 @@
+
diff --git a/ecommers/assets/apparel.svg b/ecommers/assets/apparel.svg
new file mode 100644
index 0000000..6854b38
--- /dev/null
+++ b/ecommers/assets/apparel.svg
@@ -0,0 +1,9 @@
+
diff --git a/ecommers/assets/arrow_right.svg b/ecommers/assets/arrow_right.svg
new file mode 100644
index 0000000..f344dae
--- /dev/null
+++ b/ecommers/assets/arrow_right.svg
@@ -0,0 +1,9 @@
+
diff --git a/ecommers/assets/beauty.svg b/ecommers/assets/beauty.svg
new file mode 100644
index 0000000..6f9ff8c
--- /dev/null
+++ b/ecommers/assets/beauty.svg
@@ -0,0 +1,9 @@
+
diff --git a/ecommers/assets/bell.svg b/ecommers/assets/bell.svg
new file mode 100644
index 0000000..ddda8a7
--- /dev/null
+++ b/ecommers/assets/bell.svg
@@ -0,0 +1,3 @@
+
diff --git a/ecommers/assets/check_form.svg b/ecommers/assets/check_form.svg
new file mode 100644
index 0000000..8973bbd
--- /dev/null
+++ b/ecommers/assets/check_form.svg
@@ -0,0 +1,5 @@
+
diff --git a/ecommers/assets/close_icon.svg b/ecommers/assets/close_icon.svg
new file mode 100644
index 0000000..3c72026
--- /dev/null
+++ b/ecommers/assets/close_icon.svg
@@ -0,0 +1,3 @@
+
diff --git a/ecommers/assets/credit_card.png b/ecommers/assets/credit_card.png
new file mode 100644
index 0000000..2e28be7
Binary files /dev/null and b/ecommers/assets/credit_card.png differ
diff --git a/ecommers/assets/currency.svg b/ecommers/assets/currency.svg
new file mode 100644
index 0000000..9d4d189
--- /dev/null
+++ b/ecommers/assets/currency.svg
@@ -0,0 +1,3 @@
+
diff --git a/ecommers/assets/data/login.json b/ecommers/assets/data/login.json
new file mode 100644
index 0000000..3256b08
--- /dev/null
+++ b/ecommers/assets/data/login.json
@@ -0,0 +1,5 @@
+{
+ "access_token": "someToken",
+ "refresh_token": "refreshToken",
+ "expiration_date": "2021-01-01 12:00:00"
+}
\ No newline at end of file
diff --git a/ecommers/assets/discuss_issue.svg b/ecommers/assets/discuss_issue.svg
new file mode 100644
index 0000000..69ebcf7
--- /dev/null
+++ b/ecommers/assets/discuss_issue.svg
@@ -0,0 +1,3 @@
+
diff --git a/ecommers/assets/dress_image_cotton.png b/ecommers/assets/dress_image_cotton.png
new file mode 100644
index 0000000..74ada66
Binary files /dev/null and b/ecommers/assets/dress_image_cotton.png differ
diff --git a/ecommers/assets/dress_image_cotton2.png b/ecommers/assets/dress_image_cotton2.png
new file mode 100644
index 0000000..dd62add
Binary files /dev/null and b/ecommers/assets/dress_image_cotton2.png differ
diff --git a/ecommers/assets/dress_image_floral.png b/ecommers/assets/dress_image_floral.png
new file mode 100644
index 0000000..5257ce6
Binary files /dev/null and b/ecommers/assets/dress_image_floral.png differ
diff --git a/ecommers/assets/dress_image_floral2.png b/ecommers/assets/dress_image_floral2.png
new file mode 100644
index 0000000..65772f8
Binary files /dev/null and b/ecommers/assets/dress_image_floral2.png differ
diff --git a/ecommers/assets/dress_image_pattern.png b/ecommers/assets/dress_image_pattern.png
new file mode 100644
index 0000000..d302a72
Binary files /dev/null and b/ecommers/assets/dress_image_pattern.png differ
diff --git a/ecommers/assets/dress_image_pattern2.png b/ecommers/assets/dress_image_pattern2.png
new file mode 100644
index 0000000..4972a8f
Binary files /dev/null and b/ecommers/assets/dress_image_pattern2.png differ
diff --git a/ecommers/assets/electronics.svg b/ecommers/assets/electronics.svg
new file mode 100644
index 0000000..8591509
--- /dev/null
+++ b/ecommers/assets/electronics.svg
@@ -0,0 +1,9 @@
+
diff --git a/ecommers/assets/finished.svg b/ecommers/assets/finished.svg
new file mode 100644
index 0000000..383b32b
--- /dev/null
+++ b/ecommers/assets/finished.svg
@@ -0,0 +1,3 @@
+
diff --git a/ecommers/assets/furniture.svg b/ecommers/assets/furniture.svg
new file mode 100644
index 0000000..81d6491
--- /dev/null
+++ b/ecommers/assets/furniture.svg
@@ -0,0 +1,12 @@
+
diff --git a/ecommers/assets/girl2_image.png b/ecommers/assets/girl2_image.png
new file mode 100644
index 0000000..bef22b1
Binary files /dev/null and b/ecommers/assets/girl2_image.png differ
diff --git a/ecommers/assets/girl3_image.png b/ecommers/assets/girl3_image.png
new file mode 100644
index 0000000..4c8f0d2
Binary files /dev/null and b/ecommers/assets/girl3_image.png differ
diff --git a/ecommers/assets/girl_image.png b/ecommers/assets/girl_image.png
new file mode 100644
index 0000000..33ffd90
Binary files /dev/null and b/ecommers/assets/girl_image.png differ
diff --git a/ecommers/assets/green_backpack.png b/ecommers/assets/green_backpack.png
new file mode 100644
index 0000000..4439c34
Binary files /dev/null and b/ecommers/assets/green_backpack.png differ
diff --git a/ecommers/assets/home.svg b/ecommers/assets/home.svg
new file mode 100644
index 0000000..750c6bf
--- /dev/null
+++ b/ecommers/assets/home.svg
@@ -0,0 +1,9 @@
+
diff --git a/ecommers/assets/invite_friends.svg b/ecommers/assets/invite_friends.svg
new file mode 100644
index 0000000..724f1b1
--- /dev/null
+++ b/ecommers/assets/invite_friends.svg
@@ -0,0 +1,3 @@
+
diff --git a/ecommers/assets/language.svg b/ecommers/assets/language.svg
new file mode 100644
index 0000000..ab6cbfd
--- /dev/null
+++ b/ecommers/assets/language.svg
@@ -0,0 +1,4 @@
+
diff --git a/ecommers/assets/launch_icon_android.png b/ecommers/assets/launch_icon_android.png
new file mode 100644
index 0000000..123e692
Binary files /dev/null and b/ecommers/assets/launch_icon_android.png differ
diff --git a/ecommers/assets/launch_icon_ios.png b/ecommers/assets/launch_icon_ios.png
new file mode 100644
index 0000000..6ea49b0
Binary files /dev/null and b/ecommers/assets/launch_icon_ios.png differ
diff --git a/ecommers/assets/mail_icon.svg b/ecommers/assets/mail_icon.svg
new file mode 100644
index 0000000..a80db52
--- /dev/null
+++ b/ecommers/assets/mail_icon.svg
@@ -0,0 +1,14 @@
+
diff --git a/ecommers/assets/menu_arrow.svg b/ecommers/assets/menu_arrow.svg
new file mode 100644
index 0000000..6b2b892
--- /dev/null
+++ b/ecommers/assets/menu_arrow.svg
@@ -0,0 +1,9 @@
+
diff --git a/ecommers/assets/messages.svg b/ecommers/assets/messages.svg
new file mode 100644
index 0000000..2124064
--- /dev/null
+++ b/ecommers/assets/messages.svg
@@ -0,0 +1,6 @@
+
diff --git a/ecommers/assets/notifications.svg b/ecommers/assets/notifications.svg
new file mode 100644
index 0000000..28c8b7d
--- /dev/null
+++ b/ecommers/assets/notifications.svg
@@ -0,0 +1,3 @@
+
diff --git a/ecommers/assets/password_icon.svg b/ecommers/assets/password_icon.svg
new file mode 100644
index 0000000..71cc731
--- /dev/null
+++ b/ecommers/assets/password_icon.svg
@@ -0,0 +1,13 @@
+
diff --git a/ecommers/assets/payment.svg b/ecommers/assets/payment.svg
new file mode 100644
index 0000000..aa95fd9
--- /dev/null
+++ b/ecommers/assets/payment.svg
@@ -0,0 +1,29 @@
+
diff --git a/ecommers/assets/pending_payment.svg b/ecommers/assets/pending_payment.svg
new file mode 100644
index 0000000..8208281
--- /dev/null
+++ b/ecommers/assets/pending_payment.svg
@@ -0,0 +1,3 @@
+
diff --git a/ecommers/assets/pending_shipments.svg b/ecommers/assets/pending_shipments.svg
new file mode 100644
index 0000000..a3f90f7
--- /dev/null
+++ b/ecommers/assets/pending_shipments.svg
@@ -0,0 +1,3 @@
+
diff --git a/ecommers/assets/product_backpack.png b/ecommers/assets/product_backpack.png
new file mode 100644
index 0000000..6e6cabb
Binary files /dev/null and b/ecommers/assets/product_backpack.png differ
diff --git a/ecommers/assets/product_scarf.png b/ecommers/assets/product_scarf.png
new file mode 100644
index 0000000..1e188eb
Binary files /dev/null and b/ecommers/assets/product_scarf.png differ
diff --git a/ecommers/assets/product_shirt.png b/ecommers/assets/product_shirt.png
new file mode 100644
index 0000000..9f24e01
Binary files /dev/null and b/ecommers/assets/product_shirt.png differ
diff --git a/ecommers/assets/profile_icon.svg b/ecommers/assets/profile_icon.svg
new file mode 100644
index 0000000..2972971
--- /dev/null
+++ b/ecommers/assets/profile_icon.svg
@@ -0,0 +1,3 @@
+
diff --git a/ecommers/assets/progress.flr b/ecommers/assets/progress.flr
new file mode 100644
index 0000000..a4868bc
Binary files /dev/null and b/ecommers/assets/progress.flr differ
diff --git a/ecommers/assets/rate_app.svg b/ecommers/assets/rate_app.svg
new file mode 100644
index 0000000..fe04e27
--- /dev/null
+++ b/ecommers/assets/rate_app.svg
@@ -0,0 +1,3 @@
+
diff --git a/ecommers/assets/rate_star.svg b/ecommers/assets/rate_star.svg
new file mode 100644
index 0000000..d1e71ab
--- /dev/null
+++ b/ecommers/assets/rate_star.svg
@@ -0,0 +1,3 @@
+
diff --git a/ecommers/assets/remove.svg b/ecommers/assets/remove.svg
new file mode 100644
index 0000000..97272a0
--- /dev/null
+++ b/ecommers/assets/remove.svg
@@ -0,0 +1,3 @@
+
diff --git a/ecommers/assets/right_icon.svg b/ecommers/assets/right_icon.svg
new file mode 100644
index 0000000..a93c555
--- /dev/null
+++ b/ecommers/assets/right_icon.svg
@@ -0,0 +1,3 @@
+
diff --git a/ecommers/assets/sale.png b/ecommers/assets/sale.png
new file mode 100644
index 0000000..62d40b8
Binary files /dev/null and b/ecommers/assets/sale.png differ
diff --git a/ecommers/assets/share_arrow.svg b/ecommers/assets/share_arrow.svg
new file mode 100644
index 0000000..80df4af
--- /dev/null
+++ b/ecommers/assets/share_arrow.svg
@@ -0,0 +1,7 @@
+
diff --git a/ecommers/assets/shield.svg b/ecommers/assets/shield.svg
new file mode 100644
index 0000000..2c5f960
--- /dev/null
+++ b/ecommers/assets/shield.svg
@@ -0,0 +1,7 @@
+
diff --git a/ecommers/assets/shipping.svg b/ecommers/assets/shipping.svg
new file mode 100644
index 0000000..9ebd701
--- /dev/null
+++ b/ecommers/assets/shipping.svg
@@ -0,0 +1,7 @@
+
diff --git a/ecommers/assets/shoes.svg b/ecommers/assets/shoes.svg
new file mode 100644
index 0000000..35f6bf9
--- /dev/null
+++ b/ecommers/assets/shoes.svg
@@ -0,0 +1,9 @@
+
diff --git a/ecommers/assets/stationary.svg b/ecommers/assets/stationary.svg
new file mode 100644
index 0000000..e9801c3
--- /dev/null
+++ b/ecommers/assets/stationary.svg
@@ -0,0 +1,9 @@
+
diff --git a/ecommers/assets/success.svg b/ecommers/assets/success.svg
new file mode 100644
index 0000000..caf8315
--- /dev/null
+++ b/ecommers/assets/success.svg
@@ -0,0 +1,3 @@
+
diff --git a/ecommers/assets/suggest.svg b/ecommers/assets/suggest.svg
new file mode 100644
index 0000000..c02b853
--- /dev/null
+++ b/ecommers/assets/suggest.svg
@@ -0,0 +1,14 @@
+
diff --git a/ecommers/assets/support.svg b/ecommers/assets/support.svg
new file mode 100644
index 0000000..c8ce54a
--- /dev/null
+++ b/ecommers/assets/support.svg
@@ -0,0 +1,3 @@
+
diff --git a/ecommers/i18n/en-US.json b/ecommers/i18n/en-US.json
new file mode 100644
index 0000000..2519547
--- /dev/null
+++ b/ecommers/i18n/en-US.json
@@ -0,0 +1,40 @@
+{
+ "ecommers": "ecommers",
+ "titleHomePage": "ecommers Home Page",
+ "homePageTitle": "Home page",
+ "categoriesTitle": "Categories",
+ "latetstTitle": "Latest",
+ "seeAllCategoryTitle": "See All",
+ "cartTitle": "Cart",
+ "totalOrder": "TOTAL",
+ "freeDomesticShipping": "Free Domestic Shipping",
+ "checkoutButton": "CHECKOUT",
+ "editProfile": "EDIT PROFILE",
+ "logOut": "LOG OUT",
+ "morePage": "More",
+ "checkoutTitle": "Checkout",
+ "placeOrderButton": "PLACE ORDER",
+ "addPromoCode": "Add Prome Code",
+ "items": "ITEMS",
+ "paymentMethod": "PAYMENT METHOD",
+ "shippingAddress": "SHIPPING ADDRESS",
+ "cardEnding": "Master Card ending",
+ "allCategories": "All Categories",
+ "signUp": "Sign Up",
+ "logIn": "Log In",
+ "forgotPassword": "Forgot Password",
+ "email": "EMAIL",
+ "username": "USERNAME",
+ "password": "PASSWORD",
+ "usernameOrEmail": "USERNAME / EMAIL",
+ "forgotPasswordHelpText": "Enter the email address you used to create your account and we will email you a link to reset your password",
+ "successMessage": "Your order was placed successfully. For more details, check All My Orders page under Profile tab",
+ "alertTitle": "Attention",
+ "alertLoginText": "Username or password is incorrect.",
+ "loginBottomTextSpan1": "Don’t have an account? Swipe right to \n",
+ "loginBottomTextSpan2": "create a new account.",
+ "signUpBottomTextSpan1": "By creating an account, you agree to our Privacy Policy \n",
+ "signUpBottomTextSpan2": "Terms of Service",
+ "signUpBottomTextSpan3": " and ",
+ "signUpBottomTextSpan4": "Privacy Policy"
+}
\ No newline at end of file
diff --git a/ecommers/i18nconfig.json b/ecommers/i18nconfig.json
new file mode 100644
index 0000000..515b2d7
--- /dev/null
+++ b/ecommers/i18nconfig.json
@@ -0,0 +1,12 @@
+{
+ "defaultLocale": "en-US",
+ "locales": [
+ "en-US"
+ ],
+ "localePath": "i18n",
+ "generatedPath": "lib/generated",
+ "ltr": [
+ "en-US"
+ ],
+ "rtl": []
+}
\ No newline at end of file
diff --git a/ecommers/ios/Flutter/Debug.xcconfig b/ecommers/ios/Flutter/Debug.xcconfig
index 592ceee..e8efba1 100644
--- a/ecommers/ios/Flutter/Debug.xcconfig
+++ b/ecommers/ios/Flutter/Debug.xcconfig
@@ -1 +1,2 @@
+#include "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"
#include "Generated.xcconfig"
diff --git a/ecommers/ios/Flutter/Release.xcconfig b/ecommers/ios/Flutter/Release.xcconfig
index 592ceee..399e934 100644
--- a/ecommers/ios/Flutter/Release.xcconfig
+++ b/ecommers/ios/Flutter/Release.xcconfig
@@ -1 +1,2 @@
+#include "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"
#include "Generated.xcconfig"
diff --git a/ecommers/ios/Podfile b/ecommers/ios/Podfile
new file mode 100644
index 0000000..0ee4a53
--- /dev/null
+++ b/ecommers/ios/Podfile
@@ -0,0 +1,90 @@
+# Uncomment this line to define a global platform for your project
+# platform :ios, '9.0'
+
+# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
+ENV['COCOAPODS_DISABLE_STATS'] = 'true'
+
+project 'Runner', {
+ 'Debug' => :debug,
+ 'Profile' => :release,
+ 'Release' => :release,
+}
+
+def parse_KV_file(file, separator='=')
+ file_abs_path = File.expand_path(file)
+ if !File.exists? file_abs_path
+ return [];
+ end
+ generated_key_values = {}
+ skip_line_start_symbols = ["#", "/"]
+ File.foreach(file_abs_path) do |line|
+ next if skip_line_start_symbols.any? { |symbol| line =~ /^\s*#{symbol}/ }
+ plugin = line.split(pattern=separator)
+ if plugin.length == 2
+ podname = plugin[0].strip()
+ path = plugin[1].strip()
+ podpath = File.expand_path("#{path}", file_abs_path)
+ generated_key_values[podname] = podpath
+ else
+ puts "Invalid plugin specification: #{line}"
+ end
+ end
+ generated_key_values
+end
+
+target 'Runner' do
+ use_frameworks!
+ use_modular_headers!
+
+ # Flutter Pod
+
+ copied_flutter_dir = File.join(__dir__, 'Flutter')
+ copied_framework_path = File.join(copied_flutter_dir, 'Flutter.framework')
+ copied_podspec_path = File.join(copied_flutter_dir, 'Flutter.podspec')
+ unless File.exist?(copied_framework_path) && File.exist?(copied_podspec_path)
+ # Copy Flutter.framework and Flutter.podspec to Flutter/ to have something to link against if the xcode backend script has not run yet.
+ # That script will copy the correct debug/profile/release version of the framework based on the currently selected Xcode configuration.
+ # CocoaPods will not embed the framework on pod install (before any build phases can generate) if the dylib does not exist.
+
+ generated_xcode_build_settings_path = File.join(copied_flutter_dir, 'Generated.xcconfig')
+ unless File.exist?(generated_xcode_build_settings_path)
+ raise "Generated.xcconfig must exist. If you're running pod install manually, make sure flutter pub get is executed first"
+ end
+ generated_xcode_build_settings = parse_KV_file(generated_xcode_build_settings_path)
+ cached_framework_dir = generated_xcode_build_settings['FLUTTER_FRAMEWORK_DIR'];
+
+ unless File.exist?(copied_framework_path)
+ FileUtils.cp_r(File.join(cached_framework_dir, 'Flutter.framework'), copied_flutter_dir)
+ end
+ unless File.exist?(copied_podspec_path)
+ FileUtils.cp(File.join(cached_framework_dir, 'Flutter.podspec'), copied_flutter_dir)
+ end
+ end
+
+ # Keep pod path relative so it can be checked into Podfile.lock.
+ pod 'Flutter', :path => 'Flutter'
+
+ # Plugin Pods
+
+ # Prepare symlinks folder. We use symlinks to avoid having Podfile.lock
+ # referring to absolute paths on developers' machines.
+ system('rm -rf .symlinks')
+ system('mkdir -p .symlinks/plugins')
+ plugin_pods = parse_KV_file('../.flutter-plugins')
+ plugin_pods.each do |name, path|
+ symlink = File.join('.symlinks', 'plugins', name)
+ File.symlink(path, symlink)
+ pod name, :path => File.join(symlink, 'ios')
+ end
+end
+
+# Prevent Cocoapods from embedding a second Flutter framework and causing an error with the new Xcode build system.
+install! 'cocoapods', :disable_input_output_paths => true
+
+post_install do |installer|
+ installer.pods_project.targets.each do |target|
+ target.build_configurations.each do |config|
+ config.build_settings['ENABLE_BITCODE'] = 'NO'
+ end
+ end
+end
diff --git a/ecommers/ios/Runner.xcodeproj/project.pbxproj b/ecommers/ios/Runner.xcodeproj/project.pbxproj
index 296f16e..670345f 100644
--- a/ecommers/ios/Runner.xcodeproj/project.pbxproj
+++ b/ecommers/ios/Runner.xcodeproj/project.pbxproj
@@ -11,6 +11,7 @@
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
3B80C3941E831B6300D905FE /* App.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3B80C3931E831B6300D905FE /* App.framework */; };
3B80C3951E831B6300D905FE /* App.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 3B80C3931E831B6300D905FE /* App.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
+ 535525C6E0F848FD27D5F8AB /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5DA8922DECF7128B1058B009 /* Pods_Runner.framework */; };
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; };
9705A1C61CF904A100538489 /* Flutter.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9740EEBA1CF902C7004384FC /* Flutter.framework */; };
9705A1C71CF904A300538489 /* Flutter.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 9740EEBA1CF902C7004384FC /* Flutter.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
@@ -37,8 +38,12 @@
/* Begin PBXFileReference section */
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 = ""; };
+ 38A211B064793AACD4E3792C /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; };
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; };
3B80C3931E831B6300D905FE /* App.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = App.framework; path = Flutter/App.framework; sourceTree = ""; };
+ 50449812998ADB7CCC8BDDA8 /* 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 = ""; };
+ 55DE18A60853724C21844B3A /* 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 = ""; };
+ 5DA8922DECF7128B1058B009 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.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 = ""; };
@@ -59,12 +64,24 @@
files = (
9705A1C61CF904A100538489 /* Flutter.framework in Frameworks */,
3B80C3941E831B6300D905FE /* App.framework in Frameworks */,
+ 535525C6E0F848FD27D5F8AB /* Pods_Runner.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
+ 7B63FD64ECAAC859288432BB /* Pods */ = {
+ isa = PBXGroup;
+ children = (
+ 50449812998ADB7CCC8BDDA8 /* Pods-Runner.debug.xcconfig */,
+ 55DE18A60853724C21844B3A /* Pods-Runner.release.xcconfig */,
+ 38A211B064793AACD4E3792C /* Pods-Runner.profile.xcconfig */,
+ );
+ name = Pods;
+ path = Pods;
+ sourceTree = "";
+ };
9740EEB11CF90186004384FC /* Flutter */ = {
isa = PBXGroup;
children = (
@@ -84,6 +101,8 @@
9740EEB11CF90186004384FC /* Flutter */,
97C146F01CF9000F007C117D /* Runner */,
97C146EF1CF9000F007C117D /* Products */,
+ 7B63FD64ECAAC859288432BB /* Pods */,
+ D655B94E3F542BDBCD911664 /* Frameworks */,
);
sourceTree = "";
};
@@ -118,6 +137,14 @@
name = "Supporting Files";
sourceTree = "";
};
+ D655B94E3F542BDBCD911664 /* Frameworks */ = {
+ isa = PBXGroup;
+ children = (
+ 5DA8922DECF7128B1058B009 /* Pods_Runner.framework */,
+ );
+ name = Frameworks;
+ sourceTree = "";
+ };
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
@@ -125,12 +152,14 @@
isa = PBXNativeTarget;
buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */;
buildPhases = (
+ C3F9FD5412F4662F9DD13BA8 /* [CP] Check Pods Manifest.lock */,
9740EEB61CF901F6004384FC /* Run Script */,
97C146EA1CF9000F007C117D /* Sources */,
97C146EB1CF9000F007C117D /* Frameworks */,
97C146EC1CF9000F007C117D /* Resources */,
9705A1C41CF9048500538489 /* Embed Frameworks */,
3B06AD1E1E4923F5004D2608 /* Thin Binary */,
+ FB85BEE9BA8F07030175AA50 /* [CP] Embed Pods Frameworks */,
);
buildRules = (
);
@@ -217,6 +246,43 @@
shellPath = /bin/sh;
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build";
};
+ C3F9FD5412F4662F9DD13BA8 /* [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;
+ };
+ FB85BEE9BA8F07030175AA50 /* [CP] Embed Pods Frameworks */ = {
+ isa = PBXShellScriptBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ inputPaths = (
+ );
+ name = "[CP] Embed Pods Frameworks";
+ outputPaths = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ shellPath = /bin/sh;
+ shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n";
+ showEnvVarsInLog = 0;
+ };
/* End PBXShellScriptBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
diff --git a/ecommers/ios/Runner.xcworkspace/contents.xcworkspacedata b/ecommers/ios/Runner.xcworkspace/contents.xcworkspacedata
index 1d526a1..21a3cc1 100644
--- a/ecommers/ios/Runner.xcworkspace/contents.xcworkspacedata
+++ b/ecommers/ios/Runner.xcworkspace/contents.xcworkspacedata
@@ -4,4 +4,7 @@
+
+
diff --git a/ecommers/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png b/ecommers/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png
index dc9ada4..d0c1369 100644
Binary files a/ecommers/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png and b/ecommers/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png differ
diff --git a/ecommers/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png b/ecommers/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png
index 28c6bf0..861db08 100644
Binary files a/ecommers/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png and b/ecommers/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png differ
diff --git a/ecommers/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/ecommers/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png
index 2ccbfd9..e287b05 100644
Binary files a/ecommers/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png and b/ecommers/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png differ
diff --git a/ecommers/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/ecommers/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png
index f091b6b..3847dfe 100644
Binary files a/ecommers/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png and b/ecommers/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png differ
diff --git a/ecommers/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/ecommers/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png
index 4cde121..787026b 100644
Binary files a/ecommers/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png and b/ecommers/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png differ
diff --git a/ecommers/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/ecommers/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png
index d0ef06e..0e078dd 100644
Binary files a/ecommers/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png and b/ecommers/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png differ
diff --git a/ecommers/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png b/ecommers/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png
index dcdc230..b5b997d 100644
Binary files a/ecommers/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png and b/ecommers/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png differ
diff --git a/ecommers/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png b/ecommers/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png
index 2ccbfd9..e287b05 100644
Binary files a/ecommers/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png and b/ecommers/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png differ
diff --git a/ecommers/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/ecommers/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png
index c8f9ed8..874011d 100644
Binary files a/ecommers/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png and b/ecommers/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png differ
diff --git a/ecommers/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/ecommers/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png
index a6d6b86..87d9f34 100644
Binary files a/ecommers/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png and b/ecommers/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png differ
diff --git a/ecommers/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@1x.png b/ecommers/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@1x.png
new file mode 100644
index 0000000..b4d6ff1
Binary files /dev/null and b/ecommers/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@1x.png differ
diff --git a/ecommers/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@2x.png b/ecommers/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@2x.png
new file mode 100644
index 0000000..30de143
Binary files /dev/null and b/ecommers/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@2x.png differ
diff --git a/ecommers/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@1x.png b/ecommers/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@1x.png
new file mode 100644
index 0000000..7b68462
Binary files /dev/null and b/ecommers/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@1x.png differ
diff --git a/ecommers/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@2x.png b/ecommers/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@2x.png
new file mode 100644
index 0000000..a97df51
Binary files /dev/null and b/ecommers/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@2x.png differ
diff --git a/ecommers/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/ecommers/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png
index a6d6b86..87d9f34 100644
Binary files a/ecommers/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png and b/ecommers/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png differ
diff --git a/ecommers/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/ecommers/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png
index 75b2d16..4d84bed 100644
Binary files a/ecommers/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png and b/ecommers/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png differ
diff --git a/ecommers/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@1x.png b/ecommers/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@1x.png
new file mode 100644
index 0000000..f66199a
Binary files /dev/null and b/ecommers/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@1x.png differ
diff --git a/ecommers/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@2x.png b/ecommers/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@2x.png
new file mode 100644
index 0000000..cb21b5a
Binary files /dev/null and b/ecommers/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@2x.png differ
diff --git a/ecommers/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/ecommers/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png
index c4df70d..495e2f4 100644
Binary files a/ecommers/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png and b/ecommers/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png differ
diff --git a/ecommers/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png b/ecommers/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png
index 6a84f41..f5c648d 100644
Binary files a/ecommers/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png and b/ecommers/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png differ
diff --git a/ecommers/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/ecommers/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png
index d0e1f58..c606309 100644
Binary files a/ecommers/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png and b/ecommers/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png differ
diff --git a/ecommers/lib/core/app_services/auth_response.dart b/ecommers/lib/core/app_services/auth_response.dart
new file mode 100644
index 0000000..fa4720a
--- /dev/null
+++ b/ecommers/lib/core/app_services/auth_response.dart
@@ -0,0 +1,6 @@
+class AuthResponse {
+ final bool isSuccessful;
+ final String error;
+
+ AuthResponse({this.isSuccessful, this.error});
+}
diff --git a/ecommers/lib/core/app_services/authorization_service.dart b/ecommers/lib/core/app_services/authorization_service.dart
new file mode 100644
index 0000000..9c0808e
--- /dev/null
+++ b/ecommers/lib/core/app_services/authorization_service.dart
@@ -0,0 +1,38 @@
+import 'package:chopper/chopper.dart';
+import 'package:ecommers/core/app_services/auth_response.dart';
+import 'package:ecommers/core/models/login_model.dart';
+import 'package:ecommers/core/models/user_model.dart';
+import 'package:ecommers/core/services/index.dart';
+
+class AuthorizationService {
+ Future tryLogin(String username, String password) async {
+ final userJson = UserModel(username, null, password).toJson();
+
+ final Response response = await apiService.login(userJson);
+
+ if (response.isSuccessful) {
+ membershipService.refresh(response.body);
+ }
+
+ return response.isSuccessful;
+ }
+
+ Future tryAuthorize(
+ String username, String email, String password) async {
+ final userJson = UserModel(username, email, password).toJson();
+ final Response response = await apiService.auth(userJson);
+
+ if (response.isSuccessful) {
+ membershipService.refresh(response.body);
+
+ return AuthResponse(isSuccessful: response.isSuccessful);
+ }
+
+ return AuthResponse(
+ isSuccessful: response.isSuccessful, error: response.bodyString);
+ }
+
+ Future logOut() async {
+ await membershipService.clear();
+ }
+}
diff --git a/ecommers/lib/core/app_services/index.dart b/ecommers/lib/core/app_services/index.dart
new file mode 100644
index 0000000..6706775
--- /dev/null
+++ b/ecommers/lib/core/app_services/index.dart
@@ -0,0 +1 @@
+export 'authorization_service.dart';
diff --git a/ecommers/lib/core/common/api_defines.dart b/ecommers/lib/core/common/api_defines.dart
new file mode 100644
index 0000000..674974a
--- /dev/null
+++ b/ecommers/lib/core/common/api_defines.dart
@@ -0,0 +1,4 @@
+class ApiDefines {
+ static const String login = '/login';
+ static const String auth = '/auth';
+}
\ No newline at end of file
diff --git a/ecommers/lib/core/common/categories.dart b/ecommers/lib/core/common/categories.dart
new file mode 100644
index 0000000..07cb613
--- /dev/null
+++ b/ecommers/lib/core/common/categories.dart
@@ -0,0 +1,9 @@
+enum Categories {
+ apparel,
+ beauty,
+ electronics,
+ furniture,
+ home,
+ shoes,
+ stationary
+}
diff --git a/ecommers/lib/core/common/file_manager.dart b/ecommers/lib/core/common/file_manager.dart
new file mode 100644
index 0000000..549d7f7
--- /dev/null
+++ b/ecommers/lib/core/common/file_manager.dart
@@ -0,0 +1,9 @@
+import 'package:flutter/services.dart';
+
+class FileManager {
+ static const String jsonPath = 'assets/data/';
+
+ Future readJson(String fileName) async {
+ return rootBundle.loadString('$jsonPath$fileName');
+ }
+}
\ No newline at end of file
diff --git a/ecommers/lib/core/common/index.dart b/ecommers/lib/core/common/index.dart
new file mode 100644
index 0000000..bdcd749
--- /dev/null
+++ b/ecommers/lib/core/common/index.dart
@@ -0,0 +1,5 @@
+export 'api_defines.dart';
+export 'categories.dart';
+export 'file_manager.dart';
+export 'json_serializable_converter.dart';
+export 'pages.dart';
diff --git a/ecommers/lib/core/common/json_serializable_converter.dart b/ecommers/lib/core/common/json_serializable_converter.dart
new file mode 100644
index 0000000..a4aeef9
--- /dev/null
+++ b/ecommers/lib/core/common/json_serializable_converter.dart
@@ -0,0 +1,42 @@
+import 'package:chopper/chopper.dart';
+
+typedef JsonFactory = T Function(Map json);
+
+class JsonSerializableConverter extends JsonConverter {
+ final Map _factories;
+
+ const JsonSerializableConverter({Map factories})
+ : _factories = factories;
+
+ T _decodeMap(Map values) {
+ final jsonFactory = _factories[T];
+ if (jsonFactory == null || jsonFactory is! JsonFactory) {
+ return throw 'not found factory';
+ }
+
+ return jsonFactory(values) as T;
+ }
+
+ List _decodeList(List values) =>
+ values.where((v) => v != null).map((v) => _decode(v) as T).toList();
+
+ dynamic _decode(entity) {
+ if (entity is Iterable) {
+ return _decodeList(entity as List);
+ }
+
+ if (entity is Map) {
+ return _decodeMap(entity as Map);
+ }
+
+ return entity;
+ }
+
+ @override
+ Response convertResponse(Response response) {
+ final jsonRes = super.convertResponse(response);
+
+ return jsonRes.copyWith(
+ body: _decode- (jsonRes.body) as ResultType);
+ }
+}
diff --git a/ecommers/lib/core/common/pages.dart b/ecommers/lib/core/common/pages.dart
new file mode 100644
index 0000000..bfb1744
--- /dev/null
+++ b/ecommers/lib/core/common/pages.dart
@@ -0,0 +1,12 @@
+enum Pages {
+ shell,
+ home,
+ search,
+ cart,
+ profile,
+ more,
+ categories,
+ authorization,
+ checkout,
+ success
+}
\ No newline at end of file
diff --git a/ecommers/lib/core/models/auth_rich_text_span_model.dart b/ecommers/lib/core/models/auth_rich_text_span_model.dart
new file mode 100644
index 0000000..4634297
--- /dev/null
+++ b/ecommers/lib/core/models/auth_rich_text_span_model.dart
@@ -0,0 +1,11 @@
+class AuthRichTextSpanModel {
+ final String text;
+ final bool isTappable;
+ final Function() onTap;
+
+ AuthRichTextSpanModel({
+ this.text = '',
+ this.isTappable = false,
+ this.onTap,
+ });
+}
\ No newline at end of file
diff --git a/ecommers/lib/core/models/index.dart b/ecommers/lib/core/models/index.dart
new file mode 100644
index 0000000..647b372
--- /dev/null
+++ b/ecommers/lib/core/models/index.dart
@@ -0,0 +1,2 @@
+export 'auth_rich_text_span_model.dart';
+export 'order_model.dart';
diff --git a/ecommers/lib/core/models/login_model.dart b/ecommers/lib/core/models/login_model.dart
new file mode 100644
index 0000000..7f2f458
--- /dev/null
+++ b/ecommers/lib/core/models/login_model.dart
@@ -0,0 +1,21 @@
+import 'package:json_annotation/json_annotation.dart';
+
+part 'login_model.g.dart';
+
+@JsonSerializable()
+class LoginModel {
+ @JsonKey(name: 'access_token')
+ final String token;
+
+ @JsonKey(name: 'refresh_token')
+ final String refreshToken;
+
+ @JsonKey(name: 'expiration_date')
+ final String expirationDate;
+
+ LoginModel(this.token, this.refreshToken, this.expirationDate);
+
+ static const fromJsonFactory = _$LoginModelFromJson;
+
+ Map toJson() => _$LoginModelToJson(this);
+}
diff --git a/ecommers/lib/core/models/order_model.dart b/ecommers/lib/core/models/order_model.dart
new file mode 100644
index 0000000..5b49449
--- /dev/null
+++ b/ecommers/lib/core/models/order_model.dart
@@ -0,0 +1,15 @@
+class OrderModel {
+ final String title;
+ final String description;
+ final String imagePath;
+ final double cost;
+ int count;
+
+ OrderModel({
+ this.description,
+ this.cost,
+ this.imagePath,
+ this.title,
+ this.count,
+ });
+}
diff --git a/ecommers/lib/core/models/user_model.dart b/ecommers/lib/core/models/user_model.dart
new file mode 100644
index 0000000..5a685d9
--- /dev/null
+++ b/ecommers/lib/core/models/user_model.dart
@@ -0,0 +1,21 @@
+import 'package:json_annotation/json_annotation.dart';
+
+part 'user_model.g.dart';
+
+@JsonSerializable()
+class UserModel {
+ @JsonKey(name: 'username')
+ final String username;
+
+ @JsonKey(name: 'email')
+ final String email;
+
+ @JsonKey(name: 'password')
+ final String password;
+
+ UserModel(this.username, this.email, this.password);
+
+ static const fromJsonFactory = _$UserModelFromJson;
+
+ Map toJson() => _$UserModelToJson(this);
+}
\ No newline at end of file
diff --git a/ecommers/lib/core/provider_models/index.dart b/ecommers/lib/core/provider_models/index.dart
new file mode 100644
index 0000000..2207739
--- /dev/null
+++ b/ecommers/lib/core/provider_models/index.dart
@@ -0,0 +1 @@
+export 'shell_provider_model.dart';
diff --git a/ecommers/lib/core/provider_models/log_in_provider_model.dart b/ecommers/lib/core/provider_models/log_in_provider_model.dart
new file mode 100644
index 0000000..8148f7f
--- /dev/null
+++ b/ecommers/lib/core/provider_models/log_in_provider_model.dart
@@ -0,0 +1,45 @@
+import 'package:ecommers/core/common/index.dart';
+import 'package:ecommers/core/models/index.dart';
+import 'package:ecommers/core/provider_models/provider_model_base.dart';
+import 'package:ecommers/core/services/index.dart';
+import 'package:ecommers/ui/utils/dialog_manager.dart';
+import 'package:flutter/material.dart';
+
+class LogInProviderModel extends ProviderModelBase {
+ String username;
+ String password;
+
+ List _bottomText;
+ List get bottomText => _getBottomText();
+
+ LogInProviderModel(BuildContext context) : super(context);
+
+ Future tryLogin() async {
+ isBusy = true;
+
+ final isSuccessful =
+ await authorizationService.tryLogin(username, password);
+
+ isBusy = false;
+
+ if (isSuccessful) {
+ await navigationService.navigateWithReplacementTo(Pages.shell);
+ } else {
+ await DialogManager.showAlertDialog(
+ context, localization.alertTitle, localization.alertLoginText);
+ }
+ }
+
+ List _getBottomText() {
+ return _bottomText ??= [
+ AuthRichTextSpanModel(
+ text: localization.loginBottomTextSpan1,
+ isTappable: false,
+ ),
+ AuthRichTextSpanModel(
+ text: localization.loginBottomTextSpan2,
+ isTappable: true,
+ ),
+ ];
+ }
+}
diff --git a/ecommers/lib/core/provider_models/main_provider.dart b/ecommers/lib/core/provider_models/main_provider.dart
new file mode 100644
index 0000000..5467f8a
--- /dev/null
+++ b/ecommers/lib/core/provider_models/main_provider.dart
@@ -0,0 +1,13 @@
+import 'package:ecommers/core/provider_models/provider_model_base.dart';
+import 'package:ecommers/core/services/index.dart';
+import 'package:flutter/material.dart';
+
+class MainProviderModel extends ProviderModelBase {
+ MainProviderModel(BuildContext context) : super(context);
+
+ Future initialize() async {
+ isBusy = true;
+ await membershipService.load();
+ isBusy = false;
+ }
+}
diff --git a/ecommers/lib/core/provider_models/provider_model_base.dart b/ecommers/lib/core/provider_models/provider_model_base.dart
new file mode 100644
index 0000000..54660d4
--- /dev/null
+++ b/ecommers/lib/core/provider_models/provider_model_base.dart
@@ -0,0 +1,20 @@
+import 'package:ecommers/generated/i18n.dart';
+import 'package:flutter/material.dart';
+
+abstract class ProviderModelBase extends ChangeNotifier {
+ @protected
+ final I18n localization;
+
+ final BuildContext context;
+
+ bool _isBusy = false;
+
+ bool get isBusy => _isBusy;
+
+ set isBusy(bool isBusy) {
+ _isBusy = isBusy;
+ notifyListeners();
+ }
+
+ ProviderModelBase(this.context) : localization = I18n.of(context);
+}
diff --git a/ecommers/lib/core/provider_models/shell_provider_model.dart b/ecommers/lib/core/provider_models/shell_provider_model.dart
new file mode 100644
index 0000000..84ec4ee
--- /dev/null
+++ b/ecommers/lib/core/provider_models/shell_provider_model.dart
@@ -0,0 +1,21 @@
+import 'package:ecommers/core/common/index.dart';
+import 'package:flutter/material.dart';
+
+class ShellProviderModel with ChangeNotifier {
+ final List pages = [
+ Pages.home,
+ Pages.search,
+ Pages.cart,
+ Pages.profile,
+ Pages.more
+ ];
+
+ int selectedItemIndex = 0;
+
+ Pages get selectedPage => pages[selectedItemIndex];
+
+ void onTappedItem(int index) {
+ selectedItemIndex = index;
+ notifyListeners();
+ }
+}
diff --git a/ecommers/lib/core/provider_models/sign_up_provider_model.dart b/ecommers/lib/core/provider_models/sign_up_provider_model.dart
new file mode 100644
index 0000000..77a0a0e
--- /dev/null
+++ b/ecommers/lib/core/provider_models/sign_up_provider_model.dart
@@ -0,0 +1,55 @@
+import 'package:ecommers/core/common/index.dart';
+import 'package:ecommers/core/models/index.dart';
+import 'package:ecommers/core/provider_models/provider_model_base.dart';
+import 'package:ecommers/core/services/dependency_service.dart';
+import 'package:ecommers/ui/utils/dialog_manager.dart';
+import 'package:flutter/material.dart';
+
+class SignUpProviderModel extends ProviderModelBase {
+ String email;
+ String username;
+ String password;
+
+ List _bottomText;
+
+ List get bottomText => _getBottomText();
+
+ SignUpProviderModel(BuildContext context) : super(context);
+
+ List _getBottomText() {
+ return _bottomText ??= [
+ AuthRichTextSpanModel(
+ text: localization.signUpBottomTextSpan1,
+ isTappable: false,
+ ),
+ AuthRichTextSpanModel(
+ text: localization.signUpBottomTextSpan2,
+ isTappable: true,
+ ),
+ AuthRichTextSpanModel(
+ text: localization.signUpBottomTextSpan3,
+ isTappable: false,
+ ),
+ AuthRichTextSpanModel(
+ text: localization.signUpBottomTextSpan4,
+ isTappable: true,
+ ),
+ ];
+ }
+
+ Future tryAuthorize() async {
+ isBusy = true;
+
+ final authResponse =
+ await authorizationService.tryAuthorize(username, email, password);
+
+ isBusy = false;
+
+ if (authResponse.isSuccessful) {
+ await navigationService.navigateWithReplacementTo(Pages.shell);
+ } else {
+ await DialogManager.showAlertDialog(
+ context, localization.alertTitle, authResponse.error);
+ }
+ }
+}
diff --git a/ecommers/lib/core/services/api_service.dart b/ecommers/lib/core/services/api_service.dart
new file mode 100644
index 0000000..2d1a231
--- /dev/null
+++ b/ecommers/lib/core/services/api_service.dart
@@ -0,0 +1,16 @@
+import 'package:chopper/chopper.dart';
+import 'package:ecommers/core/common/index.dart';
+import 'package:ecommers/core/models/login_model.dart';
+
+part 'api_service.chopper.dart';
+
+@ChopperApi(baseUrl: '')
+abstract class ApiService extends ChopperService {
+ static ApiService create([ChopperClient client]) => _$ApiService(client);
+
+ @Post(path: ApiDefines.login)
+ Future> login(@Body() Map login);
+
+ @Post(path: ApiDefines.auth)
+ Future> auth(@Body() Map auth);
+}
diff --git a/ecommers/lib/core/services/dependency_service.dart b/ecommers/lib/core/services/dependency_service.dart
new file mode 100644
index 0000000..52b5199
--- /dev/null
+++ b/ecommers/lib/core/services/dependency_service.dart
@@ -0,0 +1,31 @@
+import 'package:ecommers/core/app_services/index.dart';
+import 'package:ecommers/core/common/file_manager.dart';
+import 'package:ecommers/core/services/api_service.dart';
+import 'package:ecommers/core/services/membership_service.dart';
+import 'package:ecommers/core/services/navigation/navigation_service.dart';
+import 'package:ecommers/web_server/request_handler.dart';
+import 'package:flutter_secure_storage/flutter_secure_storage.dart';
+import 'package:get_it/get_it.dart';
+import 'package:ecommers/core/services/extensions/get_it_extension.dart';
+
+NavigationService get navigationService => GetIt.I.get();
+FileManager get fileManager => GetIt.I.get();
+ApiService get apiService => GetIt.I.get();
+MembershipService get membershipService => GetIt.I.get();
+RequestHandler get requestHandler => GetIt.I.get();
+AuthorizationService get authorizationService => GetIt.I.get();
+
+class DependencyService {
+ static void registerDependencies() {
+ final GetIt serviceLocator = GetIt.instance;
+
+ serviceLocator
+ ..registerLazySingleton(() => NavigationService())
+ ..registerLazySingleton(() => FileManager())
+ ..registerLazySingleton(() => RequestHandler())
+ ..registerLazySingleton(() => AuthorizationService())
+ ..registerLazySingleton(
+ () => MembershipService(const FlutterSecureStorage()))
+ ..registerHttpClient();
+ }
+}
diff --git a/ecommers/lib/core/services/extensions/get_it_extension.dart b/ecommers/lib/core/services/extensions/get_it_extension.dart
new file mode 100644
index 0000000..dad5c89
--- /dev/null
+++ b/ecommers/lib/core/services/extensions/get_it_extension.dart
@@ -0,0 +1,24 @@
+import 'package:chopper/chopper.dart';
+import 'package:ecommers/core/common/json_serializable_converter.dart';
+import 'package:ecommers/core/models/login_model.dart';
+import 'package:ecommers/core/services/api_service.dart';
+import 'package:ecommers/web_server/local_server.dart';
+import 'package:get_it/get_it.dart';
+
+extension GetItExtension on GetIt {
+ void registerHttpClient() {
+ final chopper = ChopperClient(
+ baseUrl: LocalServer.uri.origin,
+ services: [
+ ApiService.create(),
+ ],
+ converter: const JsonSerializableConverter(
+ factories: {
+ LoginModel: LoginModel.fromJsonFactory,
+ },
+ ),
+ );
+
+ registerLazySingleton(() => ApiService.create(chopper));
+ }
+}
diff --git a/ecommers/lib/core/services/index.dart b/ecommers/lib/core/services/index.dart
new file mode 100644
index 0000000..1cae73a
--- /dev/null
+++ b/ecommers/lib/core/services/index.dart
@@ -0,0 +1 @@
+export 'dependency_service.dart';
diff --git a/ecommers/lib/core/services/membership_service.dart b/ecommers/lib/core/services/membership_service.dart
new file mode 100644
index 0000000..569ddad
--- /dev/null
+++ b/ecommers/lib/core/services/membership_service.dart
@@ -0,0 +1,66 @@
+import 'package:ecommers/core/models/login_model.dart';
+import 'package:flutter_secure_storage/flutter_secure_storage.dart';
+
+class MembershipService {
+ static const String _accessTokenKey = 'accessTokenKey';
+ static const String _refreshTokenKey = 'refreshTokenKey';
+ static const String _expirationDateKey = 'expirationDateKey';
+
+ final FlutterSecureStorage secureStorage;
+
+ String _accessToken;
+ String _refreshToken;
+ DateTime _expirationDate;
+
+ String get accessToken => _accessToken;
+ String get refreshToken => _refreshToken;
+ bool get isNotExpired =>
+ _expirationDate != null && DateTime.now().isBefore(_expirationDate);
+
+ MembershipService(this.secureStorage);
+
+ Future refresh(LoginModel loginModel) async {
+ _accessToken = loginModel.token;
+ _refreshToken = loginModel.refreshToken;
+ _expirationDate = DateTime.parse(loginModel.expirationDate);
+
+ await _saveToStorage(loginModel);
+ }
+
+ Future clear() async {
+ _accessToken = null;
+ _refreshToken = null;
+ _expirationDate = null;
+
+ await _clearStorage();
+ }
+
+ Future _clearStorage() async {
+ await Future.wait(
+ {
+ secureStorage.delete(key: _accessTokenKey),
+ secureStorage.delete(key: _refreshTokenKey),
+ secureStorage.delete(key: _expirationDateKey),
+ },
+ );
+ }
+
+ Future load() async {
+ _accessToken = await secureStorage.read(key: _accessTokenKey);
+ _refreshToken = await secureStorage.read(key: _refreshTokenKey);
+ _expirationDate = DateTime.tryParse(
+ await secureStorage.read(key: _expirationDateKey) ?? '');
+ }
+
+ Future _saveToStorage(LoginModel loginModel) async {
+ await Future.wait(
+ {
+ secureStorage.write(key: _accessTokenKey, value: loginModel.token),
+ secureStorage.write(
+ key: _refreshTokenKey, value: loginModel.refreshToken),
+ secureStorage.write(
+ key: _expirationDateKey, value: loginModel.expirationDate),
+ },
+ );
+ }
+}
diff --git a/ecommers/lib/core/services/navigation/navigation_service.dart b/ecommers/lib/core/services/navigation/navigation_service.dart
new file mode 100644
index 0000000..cab0585
--- /dev/null
+++ b/ecommers/lib/core/services/navigation/navigation_service.dart
@@ -0,0 +1,54 @@
+import 'package:ecommers/core/common/index.dart';
+import 'package:ecommers/ui/pages/authorization/index.dart';
+import 'package:ecommers/ui/pages/index.dart';
+import 'package:flutter/cupertino.dart';
+import 'package:flutter/material.dart';
+
+class NavigationService {
+ final GlobalKey navigatorKey = GlobalKey();
+
+ Future navigateTo(Pages page, {Object arguments}) {
+ final route = _generateRoute(page, arguments);
+ return navigatorKey.currentState.push(route);
+ }
+
+ Future navigateWithReplacementTo(Pages page, {Object arguments}) {
+ final route = _generateRoute(page, arguments);
+ return navigatorKey.currentState.pushReplacement(route);
+ }
+
+ void goBack() {
+ navigatorKey.currentState.pop();
+ }
+
+ Route _generateRoute(Pages page, Object arguments) {
+ Widget resultPage;
+
+ switch (page) {
+ case Pages.shell:
+ resultPage = ShellPage();
+ break;
+ case Pages.categories:
+ resultPage = const CategoriesPage();
+ break;
+ case Pages.authorization:
+ resultPage = const AuthorizationPage();
+ break;
+ case Pages.checkout:
+ resultPage = CheckoutPage();
+ break;
+ case Pages.success:
+ resultPage = const SuccessPage();
+ break;
+ default:
+ resultPage = ShellPage();
+ break;
+ }
+
+ return _getRoute(resultPage);
+ }
+
+ Route _getRoute(Widget widget) {
+ return CupertinoPageRoute(builder: (_) => widget);
+ }
+}
diff --git a/ecommers/lib/extensions/string_extension.dart b/ecommers/lib/extensions/string_extension.dart
new file mode 100644
index 0000000..4d9682b
--- /dev/null
+++ b/ecommers/lib/extensions/string_extension.dart
@@ -0,0 +1,3 @@
+extension StringExtension on String {
+ bool get isNullOrEmpty => this == null || isEmpty;
+}
\ No newline at end of file
diff --git a/ecommers/lib/generated/i18n.dart b/ecommers/lib/generated/i18n.dart
new file mode 100644
index 0000000..1347d6c
--- /dev/null
+++ b/ecommers/lib/generated/i18n.dart
@@ -0,0 +1,167 @@
+import 'dart:async';
+
+import 'package:flutter/foundation.dart';
+import 'package:flutter/material.dart';
+// ignore_for_file: non_constant_identifier_names
+// ignore_for_file: camel_case_types
+// ignore_for_file: prefer_single_quotes
+// ignore_for_file: unnecessary_brace_in_string_interps
+
+//WARNING: This file is automatically generated. DO NOT EDIT, all your changes would be lost.
+
+typedef LocaleChangeCallback = void Function(Locale locale);
+
+class I18n implements WidgetsLocalizations {
+ const I18n();
+ static Locale _locale;
+ static bool _shouldReload = false;
+
+ static set locale(Locale newLocale) {
+ _shouldReload = true;
+ I18n._locale = newLocale;
+ }
+
+ static const GeneratedLocalizationsDelegate delegate = GeneratedLocalizationsDelegate();
+
+ /// function to be invoked when changing the language
+ static LocaleChangeCallback onLocaleChanged;
+
+ static I18n of(BuildContext context) =>
+ Localizations.of(context, WidgetsLocalizations);
+
+ @override
+ TextDirection get textDirection => TextDirection.ltr;
+
+ /// "ecommers"
+ String get ecommers => "ecommers";
+ /// "ecommers Home Page"
+ String get titleHomePage => "ecommers Home Page";
+ /// "Home page"
+ String get homePageTitle => "Home page";
+ /// "Categories"
+ String get categoriesTitle => "Categories";
+ /// "Latest"
+ String get latetstTitle => "Latest";
+ /// "See All"
+ String get seeAllCategoryTitle => "See All";
+ /// "Cart"
+ String get cartTitle => "Cart";
+ /// "TOTAL"
+ String get totalOrder => "TOTAL";
+ /// "Free Domestic Shipping"
+ String get freeDomesticShipping => "Free Domestic Shipping";
+ /// "CHECKOUT"
+ String get checkoutButton => "CHECKOUT";
+ /// "EDIT PROFILE"
+ String get editProfile => "EDIT PROFILE";
+ /// "LOG OUT"
+ String get logOut => "LOG OUT";
+ /// "More"
+ String get morePage => "More";
+ /// "Checkout"
+ String get checkoutTitle => "Checkout";
+ /// "PLACE ORDER"
+ String get placeOrderButton => "PLACE ORDER";
+ /// "Add Prome Code"
+ String get addPromoCode => "Add Prome Code";
+ /// "ITEMS"
+ String get items => "ITEMS";
+ /// "PAYMENT METHOD"
+ String get paymentMethod => "PAYMENT METHOD";
+ /// "SHIPPING ADDRESS"
+ String get shippingAddress => "SHIPPING ADDRESS";
+ /// "Master Card ending"
+ String get cardEnding => "Master Card ending";
+ /// "All Categories"
+ String get allCategories => "All Categories";
+ /// "Sign Up"
+ String get signUp => "Sign Up";
+ /// "Log In"
+ String get logIn => "Log In";
+ /// "Forgot Password"
+ String get forgotPassword => "Forgot Password";
+ /// "EMAIL"
+ String get email => "EMAIL";
+ /// "USERNAME"
+ String get username => "USERNAME";
+ /// "PASSWORD"
+ String get password => "PASSWORD";
+ /// "USERNAME / EMAIL"
+ String get usernameOrEmail => "USERNAME / EMAIL";
+ /// "Enter the email address you used to create your account and we will email you a link to reset your password"
+ String get forgotPasswordHelpText => "Enter the email address you used to create your account and we will email you a link to reset your password";
+ /// "Your order was placed successfully. For more details, check All My Orders page under Profile tab"
+ String get successMessage => "Your order was placed successfully. For more details, check All My Orders page under Profile tab";
+ /// "Attention"
+ String get alertTitle => "Attention";
+ /// "Username or password is incorrect."
+ String get alertLoginText => "Username or password is incorrect.";
+ /// "Don’t have an account? Swipe right to \n"
+ String get loginBottomTextSpan1 => "Don’t have an account? Swipe right to \n";
+ /// "create a new account."
+ String get loginBottomTextSpan2 => "create a new account.";
+ /// "By creating an account, you agree to our Privacy Policy \n"
+ String get signUpBottomTextSpan1 => "By creating an account, you agree to our Privacy Policy \n";
+ /// "Terms of Service"
+ String get signUpBottomTextSpan2 => "Terms of Service";
+ /// " and "
+ String get signUpBottomTextSpan3 => " and ";
+ /// "Privacy Policy"
+ String get signUpBottomTextSpan4 => "Privacy Policy";
+}
+
+class _I18n_en_US extends I18n {
+ const _I18n_en_US();
+
+ @override
+ TextDirection get textDirection => TextDirection.ltr;
+}
+
+class GeneratedLocalizationsDelegate extends LocalizationsDelegate {
+ const GeneratedLocalizationsDelegate();
+ List get supportedLocales {
+ return const [
+ Locale("en", "US")
+ ];
+ }
+
+ LocaleResolutionCallback resolution({Locale fallback}) {
+ return (Locale locale, Iterable supported) {
+ if (isSupported(locale)) {
+ return locale;
+ }
+ final Locale fallbackLocale = fallback ?? supported.first;
+ return fallbackLocale;
+ };
+ }
+
+ @override
+ Future load(Locale locale) {
+ I18n._locale ??= locale;
+ I18n._shouldReload = false;
+ final String lang = I18n._locale != null ? I18n._locale.toString() : "";
+ final String languageCode = I18n._locale != null ? I18n._locale.languageCode : "";
+ if ("en_US" == lang) {
+ return SynchronousFuture(const _I18n_en_US());
+ }
+ else if ("en" == languageCode) {
+ return SynchronousFuture(const _I18n_en_US());
+ }
+
+ return SynchronousFuture(const I18n());
+ }
+
+ @override
+ bool isSupported(Locale locale) {
+ for (var i = 0; i < supportedLocales.length && locale != null; i++) {
+ final l = supportedLocales[i];
+ if (l.languageCode == locale.languageCode) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @override
+ bool shouldReload(GeneratedLocalizationsDelegate old) => I18n._shouldReload;
+}
\ No newline at end of file
diff --git a/ecommers/lib/main.dart b/ecommers/lib/main.dart
index 5a7af45..eb451a7 100644
--- a/ecommers/lib/main.dart
+++ b/ecommers/lib/main.dart
@@ -1,111 +1,80 @@
+import 'package:ecommers/core/provider_models/main_provider.dart';
+import 'package:ecommers/core/services/index.dart';
+import 'package:ecommers/generated/i18n.dart';
+import 'package:ecommers/ui/decorations/index.dart';
+import 'package:ecommers/ui/pages/authorization/authorization_page.dart';
+import 'package:ecommers/ui/pages/index.dart';
+import 'package:ecommers/ui/widgets/progress.dart';
+import 'package:ecommers/web_server/local_server.dart';
import 'package:flutter/material.dart';
+import 'package:provider/provider.dart';
-void main() => runApp(MyApp());
+void main() {
+ runApp(MainApp());
+ DependencyService.registerDependencies();
+}
-class MyApp extends StatelessWidget {
- // This widget is the root of your application.
+class MainApp extends StatefulWidget {
@override
- Widget build(BuildContext context) {
- return MaterialApp(
- title: 'Flutter Demo',
- theme: ThemeData(
- // This is the theme of your application.
- //
- // Try running your application with "flutter run". You'll see the
- // application has a blue toolbar. Then, without quitting the app, try
- // changing the primarySwatch below to Colors.green and then invoke
- // "hot reload" (press "r" in the console where you ran "flutter run",
- // or simply save your changes to "hot reload" in a Flutter IDE).
- // Notice that the counter didn't reset back to zero; the application
- // is not restarted.
- primarySwatch: Colors.blue,
- ),
- home: MyHomePage(title: 'Flutter Demo Home Page'),
- );
- }
+ _MainAppState createState() => _MainAppState();
}
-class MyHomePage extends StatefulWidget {
- MyHomePage({Key key, this.title}) : super(key: key);
-
- // This widget is the home page of your application. It is stateful, meaning
- // that it has a State object (defined below) that contains fields that affect
- // how it looks.
-
- // This class is the configuration for the state. It holds the values (in this
- // case the title) provided by the parent (in this case the App widget) and
- // used by the build method of the State. Fields in a Widget subclass are
- // always marked "final".
+class _MainAppState extends State with WidgetsBindingObserver {
+ final GeneratedLocalizationsDelegate i18n = I18n.delegate;
- final String title;
+ @override
+ void initState() {
+ WidgetsBinding.instance.addObserver(this);
+ LocalServer.setup();
+ super.initState();
+ }
@override
- _MyHomePageState createState() => _MyHomePageState();
-}
+ void dispose() {
+ WidgetsBinding.instance.removeObserver(this);
+ super.dispose();
+ }
-class _MyHomePageState extends State {
- int _counter = 0;
+ @override
+ void didChangeAppLifecycleState(AppLifecycleState state) {
+ if (state == AppLifecycleState.resumed) {
+ LocalServer.setup();
+ } else if (state == AppLifecycleState.detached ||
+ state == AppLifecycleState.inactive) {
+ LocalServer.closeConnection();
+ }
- void _incrementCounter() {
- setState(() {
- // This call to setState tells the Flutter framework that something has
- // changed in this State, which causes it to rerun the build method below
- // so that the display can reflect the updated values. If we changed
- // _counter without calling setState(), then the build method would not be
- // called again, and so nothing would appear to happen.
- _counter++;
- });
+ super.didChangeAppLifecycleState(state);
}
@override
Widget build(BuildContext context) {
- // This method is rerun every time setState is called, for instance as done
- // by the _incrementCounter method above.
- //
- // The Flutter framework has been optimized to make rerunning build methods
- // fast, so that you can just rebuild anything that needs updating rather
- // than having to individually change instances of widgets.
- return Scaffold(
- appBar: AppBar(
- // Here we take the value from the MyHomePage object that was created by
- // the App.build method, and use it to set our appbar title.
- title: Text(widget.title),
- ),
- body: Center(
- // Center is a layout widget. It takes a single child and positions it
- // in the middle of the parent.
- child: Column(
- // Column is also a layout widget. It takes a list of children and
- // arranges them vertically. By default, it sizes itself to fit its
- // children horizontally, and tries to be as tall as its parent.
- //
- // Invoke "debug painting" (press "p" in the console, choose the
- // "Toggle Debug Paint" action from the Flutter Inspector in Android
- // Studio, or the "Toggle Debug Paint" command in Visual Studio Code)
- // to see the wireframe for each widget.
- //
- // Column has various properties to control how it sizes itself and
- // how it positions its children. Here we use mainAxisAlignment to
- // center the children vertically; the main axis here is the vertical
- // axis because Columns are vertical (the cross axis would be
- // horizontal).
- mainAxisAlignment: MainAxisAlignment.center,
- children: [
- Text(
- 'You have pushed the button this many times:',
- ),
- Text(
- '$_counter',
- style: Theme.of(context).textTheme.display1,
- ),
- ],
+ return ChangeNotifierProvider(
+ create: (BuildContext context) =>
+ MainProviderModel(context)..initialize(),
+ child: MaterialApp(
+ title: 'ecommers',
+ theme: ThemeProvider.getTheme(),
+ home: Consumer(
+ builder: (_, MainProviderModel provider, __) {
+ if (provider.isBusy) {
+ return Container(
+ color: BrandingColors.pageBackground,
+ child: const Center(child: Progress()),
+ );
+ }
+ return membershipService.isNotExpired
+ ? ShellPage()
+ : const AuthorizationPage();
+ },
+ ),
+ navigatorKey: navigationService.navigatorKey,
+ localizationsDelegates: [i18n],
+ supportedLocales: i18n.supportedLocales,
+ localeResolutionCallback: i18n.resolution(
+ fallback: const Locale('en', 'US'),
),
),
- floatingActionButton: FloatingActionButton(
- onPressed: _incrementCounter,
- tooltip: 'Increment',
- child: Icon(Icons.add),
- ), // This trailing comma makes auto-formatting nicer for build methods.
);
}
}
diff --git a/ecommers/lib/ui/decorations/assets.dart b/ecommers/lib/ui/decorations/assets.dart
new file mode 100644
index 0000000..b50383e
--- /dev/null
+++ b/ecommers/lib/ui/decorations/assets.dart
@@ -0,0 +1,59 @@
+class Assets {
+ static const String apparelIcon = 'assets/apparel.svg';
+ static const String beautyIcon = 'assets/beauty.svg';
+ static const String electronicsIcon = 'assets/electronics.svg';
+ static const String furnitureIcon = 'assets/furniture.svg';
+ static const String homeIcon = 'assets/home.svg';
+ static const String shoesIcon = 'assets/shoes.svg';
+ static const String stationaryIcon = 'assets/stationary.svg';
+ static const String arrowRightIcon = 'assets/arrow_right.svg';
+ static const String shareArrowIcon = 'assets/share_arrow.svg';
+ static const String rateStarIcon = 'assets/rate_star.svg';
+ static const String allOrderIcon = 'assets/all_order.svg';
+ static const String bellIcon = 'assets/bell.svg';
+ static const String checkFormIcon = 'assets/check_form.svg';
+ static const String currencyIcon = 'assets/currency.svg';
+ static const String discussIssueIcon = 'assets/discuss_issue.svg';
+ static const String finishedOrdersIcon = 'assets/finished.svg';
+ static const String inviteFriendsIcon = 'assets/invite_friends.svg';
+ static const String languageIcon = 'assets/language.svg';
+ static const String paymentIcon = 'assets/payment.svg';
+ static const String pendingPaymentIcon = 'assets/pending_payment.svg';
+ static const String pendingShipmentIcon = 'assets/pending_shipments.svg';
+ static const String rateAppIcon = 'assets/rate_app.svg';
+ static const String shieldIcon = 'assets/shield.svg';
+ static const String shippingIcon = 'assets/shipping.svg';
+ static const String suggestIcon = 'assets/suggest.svg';
+ static const String supportIcon = 'assets/support.svg';
+ static const String menuArrowIcon = 'assets/menu_arrow.svg';
+ static const String messagesIcon = 'assets/messages.svg';
+ static const String notificationIcon = 'assets/notifications.svg';
+ static const String arrowIcon = 'assets/right_icon.svg';
+ static const String substractIcon = 'assets/remove.svg';
+ static const String addIcon = 'assets/add.svg';
+ static const String closeIcon = 'assets/close_icon.svg';
+ static const String successIcon = 'assets/success.svg';
+ static const String mailIcon = 'assets/mail_icon.svg';
+ static const String profileIcon = 'assets/profile_icon.svg';
+ static const String passwordIcon = 'assets/password_icon.svg';
+
+ static const String girlImage = 'assets/girl_image.png';
+ static const String girl2Image = 'assets/girl2_image.png';
+ static const String girl3Image = 'assets/girl3_image.png';
+ static const String shirtImage = 'assets/product_shirt.png';
+ static const String backpackImage = 'assets/product_backpack.png';
+ static const String scarfImage = 'assets/product_scarf.png';
+ static const String greenBackpackImage = 'assets/green_backpack.png';
+
+ static const String creditCardImage = 'assets/credit_card.png';
+ static const String saleImage = 'assets/sale.png';
+
+ static const String dressCottonImage = 'assets/dress_image_cotton.png';
+ static const String dressCotton2Image = 'assets/dress_image_cotton2.png';
+ static const String dressFloralImage = 'assets/dress_image_floral.png';
+ static const String dressFloral2Image = 'assets/dress_image_floral2.png';
+ static const String dressPatternImage = 'assets/dress_image_pattern.png';
+ static const String dressPattern2Image = 'assets/dress_image_pattern2.png';
+
+ static const String progressAnimation = 'assets/progress.flr';
+}
diff --git a/ecommers/lib/ui/decorations/branding_colors.dart b/ecommers/lib/ui/decorations/branding_colors.dart
new file mode 100644
index 0000000..27f1d77
--- /dev/null
+++ b/ecommers/lib/ui/decorations/branding_colors.dart
@@ -0,0 +1,12 @@
+import 'package:flutter/material.dart';
+
+class BrandingColors {
+ static const Color background = Color(0xffffffff);
+ static const Color pageBackground = Color(0xffF5F6F8);
+ static const Color blur = Color(0xffE7EAF0);
+ static const Color backgroundIcon = Color(0xffDBDEE2);
+ static const Color primaryText = Color(0xff515C6F);
+ static const Color secondaryText = Color(0xffffffff);
+ static const Color primary = Color(0xFFFF6969);
+ static const Color secondary = Color(0xFF727C8E);
+}
\ No newline at end of file
diff --git a/ecommers/lib/ui/decorations/dimens/dimens.dart b/ecommers/lib/ui/decorations/dimens/dimens.dart
new file mode 100644
index 0000000..b5c8262
--- /dev/null
+++ b/ecommers/lib/ui/decorations/dimens/dimens.dart
@@ -0,0 +1,11 @@
+import 'dart:ui';
+
+import 'insets.dart';
+
+class Dimens {
+ static const int defaultTextMaxLines = 1;
+
+ static const Offset defaultBlurOffset = Offset(Insets.x0, Insets.x2);
+
+ static const double pagePadding = Insets.x6;
+}
diff --git a/ecommers/lib/ui/decorations/dimens/font_sizes.dart b/ecommers/lib/ui/decorations/dimens/font_sizes.dart
new file mode 100644
index 0000000..b88a653
--- /dev/null
+++ b/ecommers/lib/ui/decorations/dimens/font_sizes.dart
@@ -0,0 +1,10 @@
+class FontSizes {
+ static const small_1x = 10.0;
+ static const small_2x = 11.0;
+ static const small_3x = 12.0;
+ static const normal = 15.0;
+ static const big_1x = 18.0;
+ static const big_2x = 20.0;
+ static const big_3x = 22.0;
+ static const big_4x = 30.0;
+}
\ No newline at end of file
diff --git a/ecommers/lib/ui/decorations/dimens/index.dart b/ecommers/lib/ui/decorations/dimens/index.dart
new file mode 100644
index 0000000..e3fd207
--- /dev/null
+++ b/ecommers/lib/ui/decorations/dimens/index.dart
@@ -0,0 +1,4 @@
+export 'dimens.dart';
+export 'font_sizes.dart';
+export 'insets.dart';
+export 'radiuses.dart';
diff --git a/ecommers/lib/ui/decorations/dimens/insets.dart b/ecommers/lib/ui/decorations/dimens/insets.dart
new file mode 100644
index 0000000..53de4b2
--- /dev/null
+++ b/ecommers/lib/ui/decorations/dimens/insets.dart
@@ -0,0 +1,20 @@
+class Insets {
+ static const x0 = 0.0;
+ static const x0_5 = 2.0;
+ static const x1 = 4.0;
+ static const x1_5 = 6.0;
+ static const x2 = 8.0;
+ static const x2_5 = 10.0;
+ static const x3 = 12.0;
+ static const x3_5 = 14.0;
+ static const x4 = 16.0;
+ static const x4_5 = 18.0;
+ static const x5 = 20.0;
+ static const x5_5 = 22.0;
+ static const x6 = 24.0;
+ static const x6_5 = 26.0;
+ static const x7 = 28.0;
+ static const x7_5 = 30.0;
+ static const x8 = 32.0;
+ static const x8_5 = 34.0;
+}
\ No newline at end of file
diff --git a/ecommers/lib/ui/decorations/dimens/radiuses.dart b/ecommers/lib/ui/decorations/dimens/radiuses.dart
new file mode 100644
index 0000000..3aa3e9f
--- /dev/null
+++ b/ecommers/lib/ui/decorations/dimens/radiuses.dart
@@ -0,0 +1,7 @@
+class Radiuses {
+ static const small_1x = 5.0;
+ static const small_2x = 7.0;
+ static const normal = 10.0;
+ static const big_1x = 15.0;
+ static const big_2x = 24.0;
+}
\ No newline at end of file
diff --git a/ecommers/lib/ui/decorations/gradients.dart b/ecommers/lib/ui/decorations/gradients.dart
new file mode 100644
index 0000000..3422046
--- /dev/null
+++ b/ecommers/lib/ui/decorations/gradients.dart
@@ -0,0 +1,34 @@
+import 'package:flutter/material.dart';
+
+class Gradients {
+ static Gradient get apparelCategory =>
+ _createGradient(const Color(0xffFFAE4E), const Color(0xffFF7676));
+
+ static Gradient get beautyCategory =>
+ _createGradient(const Color(0xff4EFFF8), const Color(0xff76BAFF));
+
+ static Gradient get shoesCategory =>
+ _createGradient(const Color(0xffB4FF4E), const Color(0xff2FC145));
+
+ static Gradient get electronicsCategory =>
+ _createGradient(const Color(0xffD5A3FF), const Color(0xff77A5F8));
+
+ static Gradient get furnitureCategory =>
+ _createGradient(const Color(0xffFFF84E), const Color(0xffE6B15C));
+
+ static Gradient get homeCategory =>
+ _createGradient(const Color(0xffFF74A4), const Color(0xff9F6EA3));
+
+ static Gradient get stationaryCategory =>
+ _createGradient(const Color(0xff9D9E9F), const Color(0xff505862));
+
+ static Gradient get categoriesCompact =>
+ _createGradient(const Color(0xffffffff), const Color(0xffffffff));
+
+ static LinearGradient _createGradient(Color beginColor, Color endColor) {
+ return LinearGradient(
+ begin: Alignment.topCenter,
+ end: Alignment.bottomCenter,
+ colors: [beginColor, endColor]);
+ }
+}
diff --git a/ecommers/lib/ui/decorations/index.dart b/ecommers/lib/ui/decorations/index.dart
new file mode 100644
index 0000000..9619781
--- /dev/null
+++ b/ecommers/lib/ui/decorations/index.dart
@@ -0,0 +1,4 @@
+export 'assets.dart';
+export 'branding_colors.dart';
+export 'gradients.dart';
+export 'theme_provider.dart';
diff --git a/ecommers/lib/ui/decorations/theme_provider.dart b/ecommers/lib/ui/decorations/theme_provider.dart
new file mode 100644
index 0000000..0761b39
--- /dev/null
+++ b/ecommers/lib/ui/decorations/theme_provider.dart
@@ -0,0 +1,113 @@
+import 'package:flutter/material.dart';
+import 'dimens/index.dart';
+import 'index.dart';
+
+class ThemeProvider {
+ static ThemeData getTheme() {
+ return ThemeData(
+ primaryColor: BrandingColors.primary,
+ backgroundColor: BrandingColors.pageBackground,
+ cursorColor: BrandingColors.primary,
+ appBarTheme: const AppBarTheme(
+ brightness: Brightness.light,
+ color: BrandingColors.pageBackground,
+ elevation: 0.0,
+ ),
+ textTheme: TextTheme(
+ headline6: _TextStyles.headline6,
+ headline5: _TextStyles.headline5,
+ button: _TextStyles.button,
+ caption: _TextStyles.caption,
+ subtitle1: _TextStyles.subtitle1,
+ subtitle2: _TextStyles.subtitle2,
+ bodyText1: _TextStyles.bodyText1,
+ bodyText2: _TextStyles.bodyText2,
+ overline: _TextStyles.overline,
+ headline4: _TextStyles.headline4,
+ headline3: _TextStyles.headline3,
+ headline2: _TextStyles.headline2,
+ headline1: _TextStyles.headline1,
+ ),
+ );
+ }
+}
+
+class _TextStyles {
+ static const headline6 = TextStyle(
+ color: BrandingColors.primaryText,
+ fontSize: FontSizes.big_4x,
+ fontWeight: FontWeight.w700,
+ );
+
+ static final TextStyle headline5 = TextStyle(
+ color: BrandingColors.primaryText.withOpacity(0.5),
+ fontSize: FontSizes.small_3x,
+ fontWeight: FontWeight.w500,
+ );
+
+ static const button = TextStyle(
+ color: BrandingColors.secondary,
+ fontSize: FontSizes.small_3x,
+ fontWeight: FontWeight.w700,
+ );
+
+ static const caption = TextStyle(
+ color: BrandingColors.secondary,
+ fontSize: FontSizes.small_2x,
+ fontWeight: FontWeight.w400,
+ );
+
+ static const subtitle1 = TextStyle(
+ color: BrandingColors.primaryText,
+ fontSize: FontSizes.normal,
+ fontWeight: FontWeight.w400,
+ );
+
+ static const subtitle2 = TextStyle(
+ color: BrandingColors.primaryText,
+ fontSize: FontSizes.normal,
+ fontWeight: FontWeight.w300,
+ );
+
+ static const bodyText1 = TextStyle(
+ color: BrandingColors.primaryText,
+ fontSize: FontSizes.normal,
+ fontWeight: FontWeight.w500,
+ );
+
+ static const bodyText2 = TextStyle(
+ color: BrandingColors.primaryText,
+ fontSize: FontSizes.normal,
+ fontWeight: FontWeight.w300,
+ );
+
+ static const overline = TextStyle(
+ color: BrandingColors.secondaryText,
+ fontSize: FontSizes.small_1x,
+ fontWeight: FontWeight.w500,
+ );
+
+ static const headline4 = TextStyle(
+ color: BrandingColors.secondaryText,
+ fontSize: FontSizes.normal,
+ fontWeight: FontWeight.w400,
+ );
+
+ static const headline3 = TextStyle(
+ color: BrandingColors.secondaryText,
+ fontSize: FontSizes.big_1x,
+ fontWeight: FontWeight.w300,
+ );
+
+ static const headline2 = TextStyle(
+ color: BrandingColors.secondaryText,
+ fontSize: FontSizes.big_2x,
+ fontWeight: FontWeight.w400,
+ );
+
+ static const headline1 = TextStyle(
+ color: BrandingColors.secondaryText,
+ fontSize: FontSizes.big_3x,
+ fontWeight: FontWeight.w400,
+ );
+}
diff --git a/ecommers/lib/ui/notifier_provider_widget.dart b/ecommers/lib/ui/notifier_provider_widget.dart
new file mode 100644
index 0000000..f50990b
--- /dev/null
+++ b/ecommers/lib/ui/notifier_provider_widget.dart
@@ -0,0 +1,47 @@
+import 'package:flutter/foundation.dart';
+import 'package:flutter/material.dart';
+import 'package:provider/provider.dart';
+
+class NotifierProviderWidget extends StatefulWidget {
+ final Widget Function(BuildContext context, T model, Widget child) builder;
+ final T providerModel;
+ final Widget child;
+ final Function(T) onModelReady;
+
+ const NotifierProviderWidget({
+ Key key,
+ this.builder,
+ this.child,
+ this.providerModel,
+ this.onModelReady,
+ }) : super(key: key);
+
+ @override
+ _NotifierProviderWidgetState createState() => _NotifierProviderWidgetState();
+}
+
+class _NotifierProviderWidgetState extends State> {
+ T providerModel;
+
+ @override
+ void initState() {
+ providerModel = widget.providerModel;
+
+ if (widget.onModelReady != null) {
+ widget.onModelReady(providerModel);
+ }
+
+ super.initState();
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ return ChangeNotifierProvider(
+ create: (context) => providerModel,
+ child: Consumer(
+ builder: widget.builder,
+ child: widget.child,
+ ),
+ );
+ }
+}
\ No newline at end of file
diff --git a/ecommers/lib/ui/pages/authorization/authentication_tab_base.dart b/ecommers/lib/ui/pages/authorization/authentication_tab_base.dart
new file mode 100644
index 0000000..8ac5b39
--- /dev/null
+++ b/ecommers/lib/ui/pages/authorization/authentication_tab_base.dart
@@ -0,0 +1,26 @@
+import 'package:ecommers/ui/decorations/dimens/index.dart';
+import 'package:flutter/material.dart';
+
+class AuthorizationTabBase extends StatelessWidget {
+ final List children;
+
+ const AuthorizationTabBase({this.children});
+
+ @override
+ Widget build(BuildContext context) {
+ return LayoutBuilder(builder: (context, viewportConstraints) {
+ return SingleChildScrollView(
+ padding: const EdgeInsets.symmetric(horizontal: Dimens.pagePadding),
+ child: ConstrainedBox(
+ constraints: BoxConstraints(minHeight: viewportConstraints.maxHeight),
+ child: IntrinsicHeight(
+ child: Column(
+ mainAxisAlignment: MainAxisAlignment.center,
+ children: children,
+ ),
+ ),
+ ),
+ );
+ });
+ }
+}
diff --git a/ecommers/lib/ui/pages/authorization/authorization_page.dart b/ecommers/lib/ui/pages/authorization/authorization_page.dart
new file mode 100644
index 0000000..f8f1d8b
--- /dev/null
+++ b/ecommers/lib/ui/pages/authorization/authorization_page.dart
@@ -0,0 +1,56 @@
+import 'package:ecommers/core/provider_models/log_in_provider_model.dart';
+import 'package:ecommers/core/provider_models/sign_up_provider_model.dart';
+import 'package:ecommers/generated/i18n.dart';
+import 'package:ecommers/ui/decorations/index.dart';
+import 'package:ecommers/ui/pages/authorization/forgot_password_page.dart';
+import 'package:ecommers/ui/pages/authorization/log_in_page.dart';
+import 'package:ecommers/ui/pages/authorization/sign_up_page.dart';
+import 'package:ecommers/ui/widgets/backgrounded_safe_area.dart';
+import 'package:flutter/material.dart';
+import 'package:provider/provider.dart';
+
+class AuthorizationPage extends StatelessWidget {
+ const AuthorizationPage({Key key}) : super(key: key);
+
+ @override
+ Widget build(BuildContext context) {
+ final tabStyle = Theme.of(context).textTheme.headline6;
+ final localization = I18n.of(context);
+
+ return MultiProvider(
+ providers: [
+ ChangeNotifierProvider(create: (_) => LogInProviderModel(context)),
+ ChangeNotifierProvider(create: (_) => SignUpProviderModel(context)),
+ ],
+ child: BackgroundedSafeArea(
+ child: DefaultTabController(
+ length: 3,
+ child: Scaffold(
+ backgroundColor: BrandingColors.pageBackground,
+ appBar: AppBar(
+ bottom: TabBar(
+ tabs: [
+ Tab(text: localization.signUp),
+ Tab(text: localization.logIn),
+ Tab(text: localization.forgotPassword),
+ ],
+ indicatorColor: Colors.transparent,
+ labelStyle: tabStyle,
+ labelColor: tabStyle.color,
+ unselectedLabelColor: tabStyle.color.withOpacity(0.2),
+ isScrollable: true,
+ ),
+ ),
+ body: TabBarView(
+ children: [
+ SignUpPage(),
+ LogInPage(),
+ ForgotPasswordPage(),
+ ],
+ ),
+ ),
+ ),
+ ),
+ );
+ }
+}
diff --git a/ecommers/lib/ui/pages/authorization/forgot_password_page.dart b/ecommers/lib/ui/pages/authorization/forgot_password_page.dart
new file mode 100644
index 0000000..ac6a41d
--- /dev/null
+++ b/ecommers/lib/ui/pages/authorization/forgot_password_page.dart
@@ -0,0 +1,39 @@
+import 'package:ecommers/generated/i18n.dart';
+import 'package:ecommers/ui/decorations/dimens/index.dart';
+import 'package:ecommers/ui/decorations/index.dart';
+import 'package:ecommers/ui/pages/authorization/authentication_tab_base.dart';
+import 'package:ecommers/ui/widgets/authorization/auth_text_field_area_container.dart';
+import 'package:ecommers/ui/widgets/authorization/index.dart';
+import 'package:ecommers/ui/widgets/button/index.dart';
+import 'package:flutter/material.dart';
+
+class ForgotPasswordPage extends StatelessWidget {
+ @override
+ Widget build(BuildContext context) {
+ final localization = I18n.of(context);
+
+ return AuthorizationTabBase(
+ children: [
+ Text(
+ localization.forgotPasswordHelpText,
+ textAlign: TextAlign.center,
+ style: Theme.of(context).textTheme.bodyText2,
+ ),
+ const SizedBox(height: Insets.x8_5),
+ AuthTextFieldAreaContainer(
+ child: AuthTextField(
+ labelText: localization.email,
+ keyboardType: TextInputType.emailAddress,
+ assetIconPath: Assets.mailIcon,
+ ),
+ ),
+ const SizedBox(height: Insets.x3_5),
+ PrimaryButtonWidget(
+ text: localization.logIn,
+ assetIconPath: Assets.arrowRightIcon,
+ onPressedFunction: () {}, //TODO: add providers handler to it
+ ),
+ ],
+ );
+ }
+}
diff --git a/ecommers/lib/ui/pages/authorization/index.dart b/ecommers/lib/ui/pages/authorization/index.dart
new file mode 100644
index 0000000..4eef1ff
--- /dev/null
+++ b/ecommers/lib/ui/pages/authorization/index.dart
@@ -0,0 +1,5 @@
+export 'authentication_tab_base.dart';
+export 'authorization_page.dart';
+export 'forgot_password_page.dart';
+export 'log_in_page.dart';
+export 'sign_up_page.dart';
diff --git a/ecommers/lib/ui/pages/authorization/log_in_page.dart b/ecommers/lib/ui/pages/authorization/log_in_page.dart
new file mode 100644
index 0000000..ddebcac
--- /dev/null
+++ b/ecommers/lib/ui/pages/authorization/log_in_page.dart
@@ -0,0 +1,70 @@
+import 'package:ecommers/core/provider_models/log_in_provider_model.dart';
+import 'package:ecommers/generated/i18n.dart';
+import 'package:ecommers/ui/decorations/dimens/index.dart';
+import 'package:ecommers/ui/decorations/index.dart';
+import 'package:ecommers/ui/pages/authorization/index.dart';
+import 'package:ecommers/ui/widgets/authorization/index.dart';
+import 'package:ecommers/ui/widgets/button/index.dart';
+import 'package:ecommers/ui/widgets/progress.dart';
+import 'package:flutter/cupertino.dart';
+import 'package:flutter/material.dart';
+import 'package:provider/provider.dart';
+
+class LogInPage extends StatelessWidget {
+ @override
+ Widget build(BuildContext context) {
+ return Consumer(
+ builder: (_, LogInProviderModel provider, child) {
+ return Stack(
+ children: [
+ child,
+ Visibility(
+ visible: provider.isBusy,
+ child: const Progress(),
+ ),
+ ],
+ );
+ },
+ child: _buildContent(context),
+ );
+ }
+
+ Widget _buildContent(BuildContext context) {
+ final localization = I18n.of(context);
+ final provider = Provider.of(context, listen: false);
+
+ return AuthorizationTabBase(
+ children: [
+ const SizedBox(height: Insets.x5),
+ AuthTextFieldAreaContainer(
+ child: Column(
+ mainAxisSize: MainAxisSize.min,
+ children: [
+ AuthTextField(
+ labelText: localization.usernameOrEmail,
+ keyboardType: TextInputType.emailAddress,
+ assetIconPath: Assets.profileIcon,
+ onChanged: (String text) => provider.username = text,
+ ),
+ AuthTextField(
+ labelText: localization.password,
+ obscureText: true,
+ keyboardType: TextInputType.visiblePassword,
+ assetIconPath: Assets.passwordIcon,
+ onChanged: (String text) => provider.password = text,
+ ),
+ ],
+ ),
+ ),
+ const SizedBox(height: Insets.x3_5),
+ PrimaryButtonWidget(
+ text: localization.logIn,
+ assetIconPath: Assets.arrowRightIcon,
+ onPressedFunction: () => provider.tryLogin(),
+ ),
+ const SizedBox(height: Insets.x8_5),
+ AuthRichText(textSpanModelList: provider.bottomText),
+ ],
+ );
+ }
+}
diff --git a/ecommers/lib/ui/pages/authorization/sign_up_page.dart b/ecommers/lib/ui/pages/authorization/sign_up_page.dart
new file mode 100644
index 0000000..c5d55ed
--- /dev/null
+++ b/ecommers/lib/ui/pages/authorization/sign_up_page.dart
@@ -0,0 +1,75 @@
+import 'package:ecommers/core/provider_models/sign_up_provider_model.dart';
+import 'package:ecommers/generated/i18n.dart';
+import 'package:ecommers/ui/decorations/assets.dart';
+import 'package:ecommers/ui/decorations/dimens/index.dart';
+import 'package:ecommers/ui/decorations/index.dart';
+import 'package:ecommers/ui/pages/authorization/index.dart';
+import 'package:ecommers/ui/widgets/authorization/index.dart';
+import 'package:ecommers/ui/widgets/button/index.dart';
+import 'package:ecommers/ui/widgets/progress.dart';
+import 'package:flutter/material.dart';
+import 'package:provider/provider.dart';
+
+class SignUpPage extends StatelessWidget {
+ @override
+ Widget build(BuildContext context) {
+ return Consumer(
+ builder: (_, SignUpProviderModel provider, child) {
+ return Stack(
+ children: [
+ child,
+ Visibility(
+ visible: provider.isBusy,
+ child: const Progress(),
+ ),
+ ],
+ );
+ },
+ child: _buildContent(context),
+ );
+ }
+
+ Widget _buildContent(BuildContext context) {
+ final localization = I18n.of(context);
+ final provider = Provider.of(context, listen: false);
+
+ return AuthorizationTabBase(
+ children: [
+ const SizedBox(height: Insets.x5),
+ AuthTextFieldAreaContainer(
+ child: Column(
+ mainAxisSize: MainAxisSize.min,
+ children: [
+ AuthTextField(
+ labelText: localization.email,
+ keyboardType: TextInputType.emailAddress,
+ assetIconPath: Assets.mailIcon,
+ onChanged: (text) => provider.email = text,
+ ),
+ AuthTextField(
+ labelText: localization.username,
+ assetIconPath: Assets.profileIcon,
+ onChanged: (text) => provider.username = text,
+ ),
+ AuthTextField(
+ labelText: localization.password,
+ obscureText: true,
+ keyboardType: TextInputType.visiblePassword,
+ assetIconPath: Assets.passwordIcon,
+ onChanged: (text) => provider.password = text,
+ ),
+ ],
+ ),
+ ),
+ const SizedBox(height: Insets.x3_5),
+ PrimaryButtonWidget(
+ text: localization.signUp,
+ assetIconPath: Assets.arrowRightIcon,
+ onPressedFunction: () => provider.tryAuthorize(),
+ ),
+ const SizedBox(height: Insets.x8_5),
+ AuthRichText(textSpanModelList: provider.bottomText),
+ ],
+ );
+ }
+}
diff --git a/ecommers/lib/ui/pages/cart_page.dart b/ecommers/lib/ui/pages/cart_page.dart
new file mode 100644
index 0000000..60ebefd
--- /dev/null
+++ b/ecommers/lib/ui/pages/cart_page.dart
@@ -0,0 +1,127 @@
+import 'package:ecommers/core/common/index.dart';
+import 'package:ecommers/core/models/index.dart';
+import 'package:ecommers/core/services/index.dart';
+import 'package:ecommers/generated/i18n.dart';
+import 'package:ecommers/ui/decorations/assets.dart';
+import 'package:ecommers/ui/decorations/dimens/index.dart';
+import 'package:ecommers/ui/decorations/index.dart';
+import 'package:ecommers/ui/widgets/order/index.dart';
+import 'package:flutter/cupertino.dart';
+import 'package:flutter/material.dart';
+
+class CartPage extends StatefulWidget {
+ @override
+ _CartPageState createState() => _CartPageState();
+}
+
+class _CartPageState extends State {
+ static const _orderDeviderIndent = 100.0;
+
+ static String _getDressAssetPath(int index) {
+ final modulo = index % 7;
+
+ if (modulo == 0) return Assets.dressCottonImage;
+ if (modulo == 1) return Assets.dressFloral2Image;
+ if (modulo == 2) return Assets.dressFloralImage;
+ if (modulo == 3) return Assets.dressPattern2Image;
+ if (modulo == 4) return Assets.dressPatternImage;
+ if (modulo == 5) return Assets.dressCotton2Image;
+ if (modulo == 6) {
+ return Assets.greenBackpackImage;
+ } else {
+ return Assets.greenBackpackImage;
+ }
+ }
+
+ BuildContext _context;
+ final _orders = List.generate(
+ 20,
+ (index) => OrderModel(
+ title: 'Bottle Green Backpack',
+ description: 'Medium, Green',
+ cost: 2.58,
+ imagePath: _getDressAssetPath(index),
+ count: 1),
+ );
+
+ void decrementCount(OrderModel order) {
+ if (order.count == 1) _orders.remove(order);
+
+ setState(() {
+ order.count--;
+ });
+ }
+
+ void incrementCount(OrderModel order) {
+ setState(() {
+ order.count++;
+ });
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ _context = context;
+ final double totalOrderCost = _orders.fold(0.0,
+ (totalCost, nextOrder) => totalCost + nextOrder.count * nextOrder.cost);
+
+ return Padding(
+ padding:
+ const EdgeInsets.fromLTRB(Insets.x6, Insets.x0, Insets.x5, Insets.x4),
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.stretch,
+ children: [
+ Text(
+ I18n.of(context).cartTitle,
+ style: Theme.of(context).textTheme.headline6,
+ ),
+ const SizedBox(height: 16),
+ Expanded(
+ child: _buildOrderListView(),
+ ),
+ const Padding(
+ padding: EdgeInsets.fromLTRB(
+ Insets.x0, Insets.x4, Insets.x0, Insets.x2),
+ child: Divider(color: BrandingColors.secondary),
+ ),
+ TotalOrderWidget(
+ cost: totalOrderCost,
+ backgroundColor: BrandingColors.pageBackground,
+ onButtonPressedFunction: () =>
+ navigationService.navigateTo(Pages.checkout),
+ buttonText: I18n.of(_context).checkoutButton,
+ )
+ ],
+ ),
+ );
+ }
+
+ Widget _buildOrderListView() {
+ return ListView.separated(
+ padding:
+ const EdgeInsets.fromLTRB(Insets.x0, Insets.x0, Insets.x5, Insets.x0),
+ itemCount: _orders.length,
+ itemBuilder: (BuildContext context, int index) {
+ final currentOrder = _orders[index];
+ return OrderWidget(
+ primaryText: currentOrder.title,
+ secondaryText: currentOrder.description,
+ assetImagePath: currentOrder.imagePath,
+ cost: currentOrder.cost,
+ count: currentOrder.count,
+ countIncrementFunction: () => incrementCount(currentOrder),
+ countDecrementFunction: () => decrementCount(currentOrder),
+ );
+ },
+ separatorBuilder: (BuildContext context, int index) {
+ return Padding(
+ padding: const EdgeInsets.fromLTRB(
+ Insets.x0, Insets.x3, Insets.x0, Insets.x4),
+ child: Divider(
+ color: BrandingColors.secondary.withOpacity(0.4),
+ indent: _orderDeviderIndent,
+ ),
+ );
+ },
+ );
+ }
+}
diff --git a/ecommers/lib/ui/pages/categories_page.dart b/ecommers/lib/ui/pages/categories_page.dart
new file mode 100644
index 0000000..32a26df
--- /dev/null
+++ b/ecommers/lib/ui/pages/categories_page.dart
@@ -0,0 +1,138 @@
+import 'package:ecommers/core/common/index.dart';
+import 'package:ecommers/generated/i18n.dart';
+import 'package:ecommers/ui/decorations/dimens/index.dart';
+import 'package:ecommers/ui/decorations/index.dart';
+import 'package:ecommers/ui/pages/closeable_page.dart';
+import 'package:ecommers/ui/widgets/backgrounded_safe_area.dart';
+import 'package:ecommers/ui/widgets/category_item/category_item.dart';
+import 'package:ecommers/ui/widgets/menu/index.dart';
+import 'package:flutter/material.dart';
+
+const List _topMenuList = [
+ MenuItemModel(
+ title: 'T-shirt',
+ ),
+ MenuItemModel(
+ title: 'Shirts',
+ ),
+ MenuItemModel(
+ title: 'Pants & Jeans',
+ ),
+ MenuItemModel(
+ title: 'Socks & Ties',
+ ),
+ MenuItemModel(
+ title: 'Underwear',
+ ),
+ MenuItemModel(
+ title: 'Jackets',
+ ),
+ MenuItemModel(
+ title: 'Coats',
+ ),
+ MenuItemModel(
+ title: 'Sweaters',
+ ),
+];
+
+const List _bottomMenuList = [
+ MenuItemModel(
+ title: 'Officewear',
+ ),
+ MenuItemModel(
+ title: 'Blouce & T-Shirts',
+ ),
+ MenuItemModel(
+ title: 'Pants & Jeans',
+ ),
+ MenuItemModel(
+ title: 'Dresses',
+ ),
+ MenuItemModel(
+ title: 'Lingerie',
+ ),
+ MenuItemModel(
+ title: 'Jackets',
+ ),
+ MenuItemModel(
+ title: 'Coats',
+ ),
+ MenuItemModel(
+ title: 'Sweaters',
+ ),
+];
+
+class CategoriesPage extends StatelessWidget {
+ const CategoriesPage();
+
+ @override
+ Widget build(BuildContext context) {
+ return CloseablePage(
+ child: BackgroundedSafeArea(
+ child: SingleChildScrollView(
+ scrollDirection: Axis.vertical,
+ padding:
+ const EdgeInsets.fromLTRB(Insets.x5, 0, Insets.x5, Insets.x5),
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Text(
+ I18n.of(context).allCategories,
+ style: Theme.of(context).textTheme.headline6,
+ ),
+ const SizedBox(height: Insets.x6),
+ Row(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ _buildCategories(),
+ const SizedBox(width: Insets.x6),
+ Flexible(
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.stretch,
+ children: [
+ const MenuList(
+ title: 'MEN\'S APPAREL',
+ itemHeight: 44.0,
+ itemList: _topMenuList,
+ ),
+ Divider(
+ height: 52.0,
+ thickness: 1.0,
+ color: BrandingColors.secondary.withOpacity(0.1),
+ ),
+ const MenuList(
+ title: 'WOMEN\'S APPAREL',
+ itemHeight: 44.0,
+ itemList: _bottomMenuList,
+ ),
+ ],
+ ),
+ )
+ ],
+ ),
+ ],
+ ),
+ ),
+ ),
+ );
+ }
+
+ Widget _buildCategories() {
+ return SizedBox(
+ width: CategoryItem.size.width,
+ child: ListView.separated(
+ shrinkWrap: true,
+ physics: const NeverScrollableScrollPhysics(),
+ itemCount: Categories.values.length,
+ separatorBuilder: (BuildContext context, int index) => const SizedBox(
+ width: Insets.x8,
+ height: Insets.x8,
+ ),
+ scrollDirection: Axis.vertical,
+ itemBuilder: (BuildContext context, int index) {
+ return CategoryItem.fromType(Categories.values[index]);
+ },
+ ),
+ );
+ }
+}
diff --git a/ecommers/lib/ui/pages/checkout_page.dart b/ecommers/lib/ui/pages/checkout_page.dart
new file mode 100644
index 0000000..82cb579
--- /dev/null
+++ b/ecommers/lib/ui/pages/checkout_page.dart
@@ -0,0 +1,290 @@
+import 'package:ecommers/core/common/index.dart';
+import 'package:ecommers/core/models/index.dart';
+import 'package:ecommers/core/services/index.dart';
+import 'package:ecommers/generated/i18n.dart';
+import 'package:ecommers/ui/decorations/assets.dart';
+import 'package:ecommers/ui/decorations/dimens/index.dart';
+import 'package:ecommers/ui/decorations/index.dart';
+import 'package:ecommers/ui/pages/closeable_page.dart';
+import 'package:ecommers/ui/widgets/circle_icon.dart';
+import 'package:ecommers/ui/widgets/index.dart';
+import 'package:ecommers/ui/widgets/order/index.dart';
+import 'package:flutter/cupertino.dart';
+import 'package:flutter/material.dart';
+import 'package:keyboard_visibility/keyboard_visibility.dart';
+
+class CheckoutPage extends StatefulWidget {
+ @override
+ _CheckoutPageState createState() => _CheckoutPageState();
+}
+
+class _CheckoutPageState extends State {
+ final KeyboardVisibilityNotification keyboardVisibilityNotification =
+ KeyboardVisibilityNotification();
+ bool isTotalOrderVisible = true;
+
+ @override
+ void initState() {
+ super.initState();
+
+ keyboardVisibilityNotification.addNewListener(
+ onChange: (bool visible) {
+ setState(() {
+ isTotalOrderVisible = !visible;
+ });
+ },
+ );
+ }
+
+ @override
+ void dispose() {
+ keyboardVisibilityNotification.dispose();
+ super.dispose();
+ }
+
+ static const int _itemCount = 20;
+
+ static String _getDressAssetPath(int index) {
+ final modulo = index % 7;
+
+ if (modulo == 0) return Assets.dressCottonImage;
+ if (modulo == 1) return Assets.dressFloral2Image;
+ if (modulo == 2) return Assets.dressFloralImage;
+ if (modulo == 3) return Assets.dressPattern2Image;
+ if (modulo == 4) return Assets.dressPatternImage;
+ if (modulo == 5) return Assets.dressCotton2Image;
+ if (modulo == 6) {
+ return Assets.greenBackpackImage;
+ } else {
+ return Assets.greenBackpackImage;
+ }
+ }
+
+ final _orders = List.generate(
+ _itemCount,
+ (index) => OrderModel(
+ title: 'Bottle Green Backpack',
+ description: 'Medium, Green',
+ cost: 2.58,
+ imagePath: _getDressAssetPath(index),
+ count: 1));
+
+ @override
+ Widget build(BuildContext context) {
+ final double totalOrderCost = _orders.fold(0.0,
+ (totalCost, nextOrder) => totalCost + nextOrder.count * nextOrder.cost);
+
+ return CloseablePage(
+ child: Column(
+ children: [
+ Expanded(
+ child: BackgroundedSafeArea(
+ child: Padding(
+ padding: const EdgeInsets.fromLTRB(
+ Insets.x6, Insets.x0, Insets.x5, Insets.x4),
+ child: _buildOrderListView(),
+ ),
+ ),
+ ),
+ Visibility(
+ visible: isTotalOrderVisible,
+ child: TotalOrderWidget(
+ cost: totalOrderCost,
+ backgroundColor: BrandingColors.background,
+ onButtonPressedFunction: () =>
+ navigationService.navigateTo(Pages.success),
+ buttonText: I18n.of(context).placeOrderButton,
+ padding: const EdgeInsets.fromLTRB(
+ Insets.x6, Insets.x2, Insets.x5, Insets.x3_5),
+ ),
+ ),
+ ],
+ ),
+ );
+ }
+
+ Widget _buildListHeader() {
+ return Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Text(
+ I18n.of(context).checkoutTitle,
+ style: Theme.of(context).textTheme.headline6,
+ ),
+ const SizedBox(height: 20),
+ Text(
+ I18n.of(context).shippingAddress,
+ style: Theme.of(context).textTheme.headline5,
+ ),
+ const SizedBox(height: Insets.x2),
+ _buildShippingAddress(),
+ _buildDevider(),
+ Text(
+ I18n.of(context).paymentMethod,
+ style: Theme.of(context).textTheme.headline5,
+ ),
+ const SizedBox(height: Insets.x2),
+ _buildRowAction(
+ imagePath: Assets.creditCardImage,
+ text: Text(
+ I18n.of(context).cardEnding,
+ style: Theme.of(context)
+ .textTheme
+ .bodyText1
+ .copyWith(fontWeight: FontWeight.w700),
+ ),
+ ),
+ _buildDevider(),
+ Text(
+ I18n.of(context).items,
+ style: Theme.of(context).textTheme.headline5,
+ ),
+ const SizedBox(height: 14.0),
+ ],
+ );
+ }
+
+ Widget _buildListFooter() {
+ return Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ _buildDevider(),
+ SizedBox(
+ height: 28,
+ child: TextField(
+ decoration: InputDecoration(
+ border: InputBorder.none,
+ hintText: 'Message to seller (optional)',
+ hintStyle: Theme.of(context).textTheme.bodyText1.copyWith(
+ fontWeight: FontWeight.w300,
+ fontStyle: FontStyle.italic,
+ ),
+ ),
+ ),
+ ),
+ _buildDevider(),
+ _buildRowAction(
+ imagePath: Assets.saleImage,
+ text: Text(
+ I18n.of(context).addPromoCode,
+ style: Theme.of(context)
+ .textTheme
+ .bodyText1
+ .copyWith(color: BrandingColors.primary),
+ ),
+ ),
+ ],
+ );
+ }
+
+ Widget _buildOrderListView() {
+ final int newItemCount = _orders.length + 2;
+ return ListView.separated(
+ itemCount: newItemCount,
+ itemBuilder: (BuildContext context, int index) {
+ if (index == 0) {
+ return _buildListHeader();
+ }
+
+ if (index == newItemCount - 1) {
+ return _buildListFooter();
+ }
+
+ final currentOrder = _orders[index - 1];
+ return SmallOrderWidget(
+ primaryText: currentOrder.title,
+ secondaryText: currentOrder.description,
+ assetImagePath: currentOrder.imagePath,
+ cost: currentOrder.cost,
+ count: currentOrder.count,
+ countIncrementFunction: () => incrementCount(currentOrder),
+ countDecrementFunction: () => decrementCount(currentOrder),
+ );
+ },
+ separatorBuilder: (BuildContext context, int index) {
+ if (index == 0) {
+ return const SizedBox(height: 0);
+ }
+ if (index == newItemCount - 2) {
+ return const SizedBox(height: 0);
+ }
+ return Padding(
+ padding: const EdgeInsets.fromLTRB(
+ Insets.x0, Insets.x3, Insets.x0, Insets.x4),
+ child: Divider(
+ color: BrandingColors.secondary.withOpacity(0.4),
+ indent: 83.0,
+ ),
+ );
+ },
+ );
+ }
+
+ void decrementCount(OrderModel order) {
+ if (order.count == 1) _orders.remove(order);
+
+ setState(() {
+ order.count--;
+ });
+ }
+
+ void incrementCount(OrderModel order) {
+ setState(() {
+ order.count++;
+ });
+ }
+
+ Widget _buildShippingAddress() {
+ return Row(
+ children: [
+ Container(
+ width: 136,
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Text(
+ 'John Doe', //TODO from provider
+ style: Theme.of(context)
+ .textTheme
+ .bodyText1
+ .copyWith(fontWeight: FontWeight.w700),
+ ),
+ Text(
+ 'No 123, Sub Street, Main Street,City Name, Province, Country',
+ style: Theme.of(context)
+ .textTheme
+ .bodyText1
+ .copyWith(fontWeight: FontWeight.w400),
+ ),
+ ],
+ ),
+ ),
+ const Spacer(),
+ const CircleIcon(),
+ ],
+ );
+ }
+
+ Widget _buildRowAction({String imagePath, Text text}) {
+ return Row(
+ children: [
+ Image.asset(
+ imagePath,
+ fit: BoxFit.scaleDown,
+ ),
+ const SizedBox(width: Insets.x3_5),
+ text,
+ const Spacer(),
+ const CircleIcon(),
+ ],
+ );
+ }
+
+ Widget _buildDevider() {
+ return Divider(
+ color: BrandingColors.secondary.withOpacity(0.15),
+ thickness: 1.0,
+ height: 24.0,
+ );
+ }
+}
diff --git a/ecommers/lib/ui/pages/closeable_page.dart b/ecommers/lib/ui/pages/closeable_page.dart
new file mode 100644
index 0000000..a0166da
--- /dev/null
+++ b/ecommers/lib/ui/pages/closeable_page.dart
@@ -0,0 +1,31 @@
+import 'package:ecommers/core/services/index.dart';
+import 'package:ecommers/ui/decorations/index.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter_svg/svg.dart';
+
+class CloseablePage extends StatelessWidget {
+ final Widget child;
+
+ const CloseablePage({this.child});
+
+ @override
+ Widget build(BuildContext context) {
+ return Scaffold(
+ backgroundColor: BrandingColors.pageBackground,
+ appBar: AppBar(
+ automaticallyImplyLeading: false,
+ actions: [
+ IconButton(
+ icon: SvgPicture.asset(
+ Assets.closeIcon,
+ color: BrandingColors.primary,
+ height: 18.0,
+ ),
+ onPressed: () => navigationService.goBack(),
+ )
+ ],
+ ),
+ body: child,
+ );
+ }
+}
diff --git a/ecommers/lib/ui/pages/home_page.dart b/ecommers/lib/ui/pages/home_page.dart
new file mode 100644
index 0000000..f2fff94
--- /dev/null
+++ b/ecommers/lib/ui/pages/home_page.dart
@@ -0,0 +1,119 @@
+import 'package:carousel_slider/carousel_slider.dart';
+import 'package:ecommers/generated/i18n.dart';
+import 'package:ecommers/ui/decorations/dimens/index.dart';
+import 'package:ecommers/ui/decorations/index.dart';
+import 'package:ecommers/ui/widgets/category_item/categories_compact_widget.dart';
+import 'package:ecommers/ui/widgets/index.dart';
+import 'package:ecommers/ui/widgets/product_item/product_item_normal.dart';
+import 'package:flutter/material.dart';
+
+class HomePage extends StatelessWidget {
+ static const double _latestGridViewAxisSpacing = 12.0;
+ static const imageCardSize = Size(325.0, 184.0);
+ static const productItemNormalSize = Size(101.0, 135.0);
+
+ @override
+ Widget build(BuildContext context) {
+ return CustomScrollView(
+ slivers: [
+ SliverToBoxAdapter(
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.stretch,
+ children: [
+ CategoriesCompactWidget(),
+ const SizedBox(height: Dimens.pagePadding),
+ Padding(
+ padding: const EdgeInsets.symmetric(horizontal: Insets.x6),
+ child: Text(
+ I18n.of(context).latetstTitle,
+ style: Theme.of(context).textTheme.headline6,
+ ),
+ ),
+ const SizedBox(height: 10.0),
+ _buildLatestCarousel(context),
+ ],
+ ),
+ ),
+ _buildLatestGridView(context),
+ ],
+ );
+ }
+
+ Widget _buildLatestCarousel(BuildContext context) {
+ return CarouselSlider(
+ viewportFraction: 0.92,
+ items: List.generate(
+ 6,
+ (index) {
+ return SizedBox.expand(
+ child: Padding(
+ padding: const EdgeInsets.symmetric(horizontal: Insets.x2),
+ child: ImageCard(
+ buttonText: 'SEE MORE',
+ description: 'For all your summer clothing needs',
+ imageAsset: getCarouselImage(index),
+ onButtonPressed: () {},
+ ),
+ ),
+ );
+ },
+ ),
+ );
+ }
+
+ String getCarouselImage(int index) {
+ final modulo = index % 3;
+
+ if (modulo == 0) {
+ return Assets.girlImage;
+ } else if (modulo == 1) {
+ return Assets.girl2Image;
+ } else {
+ return Assets.girl3Image;
+ }
+ }
+
+ Widget _buildLatestGridView(BuildContext context) {
+ return SliverPadding(
+ padding: const EdgeInsets.symmetric(
+ horizontal: Dimens.pagePadding,
+ vertical: Insets.x2_5,
+ ),
+ sliver: SliverGrid(
+ gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent(
+ maxCrossAxisExtent: ProductItemNormal.size.height,
+ mainAxisSpacing: _latestGridViewAxisSpacing,
+ crossAxisSpacing: _latestGridViewAxisSpacing,
+ childAspectRatio:
+ ProductItemNormal.size.width / ProductItemNormal.size.height,
+ ),
+ delegate: SliverChildBuilderDelegate(
+ (context, index) {
+ return ProductItemNormal(
+ assetImagePath: _getDressAssetPath(index),
+ cost: 15.0,
+ title: 'best dress ever',
+ rate: 3.9,
+ );
+ },
+ childCount: 30,
+ ),
+ ),
+ );
+ }
+
+ String _getDressAssetPath(int index) {
+ final modulo = index % 6;
+
+ if (modulo == 0) return Assets.dressCottonImage;
+ if (modulo == 1) return Assets.dressFloral2Image;
+ if (modulo == 2) return Assets.dressFloralImage;
+ if (modulo == 3) return Assets.dressPattern2Image;
+ if (modulo == 4) return Assets.dressPatternImage;
+ if (modulo == 5) {
+ return Assets.dressCotton2Image;
+ } else {
+ return Assets.greenBackpackImage;
+ }
+ }
+}
diff --git a/ecommers/lib/ui/pages/index.dart b/ecommers/lib/ui/pages/index.dart
new file mode 100644
index 0000000..4840cf3
--- /dev/null
+++ b/ecommers/lib/ui/pages/index.dart
@@ -0,0 +1,10 @@
+export 'cart_page.dart';
+export 'categories_page.dart';
+export 'checkout_page.dart';
+export 'closeable_page.dart';
+export 'home_page.dart';
+export 'more_page.dart';
+export 'profile_page.dart';
+export 'search_page.dart';
+export 'shell_page.dart';
+export 'success_page.dart';
diff --git a/ecommers/lib/ui/pages/more_page.dart b/ecommers/lib/ui/pages/more_page.dart
new file mode 100644
index 0000000..8200a79
--- /dev/null
+++ b/ecommers/lib/ui/pages/more_page.dart
@@ -0,0 +1,105 @@
+import 'package:ecommers/core/common/index.dart';
+import 'package:ecommers/core/services/index.dart';
+import 'package:ecommers/generated/i18n.dart';
+import 'package:ecommers/ui/decorations/dimens/index.dart';
+import 'package:ecommers/ui/decorations/index.dart';
+import 'package:ecommers/ui/widgets/menu/index.dart';
+import 'package:flutter/cupertino.dart';
+import 'package:flutter/material.dart';
+
+class MorePage extends StatelessWidget {
+ static const List topMenuList = [
+ MenuItemModel(
+ svgAssetIconPath: Assets.shippingIcon,
+ title: 'Shipping Adress',
+ ),
+ MenuItemModel(
+ svgAssetIconPath: Assets.paymentIcon,
+ title: 'Payment Method',
+ ),
+ MenuItemModel(
+ svgAssetIconPath: Assets.currencyIcon,
+ title: 'Currency',
+ subTitle: 'USD',
+ ),
+ MenuItemModel(
+ svgAssetIconPath: Assets.languageIcon,
+ title: 'Language',
+ subTitle: 'ENGLISH',
+ ),
+ ];
+ static const List bottomMenuList = [
+ MenuItemModel(
+ svgAssetIconPath: Assets.bellIcon,
+ title: 'Notification Settings',
+ ),
+ MenuItemModel(
+ svgAssetIconPath: Assets.shieldIcon,
+ title: 'Privacy Policy',
+ ),
+ MenuItemModel(
+ svgAssetIconPath: Assets.discussIssueIcon,
+ title: 'Frequently Asked Questions',
+ ),
+ MenuItemModel(
+ svgAssetIconPath: Assets.checkFormIcon,
+ title: 'Legal Information',
+ ),
+ ];
+
+ static const EdgeInsets menuListMargin =
+ EdgeInsets.symmetric(horizontal: Insets.x5);
+
+ @override
+ Widget build(BuildContext context) {
+ return SingleChildScrollView(
+ scrollDirection: Axis.vertical,
+ child: Column(
+ mainAxisAlignment: MainAxisAlignment.start,
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Padding(
+ padding: const EdgeInsets.symmetric(horizontal: Insets.x6),
+ child: Text(
+ I18n.of(context).morePage,
+ style: Theme.of(context).textTheme.headline6,
+ ),
+ ),
+ const SizedBox(height: 35.0),
+ const MenuList(
+ margin: menuListMargin,
+ itemList: topMenuList,
+ ),
+ const SizedBox(height: 15.0),
+ const MenuList(
+ margin: menuListMargin,
+ itemList: bottomMenuList,
+ ),
+ const SizedBox(height: 40.0),
+ _buildLogOutButton(context),
+ const SizedBox(height: 30.0),
+ ],
+ ),
+ );
+ }
+
+ Widget _buildLogOutButton(BuildContext context) {
+ return Center(
+ child: CupertinoButton(
+ onPressed: logOutPressHandler,
+ child: Text(
+ I18n.of(context).logOut,
+ style: Theme.of(context)
+ .textTheme
+ .headline5
+ .apply(color: BrandingColors.primary),
+ ), //TODO use proovider
+ ),
+ );
+ }
+
+ Future logOutPressHandler() async {
+ await authorizationService.logOut();
+ await navigationService.navigateWithReplacementTo(Pages.authorization);
+ }
+}
diff --git a/ecommers/lib/ui/pages/profile_page.dart b/ecommers/lib/ui/pages/profile_page.dart
new file mode 100644
index 0000000..ca04d2d
--- /dev/null
+++ b/ecommers/lib/ui/pages/profile_page.dart
@@ -0,0 +1,142 @@
+import 'package:ecommers/generated/i18n.dart';
+import 'package:ecommers/ui/decorations/assets.dart';
+import 'package:ecommers/ui/decorations/dimens/index.dart';
+import 'package:ecommers/ui/decorations/index.dart';
+import 'package:ecommers/ui/widgets/menu/index.dart';
+import 'package:ecommers/ui/widgets/menu/menu_item_model.dart';
+import 'package:flutter/cupertino.dart';
+import 'package:flutter/material.dart';
+
+class ProfilePage extends StatelessWidget {
+ static const List _topMenuList = [
+ MenuItemModel(
+ svgAssetIconPath: Assets.allOrderIcon,
+ title: 'All My Orders',
+ ),
+ MenuItemModel(
+ svgAssetIconPath: Assets.pendingShipmentIcon,
+ title: 'Pending Shipments',
+ ),
+ MenuItemModel(
+ svgAssetIconPath: Assets.pendingPaymentIcon,
+ title: 'Pending Payments',
+ ),
+ MenuItemModel(
+ svgAssetIconPath: Assets.finishedOrdersIcon,
+ title: 'Finished Orders',
+ ),
+ ];
+
+ static const List _bottomMenuList = [
+ MenuItemModel(
+ svgAssetIconPath: Assets.inviteFriendsIcon,
+ title: 'Invite Friends',
+ ),
+ MenuItemModel(
+ svgAssetIconPath: Assets.supportIcon,
+ title: 'Customer Support',
+ ),
+ MenuItemModel(
+ svgAssetIconPath: Assets.rateAppIcon,
+ title: 'Rate Our App',
+ ),
+ MenuItemModel(
+ svgAssetIconPath: Assets.suggestIcon,
+ title: 'Make a Suggestion',
+ ),
+ ];
+
+ static const EdgeInsets _listContainerMargin =
+ EdgeInsets.symmetric(horizontal: Insets.x5);
+
+ static const double _profileCardHeight = 100.0;
+ static const double _profileCardEditButtonHeight = 30.0;
+
+ @override
+ Widget build(BuildContext context) {
+ return SingleChildScrollView(
+ scrollDirection: Axis.vertical,
+ child: Column(
+ children: [
+ _buildProfileCard(context),
+ const MenuList(
+ margin: _listContainerMargin,
+ itemList: _topMenuList,
+ ),
+ const SizedBox(height: 15.0),
+ const MenuList(
+ margin: _listContainerMargin,
+ itemList: _bottomMenuList,
+ ),
+ const SizedBox(height: 20.0),
+ ],
+ ),
+ );
+ }
+
+ Widget _buildProfileCard(BuildContext context) {
+ return Container(
+ height: _profileCardHeight,
+ margin: const EdgeInsets.all(Insets.x6),
+ child: Row(
+ children: [
+ Container(
+ height: _profileCardHeight,
+ width: _profileCardHeight,
+ decoration: const BoxDecoration(
+ shape: BoxShape.circle,
+ image: DecorationImage(
+ image: AssetImage(Assets.girlImage),
+ fit: BoxFit.cover,
+ ),
+ ),
+ ),
+ const SizedBox(width: 20.0),
+ Flexible(
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Text(
+ 'Jane Doe', //TODO: get from the provider
+ maxLines: Dimens.defaultTextMaxLines,
+ overflow: TextOverflow.ellipsis,
+ style: Theme.of(context).textTheme.headline6,
+ ),
+ Expanded(
+ child: Text(
+ 'janedoe123@email.com', //TODO: get from the provider
+ maxLines: Dimens.defaultTextMaxLines,
+ overflow: TextOverflow.ellipsis,
+ style: Theme.of(context).textTheme.bodyText1,
+ ),
+ ),
+ _buildEditProfileButton(context),
+ ],
+ ),
+ ),
+ ],
+ ),
+ );
+ }
+
+ Widget _buildEditProfileButton(BuildContext context) {
+ return SizedBox(
+ height: _profileCardEditButtonHeight,
+ child: OutlineButton(
+ borderSide: BorderSide(
+ color: BrandingColors.secondary.withOpacity(0.3),
+ width: 1.0,
+ ),
+ highlightedBorderColor: BrandingColors.secondary.withOpacity(0.3),
+ shape: RoundedRectangleBorder(
+ borderRadius: BorderRadius.circular(Radiuses.big_2x),
+ ),
+ onPressed: () {},
+ child: Text(
+ I18n.of(context).editProfile,
+ style: Theme.of(context).textTheme.button,
+ ),
+ ),
+ );
+ }
+}
diff --git a/ecommers/lib/ui/pages/search_page.dart b/ecommers/lib/ui/pages/search_page.dart
new file mode 100644
index 0000000..9175b75
--- /dev/null
+++ b/ecommers/lib/ui/pages/search_page.dart
@@ -0,0 +1,8 @@
+import 'package:flutter/material.dart';
+
+class SearchPage extends StatelessWidget {
+ @override
+ Widget build(BuildContext context) {
+ return const Text('Search');
+ }
+}
diff --git a/ecommers/lib/ui/pages/shell_page.dart b/ecommers/lib/ui/pages/shell_page.dart
new file mode 100644
index 0000000..f027691
--- /dev/null
+++ b/ecommers/lib/ui/pages/shell_page.dart
@@ -0,0 +1,83 @@
+import 'package:ecommers/core/common/index.dart';
+import 'package:ecommers/core/provider_models/index.dart';
+import 'package:ecommers/ui/decorations/index.dart';
+import 'package:ecommers/ui/notifier_provider_widget.dart';
+import 'package:ecommers/ui/pages/index.dart';
+import 'package:ecommers/ui/widgets/bottom_navigation/bottom_navigation_widget.dart';
+import 'package:ecommers/ui/widgets/index.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter_svg/svg.dart';
+
+class ShellPage extends StatefulWidget {
+ @override
+ _ShellPageState createState() => _ShellPageState();
+}
+
+class _ShellPageState extends State {
+ @override
+ Widget build(BuildContext context) {
+ return NotifierProviderWidget(
+ providerModel: ShellProviderModel(),
+ builder: (context, ShellProviderModel model, child) {
+ return Scaffold(
+ appBar: AppBar(
+ actions: [
+ _buildAction(
+ imageAssetPath: Assets.messagesIcon,
+ onIconPressedFuction: () {}, //TODO get from provider
+ badgeValue: 5, //TODO get from provider
+ ),
+ _buildAction(
+ imageAssetPath: Assets.notificationIcon,
+ onIconPressedFuction: () {}, //TODO get from provider
+ badgeValue: 6, //TODO get from provider
+ ),
+ ],
+ ),
+ backgroundColor: BrandingColors.pageBackground,
+ body: BackgroundedSafeArea(
+ child: _buildBody(model.selectedPage),
+ ),
+ bottomNavigationBar: BottomNavigationWidget(
+ selectedIndex: model.selectedItemIndex,
+ pages: model.pages,
+ onTappedFunction: model.onTappedItem,
+ orderCount: 3,
+ ),
+ );
+ },
+ );
+ }
+
+ Widget _buildBody(Pages pageType) {
+ switch (pageType) {
+ case Pages.home:
+ return HomePage();
+ case Pages.search:
+ return SearchPage();
+ case Pages.cart:
+ return CartPage();
+ case Pages.profile:
+ return ProfilePage();
+ case Pages.more:
+ return MorePage();
+ default:
+ return HomePage();
+ }
+ }
+
+ Widget _buildAction({
+ String imageAssetPath,
+ Function() onIconPressedFuction,
+ int badgeValue,
+ }) {
+ return IconButton(
+ icon: IconWithBadge(
+ badgeValue: badgeValue,
+ badgeTextStyle: Theme.of(context).textTheme.overline,
+ icon: SvgPicture.asset(imageAssetPath),
+ ),
+ onPressed: onIconPressedFuction,
+ );
+ }
+}
diff --git a/ecommers/lib/ui/pages/success_page.dart b/ecommers/lib/ui/pages/success_page.dart
new file mode 100644
index 0000000..8b857f5
--- /dev/null
+++ b/ecommers/lib/ui/pages/success_page.dart
@@ -0,0 +1,57 @@
+import 'package:ecommers/generated/i18n.dart';
+import 'package:ecommers/ui/decorations/assets.dart';
+import 'package:ecommers/ui/pages/closeable_page.dart';
+import 'package:ecommers/ui/widgets/button/index.dart';
+import 'package:ecommers/ui/widgets/order/index.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter_svg/svg.dart';
+
+class SuccessPage extends StatelessWidget {
+ static const circleImageSize = Size(101.0, 101.0);
+
+ const SuccessPage();
+
+ @override
+ Widget build(BuildContext context) {
+ return CloseablePage(
+ child: SingleChildScrollView(
+ child: Align(
+ alignment: Alignment.center,
+ child: Column(
+ mainAxisAlignment: MainAxisAlignment.center,
+ children: [
+ CircleImage(
+ image: SvgPicture.asset(
+ Assets.successIcon,
+ fit: BoxFit.scaleDown,
+ ),
+ size: circleImageSize,
+ ),
+ const SizedBox(height: 28.0),
+ Text('John Doe', //TODO from provider
+ style: Theme.of(context).textTheme.headline6),
+ const SizedBox(height: 14.0),
+ SizedBox(
+ width: 252.0,
+ child: Text(
+ I18n.of(context).successMessage,
+ style: Theme.of(context).textTheme.subtitle1,
+ textAlign: TextAlign.center,
+ ),
+ ),
+ const SizedBox(height: 50.0),
+ SizedBox(
+ width: 165.0,
+ child: PrimaryButtonWidget(
+ text: 'MY ORDERS',
+ onPressedFunction: () {},
+ ),
+ ),
+ const SizedBox(height: 20.0),
+ ],
+ ),
+ ),
+ ),
+ );
+ }
+}
diff --git a/ecommers/lib/ui/utils/dialog_manager.dart b/ecommers/lib/ui/utils/dialog_manager.dart
new file mode 100644
index 0000000..eebd6c1
--- /dev/null
+++ b/ecommers/lib/ui/utils/dialog_manager.dart
@@ -0,0 +1,15 @@
+import 'package:flutter/material.dart';
+
+class DialogManager {
+ static Future showAlertDialog(
+ BuildContext context, String title, String contentText) async {
+ await showDialog(
+ context: context,
+ builder: (_) => AlertDialog(
+ elevation: 10.0,
+ title: Text(title),
+ content: Text(contentText),
+ ),
+ );
+ }
+}
diff --git a/ecommers/lib/ui/utils/formatter.dart b/ecommers/lib/ui/utils/formatter.dart
new file mode 100644
index 0000000..cdd8c2d
--- /dev/null
+++ b/ecommers/lib/ui/utils/formatter.dart
@@ -0,0 +1,16 @@
+import 'package:intl/intl.dart';
+
+class Formatter {
+ static String getCost(double cost) {
+ final currencyFormatter = NumberFormat.simpleCurrency();
+
+ return currencyFormatter.format(cost);
+ }
+ static String getTextWithNumberCard(String nuberCard){
+ const visibleCardSymbolCount = 2;
+
+ final visibleSymbols = nuberCard.substring(nuberCard.length - visibleCardSymbolCount);
+
+ return 'Master Card ending **$visibleSymbols';
+ }
+}
\ No newline at end of file
diff --git a/ecommers/lib/ui/widgets/authorization/auth_rich_text.dart b/ecommers/lib/ui/widgets/authorization/auth_rich_text.dart
new file mode 100644
index 0000000..dacd905
--- /dev/null
+++ b/ecommers/lib/ui/widgets/authorization/auth_rich_text.dart
@@ -0,0 +1,111 @@
+import 'package:ecommers/core/models/index.dart';
+import 'package:ecommers/ui/decorations/dimens/index.dart';
+import 'package:ecommers/ui/decorations/index.dart';
+import 'package:flutter/gestures.dart';
+import 'package:flutter/material.dart';
+
+class AuthRichText extends StatefulWidget {
+ final List textSpanModelList;
+
+ const AuthRichText({this.textSpanModelList});
+
+ @override
+ _AuthRichTextState createState() => _AuthRichTextState();
+}
+
+class _AuthRichTextState extends State {
+ List _textTapRecognizerList;
+ TextStyle baseTextStyle;
+
+ @override
+ void dispose() {
+ _textTapRecognizerList.forEach(disposeRecognizer);
+ super.dispose();
+ }
+
+ @override
+ void initState() {
+ _textTapRecognizerList = [];
+ super.initState();
+ }
+
+ void disposeRecognizer(TapGestureRecognizer recognizer) {
+ recognizer.dispose();
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ baseTextStyle = Theme.of(context).textTheme.bodyText2.copyWith(
+ fontSize: FontSizes.small_3x,
+ );
+
+ final firstTextSpanModel = widget.textSpanModelList[0];
+ final childTextSpanModelList = widget.textSpanModelList.skip(1).toList();
+
+ return RichText(
+ textAlign: TextAlign.center,
+ text: _buildTextSpan(firstTextSpanModel, childTextSpanModelList),
+ );
+ }
+
+ TextSpan _buildTextSpan(AuthRichTextSpanModel textModel,
+ [List childens]) {
+ return textModel.isTappable
+ ? _buildTapableTextSpan(textModel, childens)
+ : _buildDefaultTextSpan(textModel, childens);
+ }
+
+ TextSpan _buildDefaultTextSpan(AuthRichTextSpanModel textModel,
+ [List childens]) {
+ return TextSpan(
+ text: textModel.text,
+ style: baseTextStyle,
+ children: [..._buildChildTextSpanList(childens)],
+ );
+ }
+
+ TextSpan _buildTapableTextSpan(AuthRichTextSpanModel textModel,
+ [List childens]) {
+ return TextSpan(
+ text: textModel.text,
+ style: baseTextStyle.copyWith(color: BrandingColors.primary),
+ recognizer: _getRecognizerFor(textModel),
+ children: [..._buildChildTextSpanList(childens)],
+ );
+ }
+
+ Iterable _buildChildTextSpanList(
+ List childTextSpanModelList) sync* {
+ if (childTextSpanModelList != null) {
+ for (final item in childTextSpanModelList) {
+ yield _buildTextSpan(item);
+ }
+ }
+ }
+
+ TapGestureRecognizer _getRecognizerFor(AuthRichTextSpanModel textModel) {
+ if (!textModel.isTappable) return null;
+
+ if (_textTapRecognizerList.isEmpty) {
+ return _createRecognizer(textModel.onTap);
+ }
+
+ final recogizerIndex = widget.textSpanModelList
+ .where((element) => element.isTappable)
+ .toList()
+ .indexOf(textModel);
+
+ if (recogizerIndex < _textTapRecognizerList.length && recogizerIndex >= 0) {
+ return _textTapRecognizerList[recogizerIndex];
+ }
+
+ return _createRecognizer(textModel.onTap);
+ }
+
+ TapGestureRecognizer _createRecognizer(Function() onTap) {
+ final recognizer = TapGestureRecognizer()..onTap = onTap;
+ _textTapRecognizerList.add(recognizer);
+
+ return recognizer;
+ }
+}
diff --git a/ecommers/lib/ui/widgets/authorization/auth_text_field.dart b/ecommers/lib/ui/widgets/authorization/auth_text_field.dart
new file mode 100644
index 0000000..3aff641
--- /dev/null
+++ b/ecommers/lib/ui/widgets/authorization/auth_text_field.dart
@@ -0,0 +1,48 @@
+import 'package:flutter/cupertino.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter/services.dart';
+import 'package:flutter_svg/svg.dart';
+
+class AuthTextField extends StatelessWidget {
+ final TextEditingController controller;
+ final String labelText;
+ final String assetIconPath;
+ final TextInputType keyboardType;
+ final bool obscureText;
+ final Function(String) onChanged;
+
+ const AuthTextField({
+ this.labelText = '',
+ this.assetIconPath = '',
+ this.keyboardType = TextInputType.text,
+ this.obscureText = false,
+ this.controller,
+ this.onChanged,
+ });
+
+ @override
+ Widget build(BuildContext context) {
+ return TextField(
+ controller: controller,
+ keyboardType: keyboardType,
+ obscureText: obscureText,
+ onChanged: onChanged,
+ decoration: InputDecoration(
+ labelText: labelText,
+ labelStyle: Theme.of(context).textTheme.headline5,
+ prefixIcon: SvgPicture.asset(
+ assetIconPath,
+ fit: BoxFit.scaleDown,
+ ),
+ focusedBorder: UnderlineInputBorder(
+ borderSide: BorderSide(color: Colors.transparent),
+ ),
+ enabledBorder: UnderlineInputBorder(
+ borderSide: BorderSide(color: Colors.transparent),
+ ),
+ fillColor: Colors.transparent,
+ filled: true,
+ ),
+ );
+ }
+}
diff --git a/ecommers/lib/ui/widgets/authorization/auth_text_field_area_container.dart b/ecommers/lib/ui/widgets/authorization/auth_text_field_area_container.dart
new file mode 100644
index 0000000..f75f62e
--- /dev/null
+++ b/ecommers/lib/ui/widgets/authorization/auth_text_field_area_container.dart
@@ -0,0 +1,28 @@
+import 'package:ecommers/ui/decorations/dimens/index.dart';
+import 'package:ecommers/ui/decorations/index.dart';
+import 'package:flutter/material.dart';
+
+class AuthTextFieldAreaContainer extends StatelessWidget {
+ final Widget child;
+
+ const AuthTextFieldAreaContainer({this.child});
+
+ @override
+ Widget build(BuildContext context) {
+ return Container(
+ alignment: Alignment.center,
+ decoration: BoxDecoration(
+ borderRadius: BorderRadius.circular(Radiuses.normal),
+ color: BrandingColors.background,
+ boxShadow: const [
+ BoxShadow(
+ offset: Dimens.defaultBlurOffset,
+ blurRadius: Radiuses.big_1x,
+ color: BrandingColors.blur,
+ )
+ ],
+ ),
+ child: child,
+ );
+ }
+}
\ No newline at end of file
diff --git a/ecommers/lib/ui/widgets/authorization/index.dart b/ecommers/lib/ui/widgets/authorization/index.dart
new file mode 100644
index 0000000..9746121
--- /dev/null
+++ b/ecommers/lib/ui/widgets/authorization/index.dart
@@ -0,0 +1,3 @@
+export 'auth_rich_text.dart';
+export 'auth_text_field.dart';
+export 'auth_text_field_area_container.dart';
diff --git a/ecommers/lib/ui/widgets/backgrounded_safe_area.dart b/ecommers/lib/ui/widgets/backgrounded_safe_area.dart
new file mode 100644
index 0000000..5cf1c3a
--- /dev/null
+++ b/ecommers/lib/ui/widgets/backgrounded_safe_area.dart
@@ -0,0 +1,21 @@
+import 'package:ecommers/ui/decorations/index.dart';
+import 'package:flutter/material.dart';
+
+class BackgroundedSafeArea extends StatelessWidget {
+ final Widget child;
+ final bool isBottom;
+
+ const BackgroundedSafeArea({
+ Key key,
+ this.child,
+ this.isBottom = true,
+ }) : super(key: key);
+
+ @override
+ Widget build(BuildContext context) {
+ return Container(
+ color: BrandingColors.pageBackground,
+ child: SafeArea(bottom: isBottom, child: child),
+ );
+ }
+}
diff --git a/ecommers/lib/ui/widgets/bottom_navigation/bottom_navigation_item_model.dart b/ecommers/lib/ui/widgets/bottom_navigation/bottom_navigation_item_model.dart
new file mode 100644
index 0000000..a60610d
--- /dev/null
+++ b/ecommers/lib/ui/widgets/bottom_navigation/bottom_navigation_item_model.dart
@@ -0,0 +1,11 @@
+import 'package:flutter/material.dart';
+
+class BottomNavigationItemModel {
+ final IconData icon;
+ final String title;
+
+ BottomNavigationItemModel({
+ @required this.icon,
+ @required this.title,
+ });
+}
diff --git a/ecommers/lib/ui/widgets/bottom_navigation/bottom_navigation_widget.dart b/ecommers/lib/ui/widgets/bottom_navigation/bottom_navigation_widget.dart
new file mode 100644
index 0000000..3ee76d2
--- /dev/null
+++ b/ecommers/lib/ui/widgets/bottom_navigation/bottom_navigation_widget.dart
@@ -0,0 +1,56 @@
+import 'package:ecommers/core/common/index.dart';
+import 'package:ecommers/ui/decorations/index.dart';
+import 'package:ecommers/ui/widgets/bottom_navigation/buttom_bar_item_icon.dart';
+import 'package:ecommers/ui/widgets/bottom_navigation/consts.dart';
+import 'package:flutter/material.dart';
+
+class BottomNavigationWidget extends StatefulWidget {
+ final Iterable pages;
+ final int selectedIndex;
+ final Function(int) onTappedFunction;
+ final int orderCount;
+
+ const BottomNavigationWidget({
+ @required this.pages,
+ @required this.selectedIndex,
+ @required this.onTappedFunction,
+ this.orderCount = 0,
+ });
+
+ @override
+ _BottomNavigationWidgetState createState() => _BottomNavigationWidgetState();
+}
+
+class _BottomNavigationWidgetState extends State {
+ @override
+ Widget build(BuildContext context) {
+ return BottomNavigationBar(
+ backgroundColor: BrandingColors.background,
+ items: _createBottomNavigationBarItems(),
+ unselectedItemColor: BrandingColors.primaryText,
+ selectedItemColor: BrandingColors.primary,
+ currentIndex: widget.selectedIndex,
+ selectedLabelStyle: Theme.of(context).textTheme.caption,
+ unselectedLabelStyle: Theme.of(context).textTheme.caption,
+ iconSize: 26.0,
+ showUnselectedLabels: true,
+ type: BottomNavigationBarType.fixed,
+ onTap: widget.onTappedFunction,
+ );
+ }
+
+ List _createBottomNavigationBarItems() {
+ return widget.pages
+ .map(
+ (page) => BottomNavigationBarItem(
+ icon: ButtomBarItemIcon(
+ iconData: bottomNavigationItems[page].icon,
+ hasBadge: page == Pages.cart,
+ badgeValue: page == Pages.cart ? widget.orderCount : 0,
+ ),
+ title: Text(bottomNavigationItems[page].title),
+ ),
+ )
+ .toList();
+ }
+}
diff --git a/ecommers/lib/ui/widgets/bottom_navigation/buttom_bar_item_icon.dart b/ecommers/lib/ui/widgets/bottom_navigation/buttom_bar_item_icon.dart
new file mode 100644
index 0000000..a3dfd25
--- /dev/null
+++ b/ecommers/lib/ui/widgets/bottom_navigation/buttom_bar_item_icon.dart
@@ -0,0 +1,36 @@
+import 'package:badges/badges.dart';
+import 'package:ecommers/ui/decorations/dimens/insets.dart';
+import 'package:ecommers/ui/widgets/index.dart';
+import 'package:flutter/material.dart';
+
+class ButtomBarItemIcon extends StatelessWidget {
+ final IconData iconData;
+ final bool hasBadge;
+ final int badgeValue;
+
+ static const _iconWithBadgeWidth = 41.0;
+
+ const ButtomBarItemIcon({
+ @required this.iconData,
+ this.hasBadge = false,
+ this.badgeValue = 0,
+ });
+
+ @override
+ Widget build(BuildContext context) {
+ if (hasBadge) {
+ return Container(
+ alignment: Alignment.center,
+ width: _iconWithBadgeWidth,
+ child: IconWithBadge(
+ badgePosition:
+ BadgePosition.bottomLeft(bottom: Insets.x1, left: -Insets.x4),
+ badgeTextStyle: Theme.of(context).textTheme.overline,
+ icon: Icon(iconData),
+ badgeValue: badgeValue,
+ ),
+ );
+ }
+ return Icon(iconData);
+ }
+}
diff --git a/ecommers/lib/ui/widgets/bottom_navigation/consts.dart b/ecommers/lib/ui/widgets/bottom_navigation/consts.dart
new file mode 100644
index 0000000..34ac11d
--- /dev/null
+++ b/ecommers/lib/ui/widgets/bottom_navigation/consts.dart
@@ -0,0 +1,11 @@
+import 'package:ecommers/core/common/index.dart';
+import 'package:ecommers/ui/widgets/bottom_navigation/bottom_navigation_item_model.dart';
+import 'package:flutter/material.dart';
+
+final Map bottomNavigationItems = {
+ Pages.home: BottomNavigationItemModel(icon: Icons.home, title: 'Home'),
+ Pages.search: BottomNavigationItemModel(icon: Icons.search, title: 'Search'),
+ Pages.cart: BottomNavigationItemModel(icon: Icons.shopping_cart, title: 'Cart'),
+ Pages.profile: BottomNavigationItemModel(icon: Icons.person_outline, title: 'Profile'),
+ Pages.more: BottomNavigationItemModel(icon: Icons.menu, title: 'More'),
+};
diff --git a/ecommers/lib/ui/widgets/button/button_base_widget.dart b/ecommers/lib/ui/widgets/button/button_base_widget.dart
new file mode 100644
index 0000000..8db3433
--- /dev/null
+++ b/ecommers/lib/ui/widgets/button/button_base_widget.dart
@@ -0,0 +1,80 @@
+import 'package:flutter/cupertino.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter_svg/svg.dart';
+import '../../decorations/dimens/index.dart';
+
+class ButtonBaseWidget extends StatelessWidget {
+ static const double _circleSize = 30.0;
+ static const double _iconHeight = 12.0;
+
+ final Color buttonColor;
+ final Color textColor;
+ final Color iconBackgroundColor;
+ final Color blurColor;
+ final String text;
+ final String assetIcon;
+ final Function() onPressedFunction;
+
+ const ButtonBaseWidget({
+ @required this.text,
+ @required this.assetIcon,
+ @required this.buttonColor,
+ @required this.textColor,
+ @required this.blurColor,
+ @required this.onPressedFunction,
+ this.iconBackgroundColor,
+ });
+
+ @override
+ Widget build(BuildContext context) {
+ return Container(
+ decoration: BoxDecoration(
+ boxShadow: [
+ BoxShadow(
+ blurRadius: Radiuses.normal,
+ offset: const Offset(Insets.x0, Insets.x1_5),
+ color: blurColor,
+ ),
+ ],
+ borderRadius: BorderRadius.circular(Radiuses.big_2x),
+ ),
+ child: CupertinoButton(
+ padding: const EdgeInsets.all(Insets.x2),
+ borderRadius: BorderRadius.circular(Radiuses.big_2x),
+ color: buttonColor,
+ onPressed: onPressedFunction,
+ child: Row(
+ mainAxisAlignment: MainAxisAlignment.center,
+ children: [
+ Expanded(
+ child: Align(
+ alignment: Alignment.center,
+ child: Text(
+ text.toUpperCase(),
+ style: Theme.of(context)
+ .textTheme
+ .button
+ .copyWith(color: textColor),
+ ),
+ ),
+ ),
+ Container(
+ alignment: Alignment.center,
+ height: _circleSize,
+ width: _circleSize,
+ decoration: BoxDecoration(
+ shape: BoxShape.circle,
+ color: iconBackgroundColor ?? textColor,
+ ),
+ child: SvgPicture.asset(
+ assetIcon,
+ height: _iconHeight,
+ color: buttonColor,
+ ),
+ )
+ ],
+ ),
+ ),
+ );
+ }
+}
diff --git a/ecommers/lib/ui/widgets/button/index.dart b/ecommers/lib/ui/widgets/button/index.dart
new file mode 100644
index 0000000..1321e3b
--- /dev/null
+++ b/ecommers/lib/ui/widgets/button/index.dart
@@ -0,0 +1,3 @@
+export 'button_base_widget.dart';
+export 'primary_button_widget.dart';
+export 'secondary_button_widget.dart';
diff --git a/ecommers/lib/ui/widgets/button/primary_button_widget.dart b/ecommers/lib/ui/widgets/button/primary_button_widget.dart
new file mode 100644
index 0000000..df36230
--- /dev/null
+++ b/ecommers/lib/ui/widgets/button/primary_button_widget.dart
@@ -0,0 +1,18 @@
+import 'package:ecommers/ui/decorations/index.dart';
+import 'package:ecommers/ui/widgets/button/index.dart';
+import 'package:flutter/material.dart';
+
+class PrimaryButtonWidget extends ButtonBaseWidget {
+ PrimaryButtonWidget({
+ @required String text,
+ @required Function() onPressedFunction,
+ String assetIconPath = Assets.arrowRightIcon,
+ }) : super(
+ text: text,
+ assetIcon: assetIconPath,
+ buttonColor: BrandingColors.primary,
+ textColor: BrandingColors.secondaryText,
+ onPressedFunction: onPressedFunction,
+ blurColor: BrandingColors.primary.withOpacity(0.4),
+ );
+}
diff --git a/ecommers/lib/ui/widgets/button/secondary_button_widget.dart b/ecommers/lib/ui/widgets/button/secondary_button_widget.dart
new file mode 100644
index 0000000..71eb1f6
--- /dev/null
+++ b/ecommers/lib/ui/widgets/button/secondary_button_widget.dart
@@ -0,0 +1,18 @@
+import 'package:ecommers/ui/decorations/index.dart';
+import 'package:ecommers/ui/widgets/button/index.dart';
+import 'package:flutter/material.dart';
+
+class SecondaryButtonWidget extends ButtonBaseWidget {
+ const SecondaryButtonWidget({
+ @required String text,
+ @required String assetIcon,
+ @required Function() onPressedFunction,
+ }) : super(
+ text: text,
+ assetIcon: assetIcon,
+ buttonColor: BrandingColors.background,
+ textColor: BrandingColors.secondary,
+ onPressedFunction: onPressedFunction,
+ blurColor: BrandingColors.secondary,
+ );
+}
diff --git a/ecommers/lib/ui/widgets/category_item/categories_compact_widget.dart b/ecommers/lib/ui/widgets/category_item/categories_compact_widget.dart
new file mode 100644
index 0000000..8ee5319
--- /dev/null
+++ b/ecommers/lib/ui/widgets/category_item/categories_compact_widget.dart
@@ -0,0 +1,92 @@
+import 'package:ecommers/core/common/index.dart';
+import 'package:ecommers/core/services/index.dart';
+import 'package:ecommers/generated/i18n.dart';
+import 'package:ecommers/ui/decorations/dimens/index.dart';
+import 'package:ecommers/ui/decorations/index.dart';
+import 'package:ecommers/ui/widgets/category_item/category_item.dart';
+import 'package:flutter/material.dart';
+
+class CategoriesCompactWidget extends StatelessWidget {
+ static const _containerHeight = 134.0;
+
+ static const categoryItemSize = Size(74.0, 89.0);
+
+ @override
+ Widget build(BuildContext context) {
+ return Container(
+ height: _containerHeight,
+ padding: const EdgeInsets.symmetric(horizontal: Insets.x6),
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Expanded(
+ child: Text(
+ I18n.of(context).categoriesTitle,
+ style: Theme.of(context).textTheme.headline6,
+ ),
+ ),
+ _createCategoriesListWidget(context),
+ ],
+ ),
+ );
+ }
+
+ Widget _createCategoriesListWidget(BuildContext context) {
+ final itemCount = _calculateItemCount(context);
+ final spacing = _calculateItemSpacing(context, itemCount);
+
+ return SizedBox(
+ height: categoryItemSize.height,
+ child: ListView.separated(
+ itemCount: itemCount,
+ separatorBuilder: (BuildContext context, int index) => SizedBox(
+ width: spacing,
+ height: spacing,
+ ),
+ scrollDirection: Axis.horizontal,
+ itemBuilder: (BuildContext context, int index) {
+ if (index == itemCount - 1 && index != Categories.values.length - 1) {
+ return _buildSeeAllCategory(context);
+ }
+
+ return CategoryItem.fromType(Categories.values[index]);
+ },
+ ),
+ );
+ }
+
+ Widget _buildSeeAllCategory(BuildContext context) {
+ return CategoryItem(
+ backgroundColor: BrandingColors.background,
+ shadowColor: BrandingColors.blur,
+ imagePath: Assets.arrowRightIcon,
+ title: I18n.of(context).seeAllCategoryTitle,
+ onTapFunction: () => navigationService.navigateTo(Pages.categories),
+ );
+ }
+
+ int _calculateItemCount(BuildContext context) {
+ final categoriesListWidth =
+ MediaQuery.of(context).size.width - Dimens.pagePadding * 2;
+
+ var itemCount = categoriesListWidth ~/ categoryItemSize.width;
+
+ if (itemCount > Categories.values.length) {
+ itemCount = Categories.values.length;
+ } else {
+ itemCount = itemCount;
+ }
+
+ return itemCount;
+ }
+
+ double _calculateItemSpacing(BuildContext context, int itemCount) {
+ final categoriesListWidth =
+ MediaQuery.of(context).size.width - Dimens.pagePadding * 2;
+
+ final calculatedListSpacing =
+ (categoriesListWidth % categoryItemSize.width) / (itemCount - 1);
+
+ return calculatedListSpacing;
+ }
+}
diff --git a/ecommers/lib/ui/widgets/category_item/category_item.dart b/ecommers/lib/ui/widgets/category_item/category_item.dart
new file mode 100644
index 0000000..7872aa2
--- /dev/null
+++ b/ecommers/lib/ui/widgets/category_item/category_item.dart
@@ -0,0 +1,88 @@
+import 'package:ecommers/ui/decorations/dimens/index.dart';
+import 'package:ecommers/core/common/index.dart';
+import 'package:ecommers/ui/widgets/category_item/consts.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter_svg/svg.dart';
+
+class CategoryItem extends StatelessWidget {
+ final Gradient labelBackgroundGradient;
+ final Color shadowColor;
+ final Color backgroundColor;
+ final String imagePath;
+ final String title;
+ final Function() onTapFunction;
+
+ static const size = Size(74.0, 89.0);
+ static const categoryLabelSize = 65.0;
+
+ const CategoryItem({
+ @required this.shadowColor,
+ @required this.imagePath,
+ @required this.title,
+ this.labelBackgroundGradient,
+ this.backgroundColor,
+ this.onTapFunction,
+ });
+
+ factory CategoryItem.fromType(Categories categoryType) {
+ final categoryItem = categoryItems[categoryType];
+
+ return CategoryItem(
+ labelBackgroundGradient: categoryItem.gradient,
+ shadowColor: categoryItem.shadowColor,
+ imagePath: categoryItem.imagePath,
+ title: categoryItem.title,
+ );
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ return GestureDetector(
+ onTap: onTapFunction,
+ child: Container(
+ width: size.width,
+ child: Column(
+ mainAxisSize: MainAxisSize.min,
+ children: [
+ _buildGradientLabel(),
+ const SizedBox(
+ height: labelBottomMargin,
+ ),
+ Text(
+ title,
+ style: Theme.of(context)
+ .textTheme
+ .caption
+ .copyWith(fontSize: FontSizes.normal),
+ ),
+ ],
+ ),
+ ),
+ );
+ }
+
+ Widget _buildGradientLabel() {
+ return Container(
+ alignment: Alignment.center,
+ height: categoryLabelSize,
+ width: categoryLabelSize,
+ decoration: BoxDecoration(
+ shape: BoxShape.circle,
+ color: backgroundColor,
+ boxShadow: [
+ BoxShadow(
+ color: shadowColor,
+ blurRadius: Radiuses.big_1x,
+ offset: Dimens.defaultBlurOffset),
+ ],
+ gradient: labelBackgroundGradient,
+ ),
+ child: Center(
+ child: SvgPicture.asset(
+ imagePath,
+ fit: BoxFit.scaleDown,
+ ),
+ ),
+ );
+ }
+}
diff --git a/ecommers/lib/ui/widgets/category_item/category_item_model.dart b/ecommers/lib/ui/widgets/category_item/category_item_model.dart
new file mode 100644
index 0000000..b3714b6
--- /dev/null
+++ b/ecommers/lib/ui/widgets/category_item/category_item_model.dart
@@ -0,0 +1,14 @@
+import 'package:flutter/material.dart';
+
+class CategoryItemModel {
+ final Gradient gradient;
+ final Color shadowColor;
+ final String imagePath;
+ final String title;
+
+ CategoryItemModel(
+ {this.gradient,
+ this.shadowColor,
+ this.imagePath,
+ this.title});
+}
diff --git a/ecommers/lib/ui/widgets/category_item/consts.dart b/ecommers/lib/ui/widgets/category_item/consts.dart
new file mode 100644
index 0000000..ff4513a
--- /dev/null
+++ b/ecommers/lib/ui/widgets/category_item/consts.dart
@@ -0,0 +1,59 @@
+import 'package:ecommers/core/common/index.dart';
+import 'package:ecommers/ui/decorations/index.dart';
+import 'package:flutter/material.dart';
+
+import 'category_item_model.dart';
+
+const double labelBottomMargin = 5;
+Color apparelShadowColor = const Color(0xffFF6262).withOpacity(0.34);
+Color beautyShadowColor = const Color(0xff62AAFF).withOpacity(0.35);
+Color shoesShadowColor = const Color(0xff26D555).withOpacity(0.34);
+Color electronicsShadowColor = const Color(0xff7A71E8).withOpacity(0.34);
+Color furnitureShadowColor = const Color(0xffD59F26).withOpacity(0.34);
+Color homeShadowColor = const Color(0xff834162).withOpacity(0.34);
+Color stationaryShadowColor = const Color(0xff646464).withOpacity(0.34);
+
+final Map categoryItems = {
+ Categories.apparel: CategoryItemModel(
+ gradient: Gradients.apparelCategory,
+ shadowColor: apparelShadowColor,
+ imagePath: Assets.apparelIcon,
+ title: 'Apparel',
+ ),
+ Categories.beauty: CategoryItemModel(
+ gradient: Gradients.beautyCategory,
+ shadowColor: beautyShadowColor,
+ imagePath: Assets.beautyIcon,
+ title: 'Apparel',
+ ),
+ Categories.shoes: CategoryItemModel(
+ gradient: Gradients.shoesCategory,
+ shadowColor: shoesShadowColor,
+ imagePath: Assets.shoesIcon,
+ title: 'Apparel',
+ ),
+ Categories.electronics: CategoryItemModel(
+ gradient: Gradients.electronicsCategory,
+ shadowColor: electronicsShadowColor,
+ imagePath: Assets.electronicsIcon,
+ title: 'Apparel',
+ ),
+ Categories.furniture: CategoryItemModel(
+ gradient: Gradients.furnitureCategory,
+ shadowColor: furnitureShadowColor,
+ imagePath: Assets.furnitureIcon,
+ title: 'Apparel',
+ ),
+ Categories.home: CategoryItemModel(
+ gradient: Gradients.homeCategory,
+ shadowColor: homeShadowColor,
+ imagePath: Assets.homeIcon,
+ title: 'Apparel',
+ ),
+ Categories.stationary: CategoryItemModel(
+ gradient: Gradients.stationaryCategory,
+ shadowColor: stationaryShadowColor,
+ imagePath: Assets.stationaryIcon,
+ title: 'Apparel',
+ ),
+};
diff --git a/ecommers/lib/ui/widgets/circle_icon.dart b/ecommers/lib/ui/widgets/circle_icon.dart
new file mode 100644
index 0000000..d4f99ae
--- /dev/null
+++ b/ecommers/lib/ui/widgets/circle_icon.dart
@@ -0,0 +1,29 @@
+import 'package:ecommers/ui/decorations/index.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter_svg/svg.dart';
+
+class CircleIcon extends StatelessWidget {
+ static const size = Size(18.0, 18.0);
+
+ final String imagePath;
+
+ const CircleIcon({this.imagePath = Assets.arrowIcon});
+
+ @override
+ Widget build(BuildContext context) {
+ return Container(
+ width: size.width,
+ height: size.height,
+ alignment: Alignment.center,
+ decoration: const BoxDecoration(
+ shape: BoxShape.circle,
+ color: BrandingColors.backgroundIcon,
+ ),
+ child: SvgPicture.asset(
+ imagePath,
+ fit: BoxFit.scaleDown,
+ color: BrandingColors.secondary,
+ ),
+ );
+ }
+}
diff --git a/ecommers/lib/ui/widgets/icon_with_badge.dart b/ecommers/lib/ui/widgets/icon_with_badge.dart
new file mode 100644
index 0000000..6e7516b
--- /dev/null
+++ b/ecommers/lib/ui/widgets/icon_with_badge.dart
@@ -0,0 +1,39 @@
+import 'package:flutter/material.dart';
+import 'package:badges/badges.dart';
+import '../decorations/dimens/index.dart';
+import '../decorations/index.dart';
+
+class IconWithBadge extends Badge {
+ final int badgeValue;
+ final BadgePosition badgePosition;
+ final Widget icon;
+ final TextStyle badgeTextStyle;
+
+ IconWithBadge({
+ this.badgeValue = 0,
+ this.badgePosition,
+ this.badgeTextStyle,
+ @required this.icon,
+ }) : super(
+ child: icon,
+ badgeContent: Text(
+ badgeValue.toString(),
+ style: badgeTextStyle,
+ textAlign: TextAlign.center,
+ ),
+ position:
+ badgePosition ?? BadgePosition.bottomLeft(bottom: -4, left: -9),
+ shape: BadgeShape.square,
+ borderRadius: Radiuses.small_2x,
+ elevation: 1.0,
+ badgeColor: BrandingColors.primary,
+ animationType: BadgeAnimationType.scale,
+ showBadge: badgeValue != 0,
+ padding: const EdgeInsets.fromLTRB(
+ Insets.x1_5,
+ Insets.x0_5,
+ Insets.x1_5,
+ Insets.x0_5,
+ ),
+ );
+}
diff --git a/ecommers/lib/ui/widgets/image_card.dart b/ecommers/lib/ui/widgets/image_card.dart
new file mode 100644
index 0000000..6340c69
--- /dev/null
+++ b/ecommers/lib/ui/widgets/image_card.dart
@@ -0,0 +1,70 @@
+import 'package:ecommers/ui/decorations/dimens/index.dart';
+import 'package:ecommers/ui/decorations/index.dart';
+import 'package:ecommers/ui/widgets/button/index.dart';
+import 'package:flutter/material.dart';
+
+class ImageCard extends StatelessWidget {
+ static const Size _buttonSize = Size(120.0, 40.0);
+
+ static const double _textWidth = 134.0;
+ static const int _textMaxLines = 3;
+
+ static const double _borderRadius = 10.0;
+ static const imageCardSize = Size(325.0, 184.0);
+
+ final String imageAsset;
+ final String description;
+ final String buttonText;
+ final Function() onButtonPressed;
+
+ const ImageCard({
+ this.imageAsset,
+ this.description,
+ this.buttonText,
+ this.onButtonPressed,
+ });
+
+ @override
+ Widget build(BuildContext context) {
+ return Container(
+ alignment: Alignment.centerLeft,
+ height: imageCardSize.height,
+ width: imageCardSize.width,
+ padding: const EdgeInsets.all(Insets.x6_5),
+ decoration: BoxDecoration(
+ image: DecorationImage(
+ image: AssetImage(imageAsset),
+ fit: BoxFit.cover,
+ ),
+ borderRadius: BorderRadius.circular(_borderRadius),
+ ),
+ child: Column(
+ children: [
+ Expanded(
+ child: SizedBox(
+ width: _textWidth,
+ child: Text(
+ description,
+ maxLines: _textMaxLines,
+ style: Theme.of(context).textTheme.headline3,
+ ),
+ ),
+ ),
+
+ SizedBox(
+ height: _buttonSize.height,
+ width: _buttonSize.width,
+ child: ButtonBaseWidget(
+ text: buttonText,
+ assetIcon: Assets.arrowRightIcon,
+ buttonColor: BrandingColors.background,
+ textColor: BrandingColors.secondary,
+ onPressedFunction: onButtonPressed,
+ iconBackgroundColor: BrandingColors.primary,
+ blurColor: BrandingColors.secondary.withOpacity(0.15)),
+ ),
+ ],
+ ),
+ );
+ }
+}
diff --git a/ecommers/lib/ui/widgets/index.dart b/ecommers/lib/ui/widgets/index.dart
new file mode 100644
index 0000000..e4d236d
--- /dev/null
+++ b/ecommers/lib/ui/widgets/index.dart
@@ -0,0 +1,5 @@
+export 'backgrounded_safe_area.dart';
+export 'circle_icon.dart';
+export 'icon_with_badge.dart';
+export 'image_card.dart';
+export 'rate_widget.dart';
diff --git a/ecommers/lib/ui/widgets/menu/index.dart b/ecommers/lib/ui/widgets/menu/index.dart
new file mode 100644
index 0000000..469dad4
--- /dev/null
+++ b/ecommers/lib/ui/widgets/menu/index.dart
@@ -0,0 +1,3 @@
+export 'menu_item.dart';
+export 'menu_item_model.dart';
+export 'menu_list.dart';
diff --git a/ecommers/lib/ui/widgets/menu/menu_item.dart b/ecommers/lib/ui/widgets/menu/menu_item.dart
new file mode 100644
index 0000000..405144d
--- /dev/null
+++ b/ecommers/lib/ui/widgets/menu/menu_item.dart
@@ -0,0 +1,49 @@
+import 'package:ecommers/ui/decorations/index.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter_svg/svg.dart';
+
+class MenuItem extends StatelessWidget {
+ final String svgAssetIconPath;
+ final String title;
+ final String subTitle;
+ final double height;
+
+ const MenuItem({
+ this.svgAssetIconPath,
+ this.title,
+ this.subTitle,
+ this.height,
+ });
+
+ @override
+ Widget build(BuildContext context) {
+ return SizedBox(
+ height: height,
+ child: Row(
+ mainAxisAlignment: MainAxisAlignment.center,
+ children: [
+ if (svgAssetIconPath != null)
+ SizedBox(
+ width: 57.0,
+ child: SvgPicture.asset(svgAssetIconPath),
+ )
+ else
+ const SizedBox(width: 15.0),
+ Expanded(
+ child: Text(
+ title ?? '',
+ style: Theme.of(context).textTheme.subtitle1,
+ ),
+ ),
+ Text(
+ subTitle ?? '',
+ style: Theme.of(context).textTheme.subtitle2,
+ ),
+ const SizedBox(width: 10.0),
+ SvgPicture.asset(Assets.menuArrowIcon),
+ const SizedBox(width: 15.0),
+ ],
+ ),
+ );
+ }
+}
diff --git a/ecommers/lib/ui/widgets/menu/menu_item_model.dart b/ecommers/lib/ui/widgets/menu/menu_item_model.dart
new file mode 100644
index 0000000..4e4bb10
--- /dev/null
+++ b/ecommers/lib/ui/widgets/menu/menu_item_model.dart
@@ -0,0 +1,11 @@
+class MenuItemModel {
+ final String svgAssetIconPath;
+ final String title;
+ final String subTitle;
+
+ const MenuItemModel({
+ this.svgAssetIconPath,
+ this.title,
+ this.subTitle
+ });
+}
\ No newline at end of file
diff --git a/ecommers/lib/ui/widgets/menu/menu_list.dart b/ecommers/lib/ui/widgets/menu/menu_list.dart
new file mode 100644
index 0000000..3b63141
--- /dev/null
+++ b/ecommers/lib/ui/widgets/menu/menu_list.dart
@@ -0,0 +1,75 @@
+import 'package:ecommers/ui/decorations/dimens/index.dart';
+import 'package:ecommers/ui/decorations/index.dart';
+import 'package:ecommers/ui/widgets/menu/index.dart';
+import 'package:flutter/material.dart';
+
+class MenuList extends StatelessWidget {
+ final String title;
+ final List itemList;
+ final EdgeInsets margin;
+ final double itemHeight;
+
+ const MenuList({
+ this.title,
+ this.itemList,
+ this.margin = const EdgeInsets.all(0.0),
+ this.itemHeight = 48.0,
+ });
+
+ @override
+ Widget build(BuildContext context) {
+ return Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ if (title != null && title.isNotEmpty)
+ Padding(
+ padding: const EdgeInsets.only(bottom: Insets.x2_5),
+ child: Text(
+ title,
+ style: Theme.of(context).textTheme.headline5,
+ ),
+ ),
+ Container(
+ margin: margin,
+ decoration: BoxDecoration(
+ color: BrandingColors.background,
+ borderRadius: BorderRadius.circular(Radiuses.normal),
+ boxShadow: const [
+ BoxShadow(
+ blurRadius: Radiuses.big_1x,
+ offset: Dimens.defaultBlurOffset,
+ color: BrandingColors.blur,
+ )
+ ],
+ ),
+ child: ListView.separated(
+ shrinkWrap: true,
+ physics: const NeverScrollableScrollPhysics(),
+ itemCount: itemList.length,
+ padding: const EdgeInsets.all(0.0),
+ itemBuilder: (context, index) {
+ final itemModel = itemList[index];
+
+ return MenuItem(
+ title: itemModel.title,
+ subTitle: itemModel.subTitle,
+ svgAssetIconPath: itemModel.svgAssetIconPath,
+ height: itemHeight,
+ );
+ },
+ separatorBuilder: (context, index) {
+ final menuItem = itemList[index];
+
+ return Divider(
+ color: BrandingColors.secondary.withOpacity(0.1),
+ height: 1.0,
+ indent: menuItem.svgAssetIconPath == null ? 15.0 : 57.0,
+ endIndent: 17.0,
+ thickness: 1,
+ );
+ }),
+ ),
+ ],
+ );
+ }
+}
diff --git a/ecommers/lib/ui/widgets/order/circle_image.dart b/ecommers/lib/ui/widgets/order/circle_image.dart
new file mode 100644
index 0000000..554db95
--- /dev/null
+++ b/ecommers/lib/ui/widgets/order/circle_image.dart
@@ -0,0 +1,29 @@
+import 'package:ecommers/ui/decorations/dimens/index.dart';
+import 'package:ecommers/ui/decorations/index.dart';
+import 'package:flutter/material.dart';
+
+class CircleImage extends StatelessWidget {
+ final Widget image;
+ final Size size;
+
+ const CircleImage({
+ @required this.image,
+ @required this.size,
+ });
+ @override
+ Widget build(BuildContext context) {
+ return Container(
+ alignment: Alignment.center,
+ height: size.height,
+ width: size.width,
+ decoration: const BoxDecoration(
+ shape: BoxShape.circle,
+ color: BrandingColors.background,
+ ),
+ child: Padding(
+ padding: const EdgeInsets.all(Insets.x2),
+ child: image,
+ ),
+ );
+ }
+}
diff --git a/ecommers/lib/ui/widgets/order/counter.dart b/ecommers/lib/ui/widgets/order/counter.dart
new file mode 100644
index 0000000..18cbb25
--- /dev/null
+++ b/ecommers/lib/ui/widgets/order/counter.dart
@@ -0,0 +1,70 @@
+import 'package:ecommers/ui/decorations/assets.dart';
+import 'package:ecommers/ui/decorations/dimens/index.dart';
+import 'package:ecommers/ui/widgets/circle_icon.dart';
+import 'package:flutter/material.dart';
+
+class Counter extends StatefulWidget {
+ final int count;
+ final Function() countIncrementFunction;
+ final Function() countDecrementFunction;
+
+ const Counter({
+ @required this.count,
+ @required this.countIncrementFunction,
+ @required this.countDecrementFunction,
+ });
+
+ @override
+ _CounterState createState() => _CounterState();
+}
+
+class _CounterState extends State {
+ static const _countRowWidth = 71.0;
+ @override
+ Widget build(BuildContext context) {
+ return SizedBox(
+ width: _countRowWidth,
+ child: Row(
+ children: [
+ _buildCountActionButton(
+ Assets.substractIcon,
+ widget.countDecrementFunction,
+ ),
+ Expanded(
+ child: Align(
+ alignment: Alignment.center,
+ child: Text(
+ widget.count.toString(),
+ style: Theme.of(context)
+ .textTheme
+ .caption
+ .copyWith(fontSize: FontSizes.normal),
+ ),
+ ),
+ ),
+ _buildCountActionButton(
+ Assets.addIcon,
+ widget.countIncrementFunction,
+ ),
+ ],
+ ),
+ );
+ }
+
+ Widget _buildCountActionButton(
+ String imagePath,
+ Function() onTappedFunction,
+ ) {
+ return Container(
+ width: CircleIcon.size.width + Insets.x1,
+ height: CircleIcon.size.height + Insets.x1,
+ alignment: Alignment.center,
+ child: RawMaterialButton(
+ shape: const CircleBorder(),
+ onPressed: onTappedFunction,
+ elevation: 1.0,
+ child: CircleIcon(imagePath: imagePath),
+ ),
+ );
+ }
+}
diff --git a/ecommers/lib/ui/widgets/order/index.dart b/ecommers/lib/ui/widgets/order/index.dart
new file mode 100644
index 0000000..946594a
--- /dev/null
+++ b/ecommers/lib/ui/widgets/order/index.dart
@@ -0,0 +1,5 @@
+export 'circle_image.dart';
+export 'counter.dart';
+export 'order_widget.dart';
+export 'small_order_widget.dart';
+export 'total_order_widget.dart';
diff --git a/ecommers/lib/ui/widgets/order/order_widget.dart b/ecommers/lib/ui/widgets/order/order_widget.dart
new file mode 100644
index 0000000..ebc746c
--- /dev/null
+++ b/ecommers/lib/ui/widgets/order/order_widget.dart
@@ -0,0 +1,86 @@
+import 'package:ecommers/ui/decorations/index.dart';
+import 'package:ecommers/ui/utils/formatter.dart';
+import 'package:ecommers/ui/widgets/order/counter.dart';
+import 'package:ecommers/ui/widgets/order/index.dart';
+import 'package:flutter/material.dart';
+
+class OrderWidget extends StatefulWidget {
+ final String assetImagePath;
+ final String primaryText;
+ final String secondaryText;
+ final double cost;
+ final int count;
+ final Function() countIncrementFunction;
+ final Function() countDecrementFunction;
+
+ static const orderCircleImageSize = Size(80.0, 80.0);
+
+ const OrderWidget({
+ @required this.assetImagePath,
+ @required this.primaryText,
+ @required this.secondaryText,
+ @required this.cost,
+ @required this.count,
+ @required this.countIncrementFunction,
+ @required this.countDecrementFunction,
+ });
+
+ @override
+ _OrderWidgetState createState() => _OrderWidgetState();
+}
+
+class _OrderWidgetState extends State {
+ @override
+ Widget build(BuildContext context) {
+ return Row(
+ mainAxisSize: MainAxisSize.min,
+ children: [
+ CircleImage(
+ size: OrderWidget.orderCircleImageSize,
+ image: Image.asset(
+ widget.assetImagePath,
+ fit: BoxFit.scaleDown,
+ ),
+ ),
+ const SizedBox(
+ width: 20.0,
+ ),
+ Flexible(
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Text(
+ widget.primaryText,
+ overflow: TextOverflow.ellipsis,
+ style: Theme.of(context).textTheme.bodyText1,
+ ),
+ Text(
+ widget.secondaryText,
+ overflow: TextOverflow.ellipsis,
+ style: Theme.of(context).textTheme.bodyText2,
+ ),
+ const SizedBox(
+ height: 8.0,
+ ),
+ Text(
+ Formatter.getCost(widget.count * widget.cost),
+ style: Theme.of(context)
+ .textTheme
+ .bodyText1
+ .copyWith(color: BrandingColors.primary),
+ ),
+ const SizedBox(
+ height: 8.0,
+ ),
+ Counter(
+ count: widget.count,
+ countIncrementFunction: widget.countIncrementFunction,
+ countDecrementFunction: widget.countDecrementFunction,
+ )
+ ],
+ ),
+ ),
+ ],
+ );
+ }
+}
diff --git a/ecommers/lib/ui/widgets/order/small_order_widget.dart b/ecommers/lib/ui/widgets/order/small_order_widget.dart
new file mode 100644
index 0000000..9a6851e
--- /dev/null
+++ b/ecommers/lib/ui/widgets/order/small_order_widget.dart
@@ -0,0 +1,86 @@
+import 'package:ecommers/ui/decorations/dimens/index.dart';
+import 'package:ecommers/ui/decorations/index.dart';
+import 'package:ecommers/ui/utils/formatter.dart';
+import 'package:ecommers/ui/widgets/order/counter.dart';
+import 'package:ecommers/ui/widgets/order/index.dart';
+import 'package:flutter/material.dart';
+
+class SmallOrderWidget extends StatefulWidget {
+ final String assetImagePath;
+ final String primaryText;
+ final String secondaryText;
+ final double cost;
+ final int count;
+ final Function() countIncrementFunction;
+ final Function() countDecrementFunction;
+
+ static const orderCircleImageSize = Size(69.0, 69.0);
+
+ const SmallOrderWidget({
+ @required this.assetImagePath,
+ @required this.primaryText,
+ @required this.secondaryText,
+ @required this.cost,
+ @required this.count,
+ @required this.countIncrementFunction,
+ @required this.countDecrementFunction,
+ });
+
+ @override
+ _SmallOrderWidgetState createState() => _SmallOrderWidgetState();
+}
+
+class _SmallOrderWidgetState extends State {
+ @override
+ Widget build(BuildContext context) {
+ return Row(
+ mainAxisSize: MainAxisSize.min,
+ children: [
+ CircleImage(
+ size: SmallOrderWidget.orderCircleImageSize,
+ image: Image.asset(
+ widget.assetImagePath,
+ fit: BoxFit.scaleDown,
+ ),
+ ),
+ const SizedBox(width: Insets.x3_5),
+ Expanded(
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Text(
+ widget.primaryText,
+ style: Theme.of(context)
+ .textTheme
+ .bodyText1
+ .copyWith(fontWeight: FontWeight.w700),
+ ),
+ Text(
+ widget.secondaryText,
+ style: Theme.of(context).textTheme.subtitle1,
+ ),
+ const SizedBox(height: Insets.x1),
+ Row(
+ children: [
+ Expanded(
+ child: Text(
+ Formatter.getCost(widget.count * widget.cost),
+ style: Theme.of(context)
+ .textTheme
+ .bodyText1
+ .copyWith(color: BrandingColors.primary),
+ )),
+ Counter(
+ count: widget.count,
+ countIncrementFunction: widget.countIncrementFunction,
+ countDecrementFunction: widget.countDecrementFunction,
+ ),
+ ],
+ ),
+ ],
+ ),
+ ),
+ ],
+ );
+ }
+}
diff --git a/ecommers/lib/ui/widgets/order/total_order_widget.dart b/ecommers/lib/ui/widgets/order/total_order_widget.dart
new file mode 100644
index 0000000..0e93f67
--- /dev/null
+++ b/ecommers/lib/ui/widgets/order/total_order_widget.dart
@@ -0,0 +1,77 @@
+import 'package:ecommers/generated/i18n.dart';
+import 'package:ecommers/ui/decorations/dimens/index.dart';
+import 'package:ecommers/ui/utils/formatter.dart';
+import 'package:ecommers/ui/widgets/button/index.dart';
+import 'package:flutter/material.dart';
+
+class TotalOrderWidget extends StatelessWidget {
+ final String buttonText;
+ final double cost;
+ final Function() onButtonPressedFunction;
+ final Color backgroundColor;
+ final EdgeInsets padding;
+
+ static const _buttonSize = Size(165.0, 46.0);
+
+ const TotalOrderWidget({
+ @required this.cost,
+ @required this.buttonText,
+ @required this.onButtonPressedFunction,
+ this.backgroundColor,
+ this.padding,
+ });
+
+ @override
+ Widget build(BuildContext context) {
+ return Container(
+ color: backgroundColor,
+ padding: padding,
+ child: Row(
+ mainAxisSize: MainAxisSize.min,
+ children: [
+ Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Text(
+ I18n.of(context).totalOrder,
+ style: Theme.of(context)
+ .textTheme
+ .headline5
+ .copyWith(fontSize: FontSizes.small_1x),
+ ),
+ const SizedBox(height: 7.0),
+ Text(
+ Formatter.getCost(cost),
+ style: Theme.of(context)
+ .textTheme
+ .headline6
+ .copyWith(fontSize: FontSizes.big_2x),
+ ),
+ const SizedBox(height: 4.0),
+ Text(
+ I18n.of(context).freeDomesticShipping,
+ style: Theme.of(context)
+ .textTheme
+ .headline5
+ .copyWith(fontWeight: FontWeight.w400),
+ ),
+ ],
+ ),
+ Expanded(
+ child: Align(
+ alignment: Alignment.centerRight,
+ child: SizedBox(
+ height: _buttonSize.height,
+ width: _buttonSize.width,
+ child: PrimaryButtonWidget(
+ text: buttonText,
+ onPressedFunction: onButtonPressedFunction,
+ ),
+ ),
+ ),
+ ),
+ ],
+ ),
+ );
+ }
+}
diff --git a/ecommers/lib/ui/widgets/product_item/index.dart b/ecommers/lib/ui/widgets/product_item/index.dart
new file mode 100644
index 0000000..830bd9d
--- /dev/null
+++ b/ecommers/lib/ui/widgets/product_item/index.dart
@@ -0,0 +1,4 @@
+export 'product_item_base.dart';
+export 'product_item_normal.dart';
+export 'product_item_small.dart';
+export 'product_item_wide.dart';
diff --git a/ecommers/lib/ui/widgets/product_item/product_item_base.dart b/ecommers/lib/ui/widgets/product_item/product_item_base.dart
new file mode 100644
index 0000000..33af407
--- /dev/null
+++ b/ecommers/lib/ui/widgets/product_item/product_item_base.dart
@@ -0,0 +1,43 @@
+import 'package:ecommers/ui/decorations/dimens/index.dart';
+import 'package:ecommers/ui/decorations/index.dart';
+import 'package:flutter/material.dart';
+
+abstract class ProductItemBase extends StatelessWidget {
+ @protected
+ static const double padding = 10.0;
+
+ final String assetImagePath;
+ final String title;
+ final double cost;
+ final Size productSize;
+
+ const ProductItemBase({
+ @required this.assetImagePath,
+ @required this.title,
+ @required this.cost,
+ @required this.productSize,
+ });
+
+ @override
+ Widget build(BuildContext context) {
+ return Container(
+ padding: const EdgeInsets.all(10.0),
+ height: productSize.height,
+ width: productSize.width,
+ decoration: BoxDecoration(
+ color: BrandingColors.background,
+ borderRadius: BorderRadius.circular(Radiuses.normal),
+ boxShadow: const [
+ BoxShadow(
+ blurRadius: Radiuses.big_1x,
+ color: BrandingColors.blur,
+ offset: Dimens.defaultBlurOffset,
+ ),
+ ],
+ ),
+ child: buildProductItem(context),
+ );
+ }
+
+ Widget buildProductItem(BuildContext context);
+}
diff --git a/ecommers/lib/ui/widgets/product_item/product_item_normal.dart b/ecommers/lib/ui/widgets/product_item/product_item_normal.dart
new file mode 100644
index 0000000..ca3a2ce
--- /dev/null
+++ b/ecommers/lib/ui/widgets/product_item/product_item_normal.dart
@@ -0,0 +1,54 @@
+import 'package:ecommers/ui/decorations/dimens/index.dart';
+import 'package:ecommers/ui/utils/formatter.dart';
+import 'package:ecommers/ui/widgets/product_item/product_item_base.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter/widgets.dart';
+
+class ProductItemNormal extends ProductItemBase {
+ final double rate;
+
+ static const size = Size(101.0, 135.0);
+
+ const ProductItemNormal({
+ @required String assetImagePath,
+ @required String title,
+ @required double cost,
+ this.rate,
+ }) : super(
+ assetImagePath: assetImagePath,
+ cost: cost,
+ title: title,
+ productSize: size,
+ );
+
+ @override
+ Widget buildProductItem(BuildContext context) {
+ return Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Expanded(
+ child: Center(
+ child: Image.asset(assetImagePath),
+ ),
+ ),
+ const SizedBox(height: 4.0),
+ Text(
+ title,
+ overflow: TextOverflow.ellipsis,
+ maxLines: Dimens.defaultTextMaxLines,
+ style: Theme.of(context)
+ .textTheme
+ .bodyText2
+ .copyWith(fontSize: FontSizes.small_3x),
+ ),
+ Text(
+ Formatter.getCost(cost),
+ style: Theme.of(context).textTheme.bodyText2.copyWith(
+ fontSize: FontSizes.small_1x,
+ fontWeight: FontWeight.w700,
+ ),
+ ),
+ ],
+ );
+ }
+}
diff --git a/ecommers/lib/ui/widgets/product_item/product_item_small.dart b/ecommers/lib/ui/widgets/product_item/product_item_small.dart
new file mode 100644
index 0000000..159b379
--- /dev/null
+++ b/ecommers/lib/ui/widgets/product_item/product_item_small.dart
@@ -0,0 +1,60 @@
+import 'package:ecommers/ui/decorations/dimens/index.dart';
+import 'package:ecommers/ui/utils/formatter.dart';
+import 'package:ecommers/ui/widgets/product_item/product_item_base.dart';
+import 'package:flutter/material.dart';
+
+class ProductItemSmall extends ProductItemBase {
+ final double rate;
+
+ static const productItemSmallSize = Size(185.0, 59.0);
+
+ const ProductItemSmall({
+ @required String assetImagePath,
+ @required String title,
+ @required double cost,
+ this.rate,
+ }) : super(
+ assetImagePath: assetImagePath,
+ cost: cost,
+ title: title,
+ productSize: productItemSmallSize,
+ );
+
+ @override
+ Widget buildProductItem(BuildContext context) {
+ return Row(
+ mainAxisAlignment: MainAxisAlignment.start,
+ children: [
+ Expanded(
+ flex: 3,
+ child: Image.asset(assetImagePath),
+ ),
+ const SizedBox(
+ width: ProductItemBase.padding,
+ ),
+ Expanded(
+ flex: 7,
+ child: Column(
+ mainAxisAlignment: MainAxisAlignment.end,
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Text(
+ title,
+ overflow: TextOverflow.ellipsis,
+ maxLines: Dimens.defaultTextMaxLines,
+ style: Theme.of(context)
+ .textTheme
+ .bodyText1
+ .copyWith(fontWeight: FontWeight.w400),
+ ),
+ Text(
+ Formatter.getCost(cost),
+ style: Theme.of(context).textTheme.bodyText1,
+ ),
+ ],
+ ),
+ )
+ ],
+ );
+ }
+}
diff --git a/ecommers/lib/ui/widgets/product_item/product_item_wide.dart b/ecommers/lib/ui/widgets/product_item/product_item_wide.dart
new file mode 100644
index 0000000..28c2a09
--- /dev/null
+++ b/ecommers/lib/ui/widgets/product_item/product_item_wide.dart
@@ -0,0 +1,58 @@
+import 'package:ecommers/ui/decorations/dimens/index.dart';
+import 'package:ecommers/ui/utils/formatter.dart';
+import 'package:ecommers/ui/widgets/index.dart';
+import 'package:ecommers/ui/widgets/product_item/product_item_base.dart';
+import 'package:flutter/material.dart';
+
+class ProductItemWide extends ProductItemBase {
+ final double rate;
+
+ static const productItemWideSize = Size(160.0, 218.0);
+
+ const ProductItemWide({
+ @required String assetImagePath,
+ @required String title,
+ @required double cost,
+ this.rate,
+ }) : super(
+ assetImagePath: assetImagePath,
+ cost: cost,
+ title: title,
+ productSize: productItemWideSize,
+ );
+
+ @override
+ Widget buildProductItem(BuildContext context) {
+ return Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Expanded(
+ child: Center(
+ child: Image.asset(assetImagePath),
+ ),
+ ),
+ Text(
+ title,
+ maxLines: Dimens.defaultTextMaxLines,
+ style: Theme.of(context).textTheme.bodyText2,
+ ),
+ Row(
+ children: [
+ Expanded(
+ child: Text(
+ Formatter.getCost(cost),
+ style: Theme.of(context).textTheme.bodyText2.copyWith(
+ fontSize: FontSizes.small_3x,
+ fontWeight: FontWeight.w700,
+ ),
+ ),
+ ),
+ RateWidget(
+ rate: rate,
+ ),
+ ],
+ )
+ ],
+ );
+ }
+}
diff --git a/ecommers/lib/ui/widgets/progress.dart b/ecommers/lib/ui/widgets/progress.dart
new file mode 100644
index 0000000..e1aaf05
--- /dev/null
+++ b/ecommers/lib/ui/widgets/progress.dart
@@ -0,0 +1,26 @@
+import 'dart:ui';
+
+import 'package:ecommers/ui/decorations/index.dart';
+import 'package:flare_flutter/flare_actor.dart';
+import 'package:flutter/material.dart';
+
+class Progress extends StatelessWidget {
+ static const size = Size(70, 70);
+ static const String _animationState = '0to100';
+
+ const Progress({Key key}) : super(key: key);
+
+ @override
+ Widget build(BuildContext context) {
+ return Center(
+ child: SizedBox.fromSize(
+ size: Progress.size,
+ child: const FlareActor(
+ Assets.progressAnimation,
+ alignment: Alignment.center,
+ animation: _animationState,
+ ),
+ ),
+ );
+ }
+}
diff --git a/ecommers/lib/ui/widgets/rate_widget.dart b/ecommers/lib/ui/widgets/rate_widget.dart
new file mode 100644
index 0000000..4bcfd4f
--- /dev/null
+++ b/ecommers/lib/ui/widgets/rate_widget.dart
@@ -0,0 +1,43 @@
+import 'package:ecommers/ui/decorations/dimens/index.dart';
+import 'package:ecommers/ui/decorations/index.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter_svg/svg.dart';
+
+class RateWidget extends StatelessWidget {
+ final Size size;
+ final double rate;
+
+ static const rateContainerSize = Size(33.0, 16.0);
+
+ const RateWidget({
+ @required this.rate,
+ this.size,
+ });
+
+ @override
+ Widget build(BuildContext context) {
+ final containerSize = size ?? rateContainerSize;
+
+ return Container(
+ height: containerSize.height,
+ width: containerSize.width,
+ alignment: Alignment.center,
+ decoration: BoxDecoration(
+ color: BrandingColors.primary,
+ borderRadius: BorderRadius.circular(Radiuses.big_1x),
+ ),
+ child: Center(
+ child: Row(
+ mainAxisAlignment: MainAxisAlignment.center,
+ children: [
+ SvgPicture.asset(Assets.rateStarIcon),
+ Text(
+ rate.toString(),
+ style: Theme.of(context).textTheme.overline,
+ )
+ ],
+ ),
+ ),
+ );
+ }
+}
diff --git a/ecommers/lib/web_server/data_access/user_data_access.dart b/ecommers/lib/web_server/data_access/user_data_access.dart
new file mode 100644
index 0000000..90be26c
--- /dev/null
+++ b/ecommers/lib/web_server/data_access/user_data_access.dart
@@ -0,0 +1,74 @@
+import 'package:ecommers/web_server/data_access/validation_model.dart';
+import 'package:ecommers/web_server/local_database.dart';
+import 'package:ecommers/web_server/models/user.dart';
+import 'package:ecommers/extensions/string_extension.dart';
+
+class UserDataAccess {
+ static const String userStoreKey = 'Users';
+
+ static final UserDataAccess _instance = UserDataAccess._();
+ static UserDataAccess get instance => _instance;
+
+ final LocalDatabase _database = LocalDatabase.instance;
+
+ UserDataAccess._();
+
+ List _allUsers;
+ Future
> get allUsers => _getAllUsers();
+
+ Future> _getAllUsers() async {
+ return _allUsers ??=
+ await _database.getAll(userStoreKey, User.fromJsonFactory);
+ }
+
+ Future saveUser(Map userMap) async {
+ _allUsers ??= [];
+ _allUsers.add(User.fromJsonFactory(userMap));
+
+ await _database.saveMap(userStoreKey, userMap);
+ }
+
+ Future isUserExists(User currentUser) async {
+ final existedUser = (await allUsers).firstWhere(
+ (user) =>
+ (user.email == currentUser.email ||
+ user.username == currentUser.username) &&
+ user.password == currentUser.password,
+ orElse: () => null);
+
+ return existedUser != null;
+ }
+
+ Future isNewUserValid(User newUser) async {
+ const fieldsEmptyError = 'Please fill in all fields';
+ const emailExistsError = 'This email already exists';
+ const usernameExists = 'This username already exists';
+
+ final users = await allUsers;
+
+ if (newUser.email.isNullOrEmpty ||
+ newUser.username.isNullOrEmpty ||
+ newUser.password.isNullOrEmpty) {
+ return ValidationModel(isValid: false, error: fieldsEmptyError);
+ }
+
+ if (users == null) {
+ return ValidationModel(isValid: true);
+ }
+
+ final existedUser = (await allUsers).firstWhere(
+ (user) =>
+ user.email == newUser.email || user.username == newUser.username,
+ orElse: () => null);
+
+ if (existedUser == null) {
+ return ValidationModel(isValid: true);
+ }
+
+ if (existedUser.email == newUser.email) {
+ return ValidationModel(isValid: false, error: emailExistsError);
+ }
+
+ return ValidationModel(isValid: false, error: usernameExists);
+ }
+}
diff --git a/ecommers/lib/web_server/data_access/validation_model.dart b/ecommers/lib/web_server/data_access/validation_model.dart
new file mode 100644
index 0000000..58638f9
--- /dev/null
+++ b/ecommers/lib/web_server/data_access/validation_model.dart
@@ -0,0 +1,6 @@
+class ValidationModel {
+ final String error;
+ final bool isValid;
+
+ ValidationModel({this.error = '',this.isValid});
+}
\ No newline at end of file
diff --git a/ecommers/lib/web_server/local_database.dart b/ecommers/lib/web_server/local_database.dart
new file mode 100644
index 0000000..07db841
--- /dev/null
+++ b/ecommers/lib/web_server/local_database.dart
@@ -0,0 +1,59 @@
+import 'dart:async';
+
+import 'package:path_provider/path_provider.dart';
+import 'package:sembast/sembast.dart';
+import 'package:sembast/sembast_io.dart';
+import 'package:path/path.dart';
+
+class LocalDatabase {
+ static final LocalDatabase _instance = LocalDatabase._();
+ static LocalDatabase get instance => _instance;
+
+ Completer _dbCompleter;
+
+ Future get _db async {
+ if (_dbCompleter == null) {
+ _dbCompleter = Completer();
+
+ await initializeDatabase();
+ }
+
+ return _dbCompleter.future;
+ }
+
+ LocalDatabase._();
+
+ Future initializeDatabase() async {
+ final DatabaseFactory dbFactory = databaseFactoryIo;
+
+ final database = await dbFactory.openDatabase(await _getDbPath());
+
+ _dbCompleter.complete(database);
+ }
+
+ Future _getDbPath() async {
+ const _dbName = 'local_server.db';
+
+ final dbDirectory = await getApplicationDocumentsDirectory();
+ if (!dbDirectory.existsSync()) {
+ await dbDirectory.create(recursive: true);
+ }
+
+ return join(dbDirectory.path, _dbName);
+ }
+
+ Future saveMap(String key, Map map) async {
+ final store = intMapStoreFactory.store(key);
+ await store.add(await _db, map);
+ }
+
+ Future> getAll(
+ String key, T Function(Map) fromMap) async {
+ final store = intMapStoreFactory.store(key);
+ final records = await store.find(await _db);
+
+ return records.map((snapshot) {
+ return fromMap(snapshot.value);
+ }).toList();
+ }
+}
diff --git a/ecommers/lib/web_server/local_server.dart b/ecommers/lib/web_server/local_server.dart
new file mode 100644
index 0000000..6d9368d
--- /dev/null
+++ b/ecommers/lib/web_server/local_server.dart
@@ -0,0 +1,34 @@
+import 'dart:async';
+import 'dart:io';
+
+import 'package:ecommers/core/services/index.dart';
+import 'package:http_server/http_server.dart';
+
+class LocalServer {
+ static const int _port = 8090;
+ static final String _localHost = InternetAddress.loopbackIPv4.address;
+ static HttpServer _server;
+
+ static Uri uri = Uri(scheme: 'http', host: _localHost, port: _port);
+
+ static Future setup() async {
+ _server = await HttpServer.bind(
+ _localHost,
+ _port,
+ shared: true,
+ );
+
+ _server.transform(HttpBodyHandler()).listen((HttpRequestBody body) async {
+ await Future.delayed(const Duration(seconds: 2));
+ requestHandler.process(body);
+ });
+ }
+
+ static void closeConnection() {
+ if (_server == null) {
+ return;
+ }
+
+ _server.close();
+ }
+}
diff --git a/ecommers/lib/web_server/models/user.dart b/ecommers/lib/web_server/models/user.dart
new file mode 100644
index 0000000..8069488
--- /dev/null
+++ b/ecommers/lib/web_server/models/user.dart
@@ -0,0 +1,21 @@
+import 'package:json_annotation/json_annotation.dart';
+
+part 'user.g.dart';
+
+@JsonSerializable()
+class User {
+ @JsonKey(name: 'username')
+ final String username;
+
+ @JsonKey(name: 'email')
+ final String email;
+
+ @JsonKey(name: 'password')
+ final String password;
+
+ User(this.username, this.email, this.password);
+
+ static const fromJsonFactory = _$UserFromJson;
+
+ Map toJson() => _$UserToJson(this);
+}
\ No newline at end of file
diff --git a/ecommers/lib/web_server/request_handler.dart b/ecommers/lib/web_server/request_handler.dart
new file mode 100644
index 0000000..b6ef8be
--- /dev/null
+++ b/ecommers/lib/web_server/request_handler.dart
@@ -0,0 +1,82 @@
+import 'dart:io';
+
+import 'package:ecommers/core/common/index.dart';
+import 'package:ecommers/core/services/index.dart';
+import 'package:ecommers/web_server/data_access/user_data_access.dart';
+import 'package:http_server/http_server.dart';
+
+import 'models/user.dart';
+
+class RequestHandler {
+ static final UserDataAccess _userDataAccess = UserDataAccess.instance;
+
+ void process(HttpRequestBody body) {
+ final uri = body.request.uri.toString();
+
+ switch (uri) {
+ case ApiDefines.login:
+ _handleLoginRequest(body);
+ break;
+ case ApiDefines.auth:
+ _handleAuthorizationRequest(body);
+ break;
+ default:
+ _handleUnsupportedRequest(body);
+ }
+ }
+
+ Future _handleLoginRequest(HttpRequestBody body) async {
+ const String jsonFile = 'login.json';
+
+ final userMap = body.body as Map;
+ final user = User.fromJsonFactory(userMap);
+
+ if (await _userDataAccess.isUserExists(user)) {
+ body.request.response
+ ..headers.contentType = ContentType.json
+ ..write(await fileManager.readJson(jsonFile))
+ ..close();
+
+ return;
+ }
+
+ body.request.response
+ ..statusCode = HttpStatus.unauthorized
+ ..close();
+ }
+
+ Future _handleAuthorizationRequest(HttpRequestBody body) async {
+ const String jsonFile = 'login.json';
+
+ final userMap = body.body as Map;
+ final user = User.fromJsonFactory(userMap);
+
+ final validationModel = await _userDataAccess.isNewUserValid(user);
+
+ if (validationModel.isValid) {
+ await _userDataAccess.saveUser(userMap);
+
+ body.request.response
+ ..headers.contentType = ContentType.json
+ ..write(await fileManager.readJson(jsonFile))
+ ..close();
+
+ return;
+ }
+
+ body.request.response
+ ..statusCode = HttpStatus.unauthorized
+ ..write(validationModel.error)
+ ..close();
+ }
+
+ Future _handleUnsupportedRequest(HttpRequestBody body) async {
+ const String unsupported = 'Unsupported API';
+
+ body.request.response
+ ..headers.contentType = ContentType.text
+ ..statusCode = HttpStatus.notImplemented
+ ..write(unsupported)
+ ..close();
+ }
+}
diff --git a/ecommers/pubspec.yaml b/ecommers/pubspec.yaml
index 1a9b430..318c985 100644
--- a/ecommers/pubspec.yaml
+++ b/ecommers/pubspec.yaml
@@ -14,27 +14,55 @@ description: A new Flutter project.
version: 1.0.0+1
environment:
- sdk: ">=2.1.0 <3.0.0"
+ sdk: ">=2.6.0 <3.0.0"
dependencies:
flutter:
sdk: flutter
+ flutter_svg: ^0.17.2
+ intl: ^0.16.1
+ carousel_slider: ^1.4.1
+ provider: ^4.0.4
+ badges: ^1.1.1
+ lint: ^1.1.1
+ get_it: ^4.0.0
+ keyboard_visibility: ^0.5.6
+ flutter_launcher_icons: ^0.7.4
+ http_server: ^0.9.8+3
+ chopper: ^3.0.2
+ json_annotation: ^3.0.1
+ flutter_secure_storage: ^3.3.1+1
+ flare_flutter: ^2.0.1
+ sembast: ^2.3.0
+ path_provider: ^1.6.5
+
+flutter_icons:
+ image_path_android: "assets/launch_icon_android.png" #http://www.softicons.com/web-icons/services-flat-icons-by-jozef-krajcovic/e-commerce-icon
+ image_path_ios: "assets/launch_icon_ios.png"
+ android: true
+ ios: true
# The following adds the Cupertino Icons font to your application.
# Use with the CupertinoIcons class for iOS style icons.
- cupertino_icons: ^0.1.2
+ cupertino_icons: ^0.1.3
dev_dependencies:
flutter_test:
sdk: flutter
+ chopper_generator: ^3.0.4
+ json_serializable: ^3.2.5
+ build_runner: ^1.8.0
# For information on the generic Dart part of this file, see the
# following page: https://dart.dev/tools/pub/pubspec
# The following section is specific to Flutter.
flutter:
-
+ assets:
+ - assets/
+ - assets/data/
+
# The following line ensures that the Material Icons font is
# included with your application, so that you can use the icons in
# the material Icons class.
diff --git a/ecommers/test/widget_test.dart b/ecommers/test/widget_test.dart
index 60afd84..4732d30 100644
--- a/ecommers/test/widget_test.dart
+++ b/ecommers/test/widget_test.dart
@@ -11,9 +11,9 @@ import 'package:flutter_test/flutter_test.dart';
import 'package:ecommers/main.dart';
void main() {
- testWidgets('Counter increments smoke test', (WidgetTester tester) async {
+ testWidgets('Counter increments smoke test', (tester) async {
// Build our app and trigger a frame.
- await tester.pumpWidget(MyApp());
+ await tester.pumpWidget(MainApp());
// Verify that our counter starts at 0.
expect(find.text('0'), findsOneWidget);
diff --git a/xd-resources-ecommerce-ui.xd b/xd-resources-ecommerce-ui.xd
new file mode 100644
index 0000000..49a384f
Binary files /dev/null and b/xd-resources-ecommerce-ui.xd differ