├── .codecov.yml ├── .env.example ├── .github └── workflows │ ├── release.yml │ └── tests.yml ├── .gitignore ├── .metadata ├── .vscode └── launch.json ├── CHANGELOG.md ├── LICENSE ├── README.md ├── analysis_options.yaml ├── android ├── .gitignore ├── app │ ├── build.gradle │ └── src │ │ ├── debug │ │ └── AndroidManifest.xml │ │ ├── main │ │ ├── AndroidManifest.xml │ │ ├── kotlin │ │ │ └── com │ │ │ │ └── example │ │ │ │ └── cryptocurrency_app │ │ │ │ └── MainActivity.kt │ │ └── res │ │ │ ├── drawable-hdpi │ │ │ └── splash.png │ │ │ ├── drawable-mdpi │ │ │ └── splash.png │ │ │ ├── drawable-night-v21 │ │ │ ├── background.png │ │ │ └── launch_background.xml │ │ │ ├── drawable-night │ │ │ ├── background.png │ │ │ └── launch_background.xml │ │ │ ├── drawable-v21 │ │ │ ├── background.png │ │ │ └── launch_background.xml │ │ │ ├── drawable-xhdpi │ │ │ └── splash.png │ │ │ ├── drawable-xxhdpi │ │ │ └── splash.png │ │ │ ├── drawable-xxxhdpi │ │ │ └── splash.png │ │ │ ├── drawable │ │ │ ├── background.png │ │ │ └── launch_background.xml │ │ │ ├── mipmap-hdpi │ │ │ ├── ic_launcher.png │ │ │ └── launcher_icon.png │ │ │ ├── mipmap-mdpi │ │ │ ├── ic_launcher.png │ │ │ └── launcher_icon.png │ │ │ ├── mipmap-xhdpi │ │ │ ├── ic_launcher.png │ │ │ └── launcher_icon.png │ │ │ ├── mipmap-xxhdpi │ │ │ ├── ic_launcher.png │ │ │ └── launcher_icon.png │ │ │ ├── mipmap-xxxhdpi │ │ │ ├── ic_launcher.png │ │ │ └── launcher_icon.png │ │ │ ├── values-night │ │ │ └── styles.xml │ │ │ └── values │ │ │ └── styles.xml │ │ └── profile │ │ └── AndroidManifest.xml ├── build.gradle ├── gradle.properties ├── gradle │ └── wrapper │ │ └── gradle-wrapper.properties └── settings.gradle ├── assets ├── icon │ ├── icon.png │ └── icon_ios.png └── translations │ ├── en.json │ └── es.json ├── integration_test ├── data │ └── api_data.dart ├── main_test.dart └── search_test.dart ├── ios ├── .gitignore ├── Flutter │ ├── AppFrameworkInfo.plist │ ├── Debug.xcconfig │ └── Release.xcconfig ├── Podfile ├── Podfile.lock ├── Runner.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ ├── IDEWorkspaceChecks.plist │ │ │ └── WorkspaceSettings.xcsettings │ └── xcshareddata │ │ └── xcschemes │ │ └── Runner.xcscheme ├── Runner.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ ├── IDEWorkspaceChecks.plist │ │ └── WorkspaceSettings.xcsettings └── Runner │ ├── AppDelegate.swift │ ├── Assets.xcassets │ ├── AppIcon.appiconset │ │ ├── Contents.json │ │ ├── Icon-App-1024x1024@1x.png │ │ ├── Icon-App-20x20@1x.png │ │ ├── Icon-App-20x20@2x.png │ │ ├── Icon-App-20x20@3x.png │ │ ├── Icon-App-29x29@1x.png │ │ ├── Icon-App-29x29@2x.png │ │ ├── Icon-App-29x29@3x.png │ │ ├── Icon-App-40x40@1x.png │ │ ├── Icon-App-40x40@2x.png │ │ ├── Icon-App-40x40@3x.png │ │ ├── Icon-App-60x60@2x.png │ │ ├── Icon-App-60x60@3x.png │ │ ├── Icon-App-76x76@1x.png │ │ ├── Icon-App-76x76@2x.png │ │ └── Icon-App-83.5x83.5@2x.png │ ├── LaunchBackground.imageset │ │ ├── Contents.json │ │ ├── background.png │ │ └── darkbackground.png │ └── LaunchImage.imageset │ │ ├── Contents.json │ │ ├── LaunchImage.png │ │ ├── LaunchImage@2x.png │ │ ├── LaunchImage@3x.png │ │ └── README.md │ ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard │ ├── Info.plist │ └── Runner-Bridging-Header.h ├── lib ├── constants │ ├── app_theme.dart │ ├── exceptions.dart │ ├── keys.dart │ └── utils.dart ├── generated │ └── locale_keys.g.dart ├── main.dart ├── models │ ├── allowance │ │ ├── allowance.dart │ │ ├── allowance.freezed.dart │ │ └── allowance.g.dart │ ├── exchanges │ │ ├── exchange │ │ │ ├── exchange.dart │ │ │ ├── exchange.freezed.dart │ │ │ └── exchange.g.dart │ │ └── exchanges_response │ │ │ ├── exchanges_response.dart │ │ │ ├── exchanges_response.freezed.dart │ │ │ └── exchanges_response.g.dart │ ├── graph │ │ ├── graph │ │ │ ├── graph.dart │ │ │ └── graph.freezed.dart │ │ ├── graph_response │ │ │ ├── graph_response.dart │ │ │ └── graph_response.freezed.dart │ │ ├── pair_graph │ │ │ ├── pair_graph.dart │ │ │ └── pair_graph.freezed.dart │ │ └── points │ │ │ ├── points.dart │ │ │ └── points.freezed.dart │ ├── markets │ │ ├── favorite_pair │ │ │ ├── favorite_pair.dart │ │ │ ├── favorite_pair.freezed.dart │ │ │ └── favorite_pair.g.dart │ │ ├── market_response │ │ │ ├── market_response.dart │ │ │ ├── market_response.freezed.dart │ │ │ └── market_response.g.dart │ │ └── pair │ │ │ ├── pair.dart │ │ │ ├── pair.freezed.dart │ │ │ └── pair.g.dart │ ├── orderbook │ │ ├── orderbook │ │ │ ├── orderbook.dart │ │ │ └── orderbook.freezed.dart │ │ ├── orderbook_response │ │ │ ├── orderbook_response.dart │ │ │ └── orderbook_response.freezed.dart │ │ └── price │ │ │ ├── price.dart │ │ │ └── price.freezed.dart │ ├── pair │ │ ├── change │ │ │ ├── change.dart │ │ │ ├── change.freezed.dart │ │ │ └── change.g.dart │ │ ├── pair_response │ │ │ ├── pair_response.dart │ │ │ ├── pair_response.freezed.dart │ │ │ └── pair_response.g.dart │ │ ├── pair_summary │ │ │ ├── pair_summary.dart │ │ │ ├── pair_summary.freezed.dart │ │ │ └── pair_summary.g.dart │ │ └── price │ │ │ ├── price.dart │ │ │ ├── price.freezed.dart │ │ │ └── price.g.dart │ ├── settings │ │ ├── settings_details │ │ │ ├── settings_details.dart │ │ │ └── settings_details.freezed.dart │ │ └── settings_state │ │ │ ├── settings_state.dart │ │ │ └── settings_state.freezed.dart │ └── trades │ │ ├── trade │ │ ├── trade.dart │ │ └── trade.freezed.dart │ │ └── trades_response.dart │ │ ├── trades_response.dart │ │ └── trades_response.freezed.dart ├── provider │ ├── crypto_provider.dart │ ├── navigation_provider.dart │ ├── settings_provider.dart │ └── time_provider.dart ├── repository │ └── crypto_repository.dart └── ui │ ├── home.dart │ ├── screens │ ├── details.dart │ ├── home.dart │ ├── search.dart │ └── settings.dart │ └── widgets │ ├── details │ ├── details_widget.dart │ ├── ohlc_section.dart │ ├── order_book_section.dart │ ├── summary_section.dart │ ├── time_bar_selector.dart │ └── trades_section.dart │ ├── favorite_pair.dart │ ├── line_chart.dart │ ├── pair_tile.dart │ └── title_price.dart ├── pubspec.yaml ├── run_all_tests.sh ├── screenshots ├── 1_dark.jpeg ├── 1_light.jpeg ├── 2_dark.jpeg ├── 2_light.jpeg ├── 3_dark.jpeg ├── 3_light.jpeg └── cover.png ├── test ├── api_test.dart ├── data │ └── api_data.dart ├── exception_test.dart └── widget_test.dart ├── test_driver └── integration_test.dart └── web ├── favicon.png ├── icons ├── Icon-192.png ├── Icon-512.png ├── Icon-maskable-192.png └── Icon-maskable-512.png ├── index.html └── manifest.json /.codecov.yml: -------------------------------------------------------------------------------- 1 | ignore: 2 | - '**/**/**/*.g.dart' 3 | - '**/**/**/*.freezed.dart' 4 | - '**/**/**/**/*.g.dart' 5 | - '**/**/**/**/*.freezed.dart' 6 | -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | API_KEY= 2 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: release 2 | 3 | on: 4 | push: 5 | tags: 6 | - 'v*' 7 | 8 | env: 9 | flutter_version: "2.10.2" 10 | 11 | jobs: 12 | build_deploy: 13 | name: Build apk and release 14 | runs-on: ubuntu-latest 15 | steps: 16 | - uses: actions/checkout@v2 17 | - name: Cache Flutter dependencies 18 | uses: actions/cache@v2 19 | with: 20 | path: /opt/hostedtoolcache/flutter 21 | key: ${{ runner.OS }}-flutter-install-cache-${{ env.flutter_version }} 22 | - uses: subosito/flutter-action@v2 23 | with: 24 | flutter-version: ${{ env.flutter_version }} 25 | channel: 'stable' 26 | - run: flutter pub get 27 | # build Android version 28 | - name: Create env file 29 | run: | 30 | touch .env 31 | echo API_KEY=${{ secrets.API_KEY }} >> .env 32 | cat .env 33 | - run: flutter build apk --split-per-abi 34 | # This action will create a github release and optionally upload an artifact to it. 35 | # https://github.com/ncipollo/release-action 36 | - name: Extract release notes 37 | id: extract-release-notes 38 | uses: ffurrer2/extract-release-notes@v1 39 | - name: Create a Release APK 40 | uses: ncipollo/release-action@v1 41 | with: 42 | artifacts: "build/app/outputs/apk/release/*.apk" 43 | token: ${{ secrets.GITHUB_TOKEN }} 44 | body: ${{ steps.extract-release-notes.outputs.release_notes }} 45 | -------------------------------------------------------------------------------- /.github/workflows/tests.yml: -------------------------------------------------------------------------------- 1 | #The name of your workflow. 2 | name: test 3 | # Trigger the workflow on push or pull request 4 | on: [push,pull_request_review] 5 | env: 6 | flutter_version: "2.10.2" 7 | 8 | #A workflow run is made up of one or more jobs. Jobs run in parallel by default. 9 | jobs: 10 | 11 | unit-testing: 12 | #The type of machine to run the job on. [windows,macos, ubuntu , self-hosted] 13 | runs-on: ubuntu-latest 14 | #sequence of tasks called 15 | steps: 16 | # The branch or tag ref that triggered the workflow will be checked out. 17 | # https://github.com/actions/checkout 18 | - uses: actions/checkout@v2 19 | # Setup a flutter environment. 20 | # https://github.com/marketplace/actions/flutter-action 21 | - name: Cache Flutter dependencies 22 | uses: actions/cache@v2 23 | with: 24 | path: /opt/hostedtoolcache/flutter 25 | key: ${{ runner.OS }}-flutter-install-cache-${{ env.flutter_version }} 26 | - uses: subosito/flutter-action@v2 27 | with: 28 | flutter-version: '${{ env.flutter_version }}' 29 | channel: 'stable' 30 | - name: Create env file 31 | run: | 32 | touch .env 33 | echo API_KEY=${{ secrets.API_KEY }} >> .env 34 | cat .env 35 | - run: flutter pub get 36 | # run static analys code 37 | - run: flutter analyze 38 | # run flutter widgets tests and unit tests 39 | - run: flutter test --coverage 40 | # Upload coverage reports to Codecov 41 | # https://github.com/marketplace/actions/codecov 42 | - name: Upload coverage to Codecov 43 | uses: codecov/codecov-action@v2 44 | with: 45 | token: ${{ secrets.CODECOV_TOKEN }} 46 | file: coverage/lcov.info 47 | ios-integration: 48 | #creates a build matrix for your jobs 49 | strategy: 50 | #set of different configurations of the virtual environment. 51 | matrix: 52 | device: 53 | - "iPhone 8" 54 | - "iPhone 13 Pro" 55 | fail-fast: false 56 | runs-on: macos-latest 57 | #Identifies any jobs that must complete successfully before this job will run. 58 | needs: unit-testing 59 | steps: 60 | - name: Start Simulator 61 | uses: futureware-tech/simulator-action@v1 62 | with: 63 | model: '${{matrix.device}}' 64 | - uses: actions/checkout@v2 65 | - name: Cache Flutter dependencies 66 | uses: actions/cache@v2 67 | with: 68 | path: /opt/hostedtoolcache/flutter 69 | key: ${{ runner.OS }}-flutter-install-cache-${{ env.flutter_version }} 70 | - uses: subosito/flutter-action@v2 71 | with: 72 | flutter-version: '${{ env.flutter_version }}' 73 | channel: 'stable' 74 | 75 | - name: Create env file 76 | run: | 77 | touch .env 78 | echo API_KEY=${{ secrets.API_KEY }} >> .env 79 | cat .env 80 | - run: flutter pub get 81 | # Run flutter integrate tests 82 | - name: Run Flutter Driver tests 83 | run: flutter drive --driver=test_driver/integration_test.dart --target=integration_test/main_test.dart 84 | 85 | android-integration: 86 | runs-on: macos-latest 87 | #creates a build matrix for your jobs 88 | strategy: 89 | #set of different configurations of the virtual environment. 90 | matrix: 91 | api-level: [21, 29] 92 | target: [default] 93 | needs: unit-testing 94 | steps: 95 | - uses: actions/checkout@v2 96 | - name: Cache Flutter dependencies 97 | uses: actions/cache@v2 98 | with: 99 | path: /opt/hostedtoolcache/flutter 100 | key: ${{ runner.OS }}-flutter-install-cache-${{ env.flutter_version }} 101 | - uses: subosito/flutter-action@v2 102 | with: 103 | flutter-version: '${{ env.flutter_version }}' 104 | channel: 'stable' 105 | - name: Create env file 106 | run: | 107 | touch .env 108 | echo API_KEY=${{ secrets.API_KEY }} >> .env 109 | cat .env 110 | - name: Run Flutter Driver tests 111 | #GitHub Action for installing, configuring and running Android Emulators (work only Mac OS) 112 | #https://github.com/ReactiveCircus/android-emulator-runner 113 | uses: reactivecircus/android-emulator-runner@v2 114 | with: 115 | api-level: ${{ matrix.api-level }} 116 | target: ${{ matrix.target }} 117 | arch: x86_64 118 | profile: Nexus 6 119 | script: flutter drive --driver=test_driver/integration_test.dart --target=integration_test/main_test.dart 120 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | 12 | # IntelliJ related 13 | *.iml 14 | *.ipr 15 | *.iws 16 | .idea/ 17 | 18 | # The .vscode folder contains launch configuration and tasks you configure in 19 | # VS Code which you may wish to be included in version control, so this line 20 | # is commented out by default. 21 | #.vscode/ 22 | 23 | # Flutter/Dart/Pub related 24 | **/doc/api/ 25 | **/ios/Flutter/.last_build_id 26 | .dart_tool/ 27 | .flutter-plugins 28 | .flutter-plugins-dependencies 29 | .packages 30 | .pub-cache/ 31 | .pub/ 32 | /build/ 33 | 34 | # Web related 35 | lib/generated_plugin_registrant.dart 36 | 37 | # Symbolication related 38 | app.*.symbols 39 | 40 | # Obfuscation related 41 | app.*.map.json 42 | 43 | # Folder generated by flutter test --coverage 44 | coverage/ 45 | 46 | .env 47 | ios/build/ 48 | pubspec.lock 49 | 50 | -------------------------------------------------------------------------------- /.metadata: -------------------------------------------------------------------------------- 1 | # This file tracks properties of this Flutter project. 2 | # Used by Flutter tool to assess capabilities and perform upgrades etc. 3 | # 4 | # This file should be version controlled and should not be manually edited. 5 | 6 | version: 7 | revision: 1aafb3a8b9b0c36241c5f5b34ee914770f015818 8 | channel: stable 9 | 10 | project_type: app 11 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": "app_crypto", 9 | "request": "launch", 10 | "type": "dart" 11 | }, 12 | { 13 | "name": "App Crypto", 14 | "program": "lib/main.dart", 15 | "request": "launch", 16 | "type": "dart", 17 | "args": [] 18 | } 19 | ] 20 | } -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | All notable changes to this project will be documented in this file. 3 | 4 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 5 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 6 | ## [Unreleased] 7 | 8 | 9 | ## [1.0.1] - 2022-02-21 10 | ### Update 11 | 12 | ## Changes 13 | - Flutter Upgrade 14 | - Riverpod Upgrade 15 | - Android Upgrade 16 | - GitHub Actions for Integration Testing Fixed 17 | - Settings UI Updated 18 | 19 | ## [1.0.0] - 2021-05-27 20 | ### First version 21 | 22 | ## Features 23 | - API REST (CryptoWatch) 24 | - Linear Graph View (Hour, Day, Week, etc) 25 | - OHLC Graph 26 | - Search 27 | - Light / Dark Theme 28 | - Multi Lenguage 29 | - Exchange Selection 30 | - Favorite Pair 31 | 32 | 33 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Salvador Valverde 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # Flutter Crypto APP 3 | Complete Flutter Application with Riverpod & Freezed + Dio for API REST. 4 | 5 | [![test](https://github.com/salvadordeveloper/flutter-crypto-app/actions/workflows/tests.yml/badge.svg)](https://github.com/salvadordeveloper/flutter-crypto-app/actions/workflows/tests.yml) 6 | [![build](https://github.com/salvadordeveloper/flutter-crypto-app/actions/workflows/release.yml/badge.svg)](https://github.com/salvadordeveloper/flutter-crypto-app/actions/workflows/release.yml) 7 | [![codecov](https://codecov.io/gh/salvadordeveloper/flutter-crypto-app/branch/main/graph/badge.svg?token=UYU0OB442S)](https://codecov.io/gh/salvadordeveloper/flutter-crypto-app) 8 | [![Flutter version](https://img.shields.io/badge/flutter-2.10.2-blue?logo=flutter)](https://flutter.dev/docs/get-started/install) 9 | [![GitHub license](https://img.shields.io/github/license/chinnonsantos/full_testing_flutter)](https://choosealicense.com/licenses/mit/) 10 | 11 | 12 | 13 | ## Features 14 | - API REST (CryptoWatch) 15 | - Linear Graph View (Hour, Day, Week, etc) 16 | - OHLC Graph 17 | - Search 18 | - Light / Dark Theme 19 | - Multi Language 20 | - Exchange Selection 21 | - Favorite Pair 22 | 23 | ### Stack 24 | - Flutter 2.10.2 25 | - Riverpod + Hooks 26 | - Freezed 27 | - Dio 28 | 29 | ### Testing 30 | - Unit Testing (flutter_test) 31 | - Integration Testing (integration_test) 32 | - Mock Data (http_mock_adapter) 33 | - Github Actions (iOS & Android Integration Test) 34 | 35 | ## Screenshots 36 | 37 | 38 | | Home | Details | Settings | 39 | | --- | --- | --- | 40 | |||| 41 | |||| 42 | 43 | ## Setup project 44 | 45 | Download project 46 | ```bash 47 | git clone https://github.com/salvadordeveloper/flutter-crypto-app 48 | ``` 49 | 50 | Get flutter dependencies 51 | ```bash 52 | flutter pub get 53 | ``` 54 | 55 | You need to create an account at https://cryptowat.ch/ to get a personal API KEY 56 | 57 | Rename the env.example file to .env and put there you API KEY 58 | ```bash 59 | API_KEY={CryptoWatch_KEY} 60 | ``` 61 | 62 | Run the app 63 | ```bash 64 | flutter run 65 | ``` 66 | 67 | If you have any error with generated files try to run this 68 | ```bash 69 | flutter pub run build_runner build --delete-conflicting-outputs 70 | ``` 71 | 72 | 73 | ### Testing 74 | 75 | Unit Test 76 | ```bash 77 | flutter test 78 | ``` 79 | Integration Test 80 | ```bash 81 | flutter drive --driver=test_driver/integration_test.dart --target=integration_test/main_test.dart 82 | ``` 83 | 84 | ## Resources 85 | [Flutter Docs](https://flutter.dev/docs) 86 | 87 | [Riverpod Docs](https://riverpod.dev/docs/getting_started/) 88 | 89 | [Cryptowatch Docs](https://docs.cryptowat.ch/rest-api/) 90 | 91 | 92 | ## Licence 93 | 94 | ``` 95 | MIT License 96 | 97 | Copyright (c) 2022 Salvador Valverde 98 | 99 | Permission is hereby granted, free of charge, to any person obtaining a copy 100 | of this software and associated documentation files (the "Software"), to deal 101 | in the Software without restriction, including without limitation the rights 102 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 103 | copies of the Software, and to permit persons to whom the Software is 104 | furnished to do so, subject to the following conditions: 105 | 106 | The above copyright notice and this permission notice shall be included in all 107 | copies or substantial portions of the Software. 108 | 109 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 110 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 111 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 112 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 113 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 114 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 115 | SOFTWARE. 116 | ``` 117 | 118 | -------------------------------------------------------------------------------- /analysis_options.yaml: -------------------------------------------------------------------------------- 1 | #include: package:effective_dart/analysis_options.yaml 2 | 3 | analyzer: 4 | exclude: 5 | - "**/*.g.dart" 6 | - "**/*.freezed.dart" 7 | 8 | -------------------------------------------------------------------------------- /android/.gitignore: -------------------------------------------------------------------------------- 1 | gradle-wrapper.jar 2 | /.gradle 3 | /captures/ 4 | /gradlew 5 | /gradlew.bat 6 | /local.properties 7 | GeneratedPluginRegistrant.java 8 | 9 | # Remember to never publicly share your keystore. 10 | # See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app 11 | key.properties 12 | -------------------------------------------------------------------------------- /android/app/build.gradle: -------------------------------------------------------------------------------- 1 | def localProperties = new Properties() 2 | def localPropertiesFile = rootProject.file('local.properties') 3 | if (localPropertiesFile.exists()) { 4 | localPropertiesFile.withReader('UTF-8') { reader -> 5 | localProperties.load(reader) 6 | } 7 | } 8 | 9 | def flutterRoot = localProperties.getProperty('flutter.sdk') 10 | if (flutterRoot == null) { 11 | throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") 12 | } 13 | 14 | def flutterVersionCode = localProperties.getProperty('flutter.versionCode') 15 | if (flutterVersionCode == null) { 16 | flutterVersionCode = '1' 17 | } 18 | 19 | def flutterVersionName = localProperties.getProperty('flutter.versionName') 20 | if (flutterVersionName == null) { 21 | flutterVersionName = '1.0' 22 | } 23 | 24 | apply plugin: 'com.android.application' 25 | apply plugin: 'kotlin-android' 26 | apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" 27 | 28 | android { 29 | compileSdkVersion 31 30 | 31 | sourceSets { 32 | main.java.srcDirs += 'src/main/kotlin' 33 | } 34 | 35 | lintOptions { 36 | disable 'InvalidPackage' 37 | } 38 | 39 | defaultConfig { 40 | applicationId "com.example.cryptocurrency_app" 41 | minSdkVersion 18 42 | targetSdkVersion 31 43 | versionCode flutterVersionCode.toInteger() 44 | versionName flutterVersionName 45 | } 46 | 47 | buildTypes { 48 | release { 49 | // TODO: Add your own signing config for the release build. 50 | // Signing with the debug keys for now, so `flutter run --release` works. 51 | signingConfig signingConfigs.debug 52 | } 53 | } 54 | } 55 | 56 | flutter { 57 | source '../..' 58 | } 59 | 60 | dependencies { 61 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 62 | } 63 | -------------------------------------------------------------------------------- /android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 8 | 9 | 13 | 20 | 24 | 28 | 33 | 37 | 38 | 39 | 40 | 41 | 42 | 44 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /android/app/src/main/kotlin/com/example/cryptocurrency_app/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.example.cryptocurrency_app 2 | 3 | import io.flutter.embedding.android.FlutterActivity 4 | 5 | class MainActivity: FlutterActivity() { 6 | } 7 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-hdpi/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/salvadordeveloper/flutter-crypto-app/ff3e872d6d7dbb690781a263ee3ef42a332d17cc/android/app/src/main/res/drawable-hdpi/splash.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-mdpi/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/salvadordeveloper/flutter-crypto-app/ff3e872d6d7dbb690781a263ee3ef42a332d17cc/android/app/src/main/res/drawable-mdpi/splash.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-night-v21/background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/salvadordeveloper/flutter-crypto-app/ff3e872d6d7dbb690781a263ee3ef42a332d17cc/android/app/src/main/res/drawable-night-v21/background.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-night-v21/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-night/background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/salvadordeveloper/flutter-crypto-app/ff3e872d6d7dbb690781a263ee3ef42a332d17cc/android/app/src/main/res/drawable-night/background.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-night/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-v21/background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/salvadordeveloper/flutter-crypto-app/ff3e872d6d7dbb690781a263ee3ef42a332d17cc/android/app/src/main/res/drawable-v21/background.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-v21/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-xhdpi/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/salvadordeveloper/flutter-crypto-app/ff3e872d6d7dbb690781a263ee3ef42a332d17cc/android/app/src/main/res/drawable-xhdpi/splash.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-xxhdpi/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/salvadordeveloper/flutter-crypto-app/ff3e872d6d7dbb690781a263ee3ef42a332d17cc/android/app/src/main/res/drawable-xxhdpi/splash.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-xxxhdpi/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/salvadordeveloper/flutter-crypto-app/ff3e872d6d7dbb690781a263ee3ef42a332d17cc/android/app/src/main/res/drawable-xxxhdpi/splash.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable/background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/salvadordeveloper/flutter-crypto-app/ff3e872d6d7dbb690781a263ee3ef42a332d17cc/android/app/src/main/res/drawable/background.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/salvadordeveloper/flutter-crypto-app/ff3e872d6d7dbb690781a263ee3ef42a332d17cc/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-hdpi/launcher_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/salvadordeveloper/flutter-crypto-app/ff3e872d6d7dbb690781a263ee3ef42a332d17cc/android/app/src/main/res/mipmap-hdpi/launcher_icon.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/salvadordeveloper/flutter-crypto-app/ff3e872d6d7dbb690781a263ee3ef42a332d17cc/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-mdpi/launcher_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/salvadordeveloper/flutter-crypto-app/ff3e872d6d7dbb690781a263ee3ef42a332d17cc/android/app/src/main/res/mipmap-mdpi/launcher_icon.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/salvadordeveloper/flutter-crypto-app/ff3e872d6d7dbb690781a263ee3ef42a332d17cc/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xhdpi/launcher_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/salvadordeveloper/flutter-crypto-app/ff3e872d6d7dbb690781a263ee3ef42a332d17cc/android/app/src/main/res/mipmap-xhdpi/launcher_icon.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/salvadordeveloper/flutter-crypto-app/ff3e872d6d7dbb690781a263ee3ef42a332d17cc/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxhdpi/launcher_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/salvadordeveloper/flutter-crypto-app/ff3e872d6d7dbb690781a263ee3ef42a332d17cc/android/app/src/main/res/mipmap-xxhdpi/launcher_icon.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/salvadordeveloper/flutter-crypto-app/ff3e872d6d7dbb690781a263ee3ef42a332d17cc/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxxhdpi/launcher_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/salvadordeveloper/flutter-crypto-app/ff3e872d6d7dbb690781a263ee3ef42a332d17cc/android/app/src/main/res/mipmap-xxxhdpi/launcher_icon.png -------------------------------------------------------------------------------- /android/app/src/main/res/values-night/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 16 | 19 | -------------------------------------------------------------------------------- /android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 16 | 19 | -------------------------------------------------------------------------------- /android/app/src/profile/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | ext.kotlin_version = '1.4.21' 3 | repositories { 4 | google() 5 | jcenter() 6 | } 7 | 8 | dependencies { 9 | classpath 'com.android.tools.build:gradle:4.1.2' 10 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 11 | } 12 | } 13 | 14 | allprojects { 15 | repositories { 16 | google() 17 | jcenter() 18 | } 19 | } 20 | 21 | rootProject.buildDir = '../build' 22 | subprojects { 23 | project.buildDir = "${rootProject.buildDir}/${project.name}" 24 | } 25 | subprojects { 26 | project.evaluationDependsOn(':app') 27 | } 28 | 29 | task clean(type: Delete) { 30 | delete rootProject.buildDir 31 | } 32 | -------------------------------------------------------------------------------- /android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx1536M 2 | android.useAndroidX=true 3 | android.enableJetifier=true 4 | -------------------------------------------------------------------------------- /android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Mon Feb 21 12:57:55 MST 2022 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-all.zip 7 | -------------------------------------------------------------------------------- /android/settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | 3 | def localPropertiesFile = new File(rootProject.projectDir, "local.properties") 4 | def properties = new Properties() 5 | 6 | assert localPropertiesFile.exists() 7 | localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) } 8 | 9 | def flutterSdkPath = properties.getProperty("flutter.sdk") 10 | assert flutterSdkPath != null, "flutter.sdk not set in local.properties" 11 | apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle" 12 | -------------------------------------------------------------------------------- /assets/icon/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/salvadordeveloper/flutter-crypto-app/ff3e872d6d7dbb690781a263ee3ef42a332d17cc/assets/icon/icon.png -------------------------------------------------------------------------------- /assets/icon/icon_ios.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/salvadordeveloper/flutter-crypto-app/ff3e872d6d7dbb690781a263ee3ef42a332d17cc/assets/icon/icon_ios.png -------------------------------------------------------------------------------- /assets/translations/en.json: -------------------------------------------------------------------------------- 1 | { 2 | "homeTitle" : "Home", 3 | "openChart" : "Open Chart", 4 | 5 | "searchTitle" : "Search", 6 | "searchBar" : "Search coin...", 7 | "noResults" : "No results", 8 | 9 | "settingsTitle": "Settings", 10 | 11 | "languageSection" : "Language", 12 | "language": "Language", 13 | "dataSection" : "Data", 14 | "exchange" : "Exchange", 15 | "topPair" : "Top Pair", 16 | "designSection" : "Design", 17 | "appTheme" : "App theme", 18 | 19 | "spanish" : "Spanish", 20 | "english" : "English", 21 | 22 | "summary" : "Summary", 23 | "orderbook" : "Orderbook", 24 | "trades" : "Trades", 25 | "ohlc" : "OHLC", 26 | 27 | "price" : "Price", 28 | "last" : "Last", 29 | "high" : "High", 30 | "low" : "Low", 31 | "change" : "Change", 32 | "volume" : "Volume", 33 | "quoteVolume" : "Quote Volume", 34 | "time" : "Time", 35 | "amount" : "Amount", 36 | "bid" : "Bid", 37 | "ask" : "Ask", 38 | 39 | "errorRequestCancelled" : "Request to API server was cancelled", 40 | "errorConnectionTimeout" : "Connection timeout with API server", 41 | "errorInternetConnection" : "Connection to API server failed due to internet connection", 42 | "errorReceiveTimeout" : "Receive timeout in connection with API server", 43 | "errorSendTimeout" : "Send timout in connection iwth API server", 44 | "errorBadRequest": "Bad request", 45 | "errorRequestNotFound": "The requested resource was not found", 46 | "errorIntenalServer" : "Internal server error", 47 | "errorSomethingWentWrong" : "Something went wrong" 48 | 49 | 50 | } -------------------------------------------------------------------------------- /assets/translations/es.json: -------------------------------------------------------------------------------- 1 | { 2 | "homeTitle" : "Inicio", 3 | "openChart" : "Abrir grafica", 4 | 5 | "searchTitle" : "Buscar", 6 | "searchBar" : "Buscar moneda...", 7 | "noResults" : "Sin resultados", 8 | 9 | "settingsTitle": "Ajustes", 10 | 11 | "languageSection" : "Lenguage", 12 | "language": "Idioma", 13 | "dataSection" : "Datos", 14 | "exchange" : "Exchange", 15 | "topPair" : "Top Par", 16 | "designSection" : "Diseño", 17 | "appTheme" : "Tema", 18 | 19 | "spanish" : "Español", 20 | "english" : "Ingles", 21 | 22 | 23 | "summary" : "Resumen", 24 | "orderbook" : "Orderbook", 25 | "trades" : "Trades", 26 | "ohlc" : "OHLC", 27 | 28 | "price" : "Precio", 29 | "last" : "Ultimo", 30 | "high" : "Más alto", 31 | "low" : "Más bajo", 32 | "change" : "Cambio", 33 | "volume" : "Volumen", 34 | "quoteVolume" : "Quote Volume", 35 | "time" : "Tiempo", 36 | "amount" : "Cantidad", 37 | "bid" : "Bid", 38 | "ask" : "Ask", 39 | 40 | "errorRequestCancelled" : "Request to API server was cancelled", 41 | "errorConnectionTimeout" : "Connection timeout with API server", 42 | "errorInternetConnection" : "Connection to API server failed due to internet connection", 43 | "errorReceiveTimeout" : "Receive timeout in connection with API server", 44 | "errorSendTimeout" : "Send timout in connection iwth API server", 45 | "errorBadRequest": "Bad request", 46 | "errorRequestNotFound": "The requested resource was not found", 47 | "errorIntenalServer" : "Internal server error", 48 | "errorSomethingWentWrong" : "Something went wrong" 49 | 50 | } -------------------------------------------------------------------------------- /integration_test/data/api_data.dart: -------------------------------------------------------------------------------- 1 | class ApiData { 2 | static final Map exchanges = { 3 | "result": [ 4 | { 5 | "id": 17, 6 | "symbol": "mexbt", 7 | "name": "meXBT", 8 | "route": "https://api.cryptowat.ch/exchanges/mexbt", 9 | "active": false 10 | }, 11 | { 12 | "id": 62, 13 | "symbol": "coinone", 14 | "name": "Coinone", 15 | "route": "https://api.cryptowat.ch/exchanges/coinone", 16 | "active": true 17 | }, 18 | ], 19 | "allowance": {"cost": 0, "remaining": 100, "upgrade": ""} 20 | }; 21 | 22 | static final Map pairs = { 23 | "result": [ 24 | { 25 | "id": 579, 26 | "exchange": "binance", 27 | "pair": "btcusdt", 28 | "active": true, 29 | "route": "https://api.cryptowat.ch/markets/binance/btcusdt" 30 | }, 31 | { 32 | "id": 580, 33 | "exchange": "binance", 34 | "pair": "ethbtc", 35 | "active": true, 36 | "route": "https://api.cryptowat.ch/markets/binance/ethbtc" 37 | }, 38 | { 39 | "id": 581, 40 | "exchange": "binance", 41 | "pair": "ltcbtc", 42 | "active": true, 43 | "route": "https://api.cryptowat.ch/markets/binance/ltcbtc" 44 | }, 45 | { 46 | "id": 582, 47 | "exchange": "binance", 48 | "pair": "neobtc", 49 | "active": true, 50 | "route": "https://api.cryptowat.ch/markets/binance/neobtc" 51 | }, 52 | ], 53 | "allowance": {"cost": 0, "remaining": 100, "upgrade": ""} 54 | }; 55 | 56 | static final Map pair_btcusdt_summary = { 57 | "result": { 58 | "price": { 59 | "last": 35503.33, 60 | "high": 43861.94, 61 | "low": 30000, 62 | "change": {"percentage": -0.18764266402587584, "absolute": -8200.75} 63 | }, 64 | "volume": 257132.87322650044, 65 | "volumeQuote": 10096197214.14349 66 | }, 67 | "allowance": {"cost": 0, "remaining": 100, "upgrade": ""} 68 | }; 69 | 70 | static final Map pair_btcusdt_oderbook = { 71 | "result": { 72 | "asks": [ 73 | [35922.59, 0.004088], 74 | [35925.23, 0.003071], 75 | [35925.71, 0.012824], 76 | [35927.12, 0.000556], 77 | [35927.58, 0.2178], 78 | ], 79 | "bids": [ 80 | [35904.23, 0.153095], 81 | [35900.35, 0.082238], 82 | [35898, 0.12], 83 | [35897.99, 0.006152], 84 | [35897.68, 0.04332], 85 | ], 86 | "seqNum": 429614 87 | }, 88 | "allowance": {"cost": 0, "remaining": 100, "upgrade": ""} 89 | }; 90 | 91 | static final Map pair_btcusdt_trades = { 92 | "result": [ 93 | [0, 1621433110, 34452.66, 0.008464], 94 | [0, 1621433110, 34454.43, 0.016662], 95 | [0, 1621433110, 34485.69, 0.00476], 96 | [0, 1621433110, 34475.97, 0.000401], 97 | [0, 1621433110, 34456.09, 0.0011], 98 | [0, 1621433110, 34456.09, 0.004997], 99 | ], 100 | "allowance": {"cost": 0, "remaining": 100, "upgrade": ""} 101 | }; 102 | 103 | static final Map pair_btcusdt_graph = { 104 | "result": { 105 | "14400": [ 106 | [ 107 | 1607054400, 108 | 19422.34, 109 | 19527, 110 | 19122.74, 111 | 19162.62, 112 | 8683.588417, 113 | 167917416.81467284 114 | ], 115 | [ 116 | 1607097600, 117 | 18835.47, 118 | 19146.22, 119 | 18686.38, 120 | 18943.35, 121 | 14717.586675, 122 | 278732315.17076141 123 | ], 124 | [ 125 | 1607112000, 126 | 18944.06, 127 | 19078.68, 128 | 18817, 129 | 19038.73, 130 | 8799.851665, 131 | 166925728.42698553 132 | ], 133 | ] 134 | }, 135 | "allowance": {"cost": 0, "remaining": 100, "upgrade": ""} 136 | }; 137 | } 138 | -------------------------------------------------------------------------------- /integration_test/main_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:integration_test/integration_test.dart'; 2 | 3 | import 'search_test.dart' as app; 4 | 5 | void main() { 6 | IntegrationTestWidgetsFlutterBinding.ensureInitialized(); 7 | app.main(); 8 | } 9 | -------------------------------------------------------------------------------- /integration_test/search_test.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'package:cryptocurrency_app/constants/keys.dart'; 3 | import 'package:cryptocurrency_app/main.dart' as app; 4 | import 'package:cryptocurrency_app/repository/crypto_repository.dart'; 5 | import 'package:dio/dio.dart'; 6 | import 'package:easy_localization/easy_localization.dart'; 7 | import 'package:flutter/material.dart'; 8 | import 'package:flutter_test/flutter_test.dart'; 9 | import 'package:hooks_riverpod/hooks_riverpod.dart'; 10 | import 'package:http_mock_adapter/http_mock_adapter.dart'; 11 | import 'data/api_data.dart'; 12 | 13 | void main() { 14 | final dio = Dio(); 15 | final dioAdapter = DioAdapter(dio: dio); 16 | setUpAll(() { 17 | dio.httpClientAdapter = dioAdapter; 18 | 19 | //Get graph 20 | dioAdapter.onGet('/markets/binance/btcusdt/ohlc', (request) { 21 | request.reply(200, ApiData.pair_btcusdt_graph); 22 | }); 23 | 24 | //Get trades 25 | dioAdapter.onGet('/markets/binance/btcusdt/trades', (request) { 26 | request.reply(200, ApiData.pair_btcusdt_trades); 27 | }); 28 | 29 | //Get orderbook 30 | dioAdapter.onGet('/markets/binance/btcusdt/orderbook', (request) { 31 | request.reply(200, ApiData.pair_btcusdt_oderbook); 32 | }); 33 | 34 | //Get sumary of btcusdt 35 | dioAdapter.onGet('/markets/binance/btcusdt/summary', (request) { 36 | print(request.toString()); 37 | request.reply(200, ApiData.pair_btcusdt_summary); 38 | }); 39 | 40 | //Get list of pairs from Binance 41 | dioAdapter.onGet('/markets/binance', (request) { 42 | print(request.toString()); 43 | 44 | request.reply(200, ApiData.pairs); 45 | }); 46 | }); 47 | 48 | group('Search Screen Test', () { 49 | testWidgets('Search screen loading data', (tester) async { 50 | WidgetsFlutterBinding.ensureInitialized(); 51 | await EasyLocalization.ensureInitialized(); 52 | 53 | runApp(EasyLocalization( 54 | supportedLocales: [Locale('en'), Locale('es')], 55 | path: 'assets/translations', 56 | fallbackLocale: Locale('en'), 57 | child: ProviderScope( 58 | overrides: [clientProvider.overrideWithValue(dio)], 59 | child: app.MyApp(), 60 | ))); 61 | 62 | await tester.pumpAndSettle(); 63 | final searchButton = find.byKey(Keys.NAV_SEARCH); 64 | 65 | await pumpUntilFound(tester, searchButton); 66 | 67 | await tester.tap(searchButton); 68 | 69 | await tester.pumpAndSettle(); 70 | 71 | final results = await find.byKey(Keys.PAIR_TILE); 72 | 73 | expect(results, findsNWidgets(4)); 74 | 75 | final searchTextField = await find.byKey(Keys.SEARCH_TEXT_FIELD); 76 | await tester.showKeyboard(searchTextField); 77 | tester.testTextInput.enterText("btcusdt"); 78 | await tester.testTextInput.receiveAction(TextInputAction.done); 79 | 80 | await tester.pumpAndSettle(); 81 | final resultsFiltered = await find.byKey(Keys.PAIR_TILE); 82 | expect(resultsFiltered, findsOneWidget); 83 | }); 84 | }); 85 | } 86 | 87 | Future pumpUntilFound( 88 | WidgetTester tester, 89 | Finder finder, { 90 | Duration timeout = const Duration(seconds: 30), 91 | }) async { 92 | var timerDone = false; 93 | final timer = 94 | Timer(timeout, () => throw TimeoutException("Pump until has timed out")); 95 | while (timerDone != true) { 96 | await tester.pump(); 97 | 98 | final found = tester.any(finder); 99 | if (found) { 100 | timerDone = true; 101 | } 102 | } 103 | timer.cancel(); 104 | } 105 | -------------------------------------------------------------------------------- /ios/.gitignore: -------------------------------------------------------------------------------- 1 | *.mode1v3 2 | *.mode2v3 3 | *.moved-aside 4 | *.pbxuser 5 | *.perspectivev3 6 | **/*sync/ 7 | .sconsign.dblite 8 | .tags* 9 | **/.vagrant/ 10 | **/DerivedData/ 11 | Icon? 12 | **/Pods/ 13 | **/.symlinks/ 14 | profile 15 | xcuserdata 16 | **/.generated/ 17 | Flutter/App.framework 18 | Flutter/Flutter.framework 19 | Flutter/Flutter.podspec 20 | Flutter/Generated.xcconfig 21 | Flutter/app.flx 22 | Flutter/app.zip 23 | Flutter/flutter_assets/ 24 | Flutter/flutter_export_environment.sh 25 | ServiceDefinitions.json 26 | Runner/GeneratedPluginRegistrant.* 27 | 28 | # Exceptions to above rules. 29 | !default.mode1v3 30 | !default.mode2v3 31 | !default.pbxuser 32 | !default.perspectivev3 33 | -------------------------------------------------------------------------------- /ios/Flutter/AppFrameworkInfo.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | App 9 | CFBundleIdentifier 10 | io.flutter.flutter.app 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | App 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1.0 23 | MinimumOSVersion 24 | 9.0 25 | 26 | 27 | -------------------------------------------------------------------------------- /ios/Flutter/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /ios/Flutter/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /ios/Podfile: -------------------------------------------------------------------------------- 1 | # Uncomment this line to define a global platform for your project 2 | # platform :ios, '9.0' 3 | 4 | # CocoaPods analytics sends network stats synchronously affecting flutter build latency. 5 | ENV['COCOAPODS_DISABLE_STATS'] = 'true' 6 | 7 | project 'Runner', { 8 | 'Debug' => :debug, 9 | 'Profile' => :release, 10 | 'Release' => :release, 11 | } 12 | 13 | def flutter_root 14 | generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__) 15 | unless File.exist?(generated_xcode_build_settings_path) 16 | raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first" 17 | end 18 | 19 | File.foreach(generated_xcode_build_settings_path) do |line| 20 | matches = line.match(/FLUTTER_ROOT\=(.*)/) 21 | return matches[1].strip if matches 22 | end 23 | raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get" 24 | end 25 | 26 | require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) 27 | 28 | flutter_ios_podfile_setup 29 | 30 | target 'Runner' do 31 | use_frameworks! 32 | use_modular_headers! 33 | 34 | flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) 35 | end 36 | 37 | post_install do |installer| 38 | installer.pods_project.targets.each do |target| 39 | flutter_additional_ios_build_settings(target) 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /ios/Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - Flutter (1.0.0) 3 | - flutter_secure_storage (3.3.1): 4 | - Flutter 5 | - integration_test (0.0.1): 6 | - Flutter 7 | - shared_preferences (0.0.1): 8 | - Flutter 9 | 10 | DEPENDENCIES: 11 | - Flutter (from `Flutter`) 12 | - flutter_secure_storage (from `.symlinks/plugins/flutter_secure_storage/ios`) 13 | - integration_test (from `.symlinks/plugins/integration_test/ios`) 14 | - shared_preferences (from `.symlinks/plugins/shared_preferences/ios`) 15 | 16 | EXTERNAL SOURCES: 17 | Flutter: 18 | :path: Flutter 19 | flutter_secure_storage: 20 | :path: ".symlinks/plugins/flutter_secure_storage/ios" 21 | integration_test: 22 | :path: ".symlinks/plugins/integration_test/ios" 23 | shared_preferences: 24 | :path: ".symlinks/plugins/shared_preferences/ios" 25 | 26 | SPEC CHECKSUMS: 27 | Flutter: 50d75fe2f02b26cc09d224853bb45737f8b3214a 28 | flutter_secure_storage: 7953c38a04c3fdbb00571bcd87d8e3b5ceb9daec 29 | integration_test: a1e7d09bd98eca2fc37aefd79d4f41ad37bdbbe5 30 | shared_preferences: af6bfa751691cdc24be3045c43ec037377ada40d 31 | 32 | PODFILE CHECKSUM: aafe91acc616949ddb318b77800a7f51bffa2a4c 33 | 34 | COCOAPODS: 1.10.2 35 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 39 | 40 | 41 | 42 | 43 | 44 | 54 | 56 | 62 | 63 | 64 | 65 | 66 | 67 | 73 | 75 | 81 | 82 | 83 | 84 | 86 | 87 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Runner/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import Flutter 3 | 4 | @UIApplicationMain 5 | @objc class AppDelegate: FlutterAppDelegate { 6 | override func application( 7 | _ application: UIApplication, 8 | didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? 9 | ) -> Bool { 10 | GeneratedPluginRegistrant.register(with: self) 11 | return super.application(application, didFinishLaunchingWithOptions: launchOptions) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "20x20", 5 | "idiom" : "iphone", 6 | "filename" : "Icon-App-20x20@2x.png", 7 | "scale" : "2x" 8 | }, 9 | { 10 | "size" : "20x20", 11 | "idiom" : "iphone", 12 | "filename" : "Icon-App-20x20@3x.png", 13 | "scale" : "3x" 14 | }, 15 | { 16 | "size" : "29x29", 17 | "idiom" : "iphone", 18 | "filename" : "Icon-App-29x29@1x.png", 19 | "scale" : "1x" 20 | }, 21 | { 22 | "size" : "29x29", 23 | "idiom" : "iphone", 24 | "filename" : "Icon-App-29x29@2x.png", 25 | "scale" : "2x" 26 | }, 27 | { 28 | "size" : "29x29", 29 | "idiom" : "iphone", 30 | "filename" : "Icon-App-29x29@3x.png", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "size" : "40x40", 35 | "idiom" : "iphone", 36 | "filename" : "Icon-App-40x40@2x.png", 37 | "scale" : "2x" 38 | }, 39 | { 40 | "size" : "40x40", 41 | "idiom" : "iphone", 42 | "filename" : "Icon-App-40x40@3x.png", 43 | "scale" : "3x" 44 | }, 45 | { 46 | "size" : "60x60", 47 | "idiom" : "iphone", 48 | "filename" : "Icon-App-60x60@2x.png", 49 | "scale" : "2x" 50 | }, 51 | { 52 | "size" : "60x60", 53 | "idiom" : "iphone", 54 | "filename" : "Icon-App-60x60@3x.png", 55 | "scale" : "3x" 56 | }, 57 | { 58 | "size" : "20x20", 59 | "idiom" : "ipad", 60 | "filename" : "Icon-App-20x20@1x.png", 61 | "scale" : "1x" 62 | }, 63 | { 64 | "size" : "20x20", 65 | "idiom" : "ipad", 66 | "filename" : "Icon-App-20x20@2x.png", 67 | "scale" : "2x" 68 | }, 69 | { 70 | "size" : "29x29", 71 | "idiom" : "ipad", 72 | "filename" : "Icon-App-29x29@1x.png", 73 | "scale" : "1x" 74 | }, 75 | { 76 | "size" : "29x29", 77 | "idiom" : "ipad", 78 | "filename" : "Icon-App-29x29@2x.png", 79 | "scale" : "2x" 80 | }, 81 | { 82 | "size" : "40x40", 83 | "idiom" : "ipad", 84 | "filename" : "Icon-App-40x40@1x.png", 85 | "scale" : "1x" 86 | }, 87 | { 88 | "size" : "40x40", 89 | "idiom" : "ipad", 90 | "filename" : "Icon-App-40x40@2x.png", 91 | "scale" : "2x" 92 | }, 93 | { 94 | "size" : "76x76", 95 | "idiom" : "ipad", 96 | "filename" : "Icon-App-76x76@1x.png", 97 | "scale" : "1x" 98 | }, 99 | { 100 | "size" : "76x76", 101 | "idiom" : "ipad", 102 | "filename" : "Icon-App-76x76@2x.png", 103 | "scale" : "2x" 104 | }, 105 | { 106 | "size" : "83.5x83.5", 107 | "idiom" : "ipad", 108 | "filename" : "Icon-App-83.5x83.5@2x.png", 109 | "scale" : "2x" 110 | }, 111 | { 112 | "size" : "1024x1024", 113 | "idiom" : "ios-marketing", 114 | "filename" : "Icon-App-1024x1024@1x.png", 115 | "scale" : "1x" 116 | } 117 | ], 118 | "info" : { 119 | "version" : 1, 120 | "author" : "xcode" 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/salvadordeveloper/flutter-crypto-app/ff3e872d6d7dbb690781a263ee3ef42a332d17cc/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/salvadordeveloper/flutter-crypto-app/ff3e872d6d7dbb690781a263ee3ef42a332d17cc/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/salvadordeveloper/flutter-crypto-app/ff3e872d6d7dbb690781a263ee3ef42a332d17cc/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/salvadordeveloper/flutter-crypto-app/ff3e872d6d7dbb690781a263ee3ef42a332d17cc/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/salvadordeveloper/flutter-crypto-app/ff3e872d6d7dbb690781a263ee3ef42a332d17cc/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/salvadordeveloper/flutter-crypto-app/ff3e872d6d7dbb690781a263ee3ef42a332d17cc/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/salvadordeveloper/flutter-crypto-app/ff3e872d6d7dbb690781a263ee3ef42a332d17cc/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/salvadordeveloper/flutter-crypto-app/ff3e872d6d7dbb690781a263ee3ef42a332d17cc/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/salvadordeveloper/flutter-crypto-app/ff3e872d6d7dbb690781a263ee3ef42a332d17cc/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/salvadordeveloper/flutter-crypto-app/ff3e872d6d7dbb690781a263ee3ef42a332d17cc/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/salvadordeveloper/flutter-crypto-app/ff3e872d6d7dbb690781a263ee3ef42a332d17cc/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/salvadordeveloper/flutter-crypto-app/ff3e872d6d7dbb690781a263ee3ef42a332d17cc/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/salvadordeveloper/flutter-crypto-app/ff3e872d6d7dbb690781a263ee3ef42a332d17cc/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/salvadordeveloper/flutter-crypto-app/ff3e872d6d7dbb690781a263ee3ef42a332d17cc/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/salvadordeveloper/flutter-crypto-app/ff3e872d6d7dbb690781a263ee3ef42a332d17cc/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchBackground.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "background.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "appearances" : [ 10 | { 11 | "appearance" : "luminosity", 12 | "value" : "dark" 13 | } 14 | ], 15 | "filename" : "darkbackground.png", 16 | "idiom" : "universal", 17 | "scale" : "1x" 18 | }, 19 | { 20 | "idiom" : "universal", 21 | "scale" : "2x" 22 | }, 23 | { 24 | "appearances" : [ 25 | { 26 | "appearance" : "luminosity", 27 | "value" : "dark" 28 | } 29 | ], 30 | "idiom" : "universal", 31 | "scale" : "2x" 32 | }, 33 | { 34 | "idiom" : "universal", 35 | "scale" : "3x" 36 | }, 37 | { 38 | "appearances" : [ 39 | { 40 | "appearance" : "luminosity", 41 | "value" : "dark" 42 | } 43 | ], 44 | "idiom" : "universal", 45 | "scale" : "3x" 46 | } 47 | ], 48 | "info" : { 49 | "author" : "xcode", 50 | "version" : 1 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchBackground.imageset/background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/salvadordeveloper/flutter-crypto-app/ff3e872d6d7dbb690781a263ee3ef42a332d17cc/ios/Runner/Assets.xcassets/LaunchBackground.imageset/background.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchBackground.imageset/darkbackground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/salvadordeveloper/flutter-crypto-app/ff3e872d6d7dbb690781a263ee3ef42a332d17cc/ios/Runner/Assets.xcassets/LaunchBackground.imageset/darkbackground.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "LaunchImage.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "filename" : "LaunchImage@2x.png", 10 | "idiom" : "universal", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "filename" : "LaunchImage@3x.png", 15 | "idiom" : "universal", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "author" : "xcode", 21 | "version" : 1 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/salvadordeveloper/flutter-crypto-app/ff3e872d6d7dbb690781a263ee3ef42a332d17cc/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/salvadordeveloper/flutter-crypto-app/ff3e872d6d7dbb690781a263ee3ef42a332d17cc/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/salvadordeveloper/flutter-crypto-app/ff3e872d6d7dbb690781a263ee3ef42a332d17cc/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md: -------------------------------------------------------------------------------- 1 | # Launch Screen Assets 2 | 3 | You can customize the launch screen with your own desired assets by replacing the image files in this directory. 4 | 5 | You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. -------------------------------------------------------------------------------- /ios/Runner/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /ios/Runner/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /ios/Runner/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleLocalizations 14 | 15 | en 16 | es 17 | 18 | CFBundleName 19 | Crypto App 20 | CFBundlePackageType 21 | APPL 22 | CFBundleShortVersionString 23 | $(FLUTTER_BUILD_NAME) 24 | CFBundleSignature 25 | ???? 26 | CFBundleVersion 27 | $(FLUTTER_BUILD_NUMBER) 28 | LSRequiresIPhoneOS 29 | 30 | UILaunchStoryboardName 31 | LaunchScreen 32 | UIMainStoryboardFile 33 | Main 34 | UIStatusBarHidden 35 | 36 | UISupportedInterfaceOrientations 37 | 38 | UIInterfaceOrientationPortrait 39 | UIInterfaceOrientationLandscapeLeft 40 | UIInterfaceOrientationLandscapeRight 41 | 42 | UISupportedInterfaceOrientations~ipad 43 | 44 | UIInterfaceOrientationPortrait 45 | UIInterfaceOrientationPortraitUpsideDown 46 | UIInterfaceOrientationLandscapeLeft 47 | UIInterfaceOrientationLandscapeRight 48 | 49 | UIViewControllerBasedStatusBarAppearance 50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /ios/Runner/Runner-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | #import "GeneratedPluginRegistrant.h" 2 | -------------------------------------------------------------------------------- /lib/constants/app_theme.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class AppTheme { 4 | static final ThemeData light = ThemeData( 5 | iconTheme: IconThemeData(color: Colors.black87), 6 | bottomNavigationBarTheme: BottomNavigationBarThemeData( 7 | backgroundColor: Colors.blueGrey, 8 | selectedItemColor: Colors.white, 9 | unselectedItemColor: Colors.white70), 10 | appBarTheme: AppBarTheme( 11 | color: Colors.blueGrey, 12 | ), 13 | brightness: Brightness.light, 14 | scaffoldBackgroundColor: Colors.white, 15 | cardColor: Colors.grey[500], 16 | unselectedWidgetColor: Colors.black45, 17 | focusColor: Colors.black, 18 | textTheme: TextTheme( 19 | headline1: TextStyle( 20 | color: Colors.black, fontSize: 40, fontWeight: FontWeight.w500), 21 | headline2: TextStyle( 22 | color: Colors.black, fontSize: 34, fontWeight: FontWeight.w400), 23 | headline3: TextStyle( 24 | color: Colors.black, fontSize: 22, fontWeight: FontWeight.w500), 25 | headline4: TextStyle( 26 | color: Colors.black54, fontSize: 16, fontWeight: FontWeight.bold), 27 | headline5: TextStyle( 28 | color: Colors.black, fontSize: 19, fontWeight: FontWeight.w700), 29 | headline6: TextStyle( 30 | color: Colors.black, fontSize: 13, fontWeight: FontWeight.w400), 31 | subtitle1: TextStyle( 32 | color: Colors.black87, fontSize: 14, fontWeight: FontWeight.normal), 33 | subtitle2: TextStyle( 34 | color: Colors.black, fontSize: 16, fontWeight: FontWeight.bold), 35 | ), 36 | ); 37 | 38 | static final ThemeData dark = ThemeData( 39 | primaryColor: Colors.black12, 40 | bottomNavigationBarTheme: BottomNavigationBarThemeData( 41 | backgroundColor: Colors.black, 42 | selectedItemColor: Colors.white, 43 | unselectedItemColor: Colors.white70), 44 | appBarTheme: AppBarTheme( 45 | color: Colors.black, 46 | ), 47 | brightness: Brightness.dark, 48 | scaffoldBackgroundColor: Colors.black, 49 | focusColor: Colors.white, 50 | textTheme: TextTheme( 51 | headline1: TextStyle( 52 | color: Colors.white, fontSize: 40, fontWeight: FontWeight.w500), 53 | headline2: TextStyle( 54 | color: Colors.white, fontSize: 34, fontWeight: FontWeight.w400), 55 | headline3: TextStyle( 56 | color: Colors.white, fontSize: 22, fontWeight: FontWeight.w500), 57 | headline4: TextStyle( 58 | color: Colors.white70, fontSize: 16, fontWeight: FontWeight.bold), 59 | headline5: TextStyle( 60 | color: Colors.white, fontSize: 19, fontWeight: FontWeight.w700), 61 | headline6: TextStyle( 62 | color: Colors.white, fontSize: 13, fontWeight: FontWeight.w400), 63 | subtitle1: TextStyle( 64 | color: Colors.white70, fontSize: 14, fontWeight: FontWeight.normal), 65 | subtitle2: TextStyle( 66 | color: Colors.white, fontSize: 16, fontWeight: FontWeight.bold)), 67 | ); 68 | } 69 | -------------------------------------------------------------------------------- /lib/constants/exceptions.dart: -------------------------------------------------------------------------------- 1 | import 'package:dio/dio.dart'; 2 | import '../../generated/locale_keys.g.dart'; 3 | 4 | class DataException implements Exception { 5 | DataException({required this.message}); 6 | 7 | DataException.fromDioError(DioError dioError) { 8 | switch (dioError.type) { 9 | case DioErrorType.cancel: 10 | message = LocaleKeys.errorRequestCancelled; 11 | break; 12 | case DioErrorType.connectTimeout: 13 | message = LocaleKeys.errorConnectionTimeout; 14 | break; 15 | case DioErrorType.receiveTimeout: 16 | message = LocaleKeys.errorReceiveTimeout; 17 | break; 18 | case DioErrorType.response: 19 | message = _handleError(dioError.response!.statusCode!); 20 | break; 21 | case DioErrorType.sendTimeout: 22 | message = LocaleKeys.errorSendTimeout; 23 | break; 24 | default: 25 | message = LocaleKeys.errorInternetConnection; 26 | break; 27 | } 28 | } 29 | 30 | String message = ""; 31 | 32 | String _handleError(int statusCode) { 33 | switch (statusCode) { 34 | case 400: 35 | return LocaleKeys.errorBadRequest; 36 | case 404: 37 | return LocaleKeys.errorRequestNotFound; 38 | case 500: 39 | return LocaleKeys.errorIntenalServer; 40 | default: 41 | return LocaleKeys.errorSomethingWentWrong; 42 | } 43 | } 44 | 45 | @override 46 | String toString() => message; 47 | } 48 | -------------------------------------------------------------------------------- /lib/constants/keys.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class Keys { 4 | static final NAV_BAR = Key('nav_bar'); 5 | static final NAV_HOME = Key('nav_home'); 6 | static final NAV_SEARCH = Key('nav_search'); 7 | static final NAV_SETTINGS = Key('nav_settings'); 8 | 9 | static final HOME_SCREEN = Key('home_screen'); 10 | static final SEARCH_SCREEN = Key('search_screen'); 11 | static final SETTINGS_SCREEN = Key('settings_screen'); 12 | static final DETAILS_SCREEN = Key('details_screen'); 13 | 14 | static final SEARCH_TEXT_FIELD = Key('seach_text_field'); 15 | 16 | static final PAIR_TILE = Key('pair_tile'); 17 | } 18 | -------------------------------------------------------------------------------- /lib/constants/utils.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:intl/intl.dart'; 3 | 4 | import '../models/graph/graph/graph.dart'; 5 | 6 | ThemeMode getThemeMode(String type) { 7 | ThemeMode themeMode = ThemeMode.system; 8 | switch (type) { 9 | case "System": 10 | themeMode = ThemeMode.system; 11 | break; 12 | case "Dark": 13 | themeMode = ThemeMode.dark; 14 | break; 15 | case "Light": 16 | themeMode = ThemeMode.light; 17 | break; 18 | } 19 | return themeMode; 20 | } 21 | 22 | final themeModes = ["System", "Dark", "Light"]; 23 | 24 | final String defaultLenguage = "English"; 25 | final String defaultExchange = "binance"; 26 | final String defaultPair = "btcusdt"; 27 | final String defaultTheme = "System"; 28 | 29 | List getPoints(Graph graph) { 30 | if (graph.pairs[0].points.length > 0) 31 | return graph.pairs[0].points.map((e) => e.closePrice).toList(); 32 | else 33 | return []; 34 | } 35 | 36 | String epochToString(String epoch) { 37 | final DateTime timeStamp = 38 | DateTime.fromMillisecondsSinceEpoch(int.parse(epoch) * 1000); 39 | return DateFormat('dd/MM/yyyy').format(timeStamp); 40 | } 41 | 42 | final List demoGraphData = const [ 43 | 86, 44 | 45, 45 | 59, 46 | 65, 47 | 1, 48 | 62, 49 | 26, 50 | 41, 51 | 88, 52 | 60, 53 | 17, 54 | 18, 55 | 58, 56 | 67, 57 | 55, 58 | 56, 59 | 97, 60 | 96, 61 | 22, 62 | 57, 63 | 29, 64 | 69, 65 | 19, 66 | 30, 67 | 47, 68 | 63, 69 | 33, 70 | 37, 71 | 40, 72 | 51, 73 | 53, 74 | 91, 75 | 71, 76 | 92, 77 | 28, 78 | ]; 79 | -------------------------------------------------------------------------------- /lib/generated/locale_keys.g.dart: -------------------------------------------------------------------------------- 1 | // DO NOT EDIT. This is code generated via package:easy_localization/generate.dart 2 | 3 | abstract class LocaleKeys { 4 | static const homeTitle = 'homeTitle'; 5 | static const openChart = 'openChart'; 6 | static const searchTitle = 'searchTitle'; 7 | static const searchBar = 'searchBar'; 8 | static const noResults = 'noResults'; 9 | static const settingsTitle = 'settingsTitle'; 10 | static const languageSection = 'languageSection'; 11 | static const language = 'language'; 12 | static const dataSection = 'dataSection'; 13 | static const exchange = 'exchange'; 14 | static const topPair = 'topPair'; 15 | static const designSection = 'designSection'; 16 | static const appTheme = 'appTheme'; 17 | static const spanish = 'spanish'; 18 | static const english = 'english'; 19 | static const summary = 'summary'; 20 | static const orderbook = 'orderbook'; 21 | static const trades = 'trades'; 22 | static const ohlc = 'ohlc'; 23 | static const price = 'price'; 24 | static const last = 'last'; 25 | static const high = 'high'; 26 | static const low = 'low'; 27 | static const change = 'change'; 28 | static const volume = 'volume'; 29 | static const quoteVolume = 'quoteVolume'; 30 | static const time = 'time'; 31 | static const amount = 'amount'; 32 | static const bid = 'bid'; 33 | static const ask = 'ask'; 34 | static const errorRequestCancelled = 'errorRequestCancelled'; 35 | static const errorConnectionTimeout = 'errorConnectionTimeout'; 36 | static const errorInternetConnection = 'errorInternetConnection'; 37 | static const errorReceiveTimeout = 'errorReceiveTimeout'; 38 | static const errorSendTimeout = 'errorSendTimeout'; 39 | static const errorBadRequest = 'errorBadRequest'; 40 | static const errorRequestNotFound = 'errorRequestNotFound'; 41 | static const errorIntenalServer = 'errorIntenalServer'; 42 | static const errorSomethingWentWrong = 'errorSomethingWentWrong'; 43 | 44 | } 45 | -------------------------------------------------------------------------------- /lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'package:cryptocurrency_app/constants/app_theme.dart'; 2 | import 'package:easy_localization/easy_localization.dart'; 3 | import 'package:flutter/material.dart'; 4 | import 'package:hooks_riverpod/hooks_riverpod.dart'; 5 | import 'package:flutter_dotenv/flutter_dotenv.dart'; 6 | import 'provider/settings_provider.dart'; 7 | import 'ui/home.dart'; 8 | import 'package:cryptocurrency_app/constants/utils.dart' as Utils; 9 | 10 | void main() async { 11 | await dotenv.load(fileName: ".env"); 12 | WidgetsFlutterBinding.ensureInitialized(); 13 | await EasyLocalization.ensureInitialized(); 14 | 15 | runApp(EasyLocalization( 16 | supportedLocales: [Locale('en'), Locale('es')], 17 | path: 'assets/translations', 18 | fallbackLocale: Locale('en'), 19 | child: ProviderScope(child: MyApp()))); 20 | } 21 | 22 | class MyApp extends HookConsumerWidget { 23 | MyApp({Key? key}) : super(key: key); 24 | 25 | @override 26 | Widget build(BuildContext context, WidgetRef ref) { 27 | final settings = ref.watch(cryptoSettings); 28 | 29 | final themeMode = settings.maybeWhen( 30 | data: (data) => Utils.getThemeMode(data.themeMode), 31 | orElse: () => ThemeMode.system); 32 | 33 | return MaterialApp( 34 | debugShowCheckedModeBanner: false, 35 | localizationsDelegates: context.localizationDelegates, 36 | supportedLocales: context.supportedLocales, 37 | locale: context.locale, 38 | home: Home(), 39 | themeMode: themeMode, 40 | theme: AppTheme.light, 41 | darkTheme: AppTheme.dark); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /lib/models/allowance/allowance.dart: -------------------------------------------------------------------------------- 1 | import 'package:freezed_annotation/freezed_annotation.dart'; 2 | 3 | part 'allowance.freezed.dart'; 4 | part 'allowance.g.dart'; 5 | 6 | @freezed 7 | abstract class Allowance with _$Allowance { 8 | const factory Allowance({ 9 | required double cost, 10 | required double remaining, 11 | }) = _Allowance; 12 | 13 | factory Allowance.fromJson(Map json) => 14 | _$AllowanceFromJson(json); 15 | } 16 | -------------------------------------------------------------------------------- /lib/models/allowance/allowance.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'allowance.dart'; 4 | 5 | // ************************************************************************** 6 | // JsonSerializableGenerator 7 | // ************************************************************************** 8 | 9 | _$_Allowance _$_$_AllowanceFromJson(Map json) { 10 | return _$_Allowance( 11 | cost: (json['cost'] as num).toDouble(), 12 | remaining: (json['remaining'] as num).toDouble(), 13 | ); 14 | } 15 | 16 | Map _$_$_AllowanceToJson(_$_Allowance instance) => 17 | { 18 | 'cost': instance.cost, 19 | 'remaining': instance.remaining, 20 | }; 21 | -------------------------------------------------------------------------------- /lib/models/exchanges/exchange/exchange.dart: -------------------------------------------------------------------------------- 1 | import 'package:freezed_annotation/freezed_annotation.dart'; 2 | 3 | part 'exchange.freezed.dart'; 4 | part 'exchange.g.dart'; 5 | 6 | @freezed 7 | abstract class Exchange with _$Exchange { 8 | const factory Exchange( 9 | {required int id, 10 | required String symbol, 11 | required String name, 12 | required String route, 13 | required bool active}) = _Exchange; 14 | 15 | factory Exchange.fromJson(Map json) => 16 | _$ExchangeFromJson(json); 17 | } 18 | -------------------------------------------------------------------------------- /lib/models/exchanges/exchange/exchange.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'exchange.dart'; 4 | 5 | // ************************************************************************** 6 | // JsonSerializableGenerator 7 | // ************************************************************************** 8 | 9 | _$_Exchange _$_$_ExchangeFromJson(Map json) { 10 | return _$_Exchange( 11 | id: json['id'] as int, 12 | symbol: json['symbol'] as String, 13 | name: json['name'] as String, 14 | route: json['route'] as String, 15 | active: json['active'] as bool, 16 | ); 17 | } 18 | 19 | Map _$_$_ExchangeToJson(_$_Exchange instance) => 20 | { 21 | 'id': instance.id, 22 | 'symbol': instance.symbol, 23 | 'name': instance.name, 24 | 'route': instance.route, 25 | 'active': instance.active, 26 | }; 27 | -------------------------------------------------------------------------------- /lib/models/exchanges/exchanges_response/exchanges_response.dart: -------------------------------------------------------------------------------- 1 | import 'package:cryptocurrency_app/models/exchanges/exchange/exchange.dart'; 2 | import 'package:freezed_annotation/freezed_annotation.dart'; 3 | 4 | part 'exchanges_response.freezed.dart'; 5 | part 'exchanges_response.g.dart'; 6 | 7 | @freezed 8 | abstract class ExchangesResponse with _$ExchangesResponse { 9 | const factory ExchangesResponse({required List result}) = 10 | _ExchangesResponse; 11 | 12 | factory ExchangesResponse.fromJson(Map json) => 13 | _$ExchangesResponseFromJson(json); 14 | } 15 | -------------------------------------------------------------------------------- /lib/models/exchanges/exchanges_response/exchanges_response.freezed.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | // ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides 3 | 4 | part of 'exchanges_response.dart'; 5 | 6 | // ************************************************************************** 7 | // FreezedGenerator 8 | // ************************************************************************** 9 | 10 | T _$identity(T value) => value; 11 | 12 | final _privateConstructorUsedError = UnsupportedError( 13 | 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more informations: https://github.com/rrousselGit/freezed#custom-getters-and-methods'); 14 | 15 | ExchangesResponse _$ExchangesResponseFromJson(Map json) { 16 | return _ExchangesResponse.fromJson(json); 17 | } 18 | 19 | /// @nodoc 20 | class _$ExchangesResponseTearOff { 21 | const _$ExchangesResponseTearOff(); 22 | 23 | _ExchangesResponse call({required List result}) { 24 | return _ExchangesResponse( 25 | result: result, 26 | ); 27 | } 28 | 29 | ExchangesResponse fromJson(Map json) { 30 | return ExchangesResponse.fromJson(json); 31 | } 32 | } 33 | 34 | /// @nodoc 35 | const $ExchangesResponse = _$ExchangesResponseTearOff(); 36 | 37 | /// @nodoc 38 | mixin _$ExchangesResponse { 39 | List get result => throw _privateConstructorUsedError; 40 | 41 | Map toJson() => throw _privateConstructorUsedError; 42 | @JsonKey(ignore: true) 43 | $ExchangesResponseCopyWith get copyWith => 44 | throw _privateConstructorUsedError; 45 | } 46 | 47 | /// @nodoc 48 | abstract class $ExchangesResponseCopyWith<$Res> { 49 | factory $ExchangesResponseCopyWith( 50 | ExchangesResponse value, $Res Function(ExchangesResponse) then) = 51 | _$ExchangesResponseCopyWithImpl<$Res>; 52 | $Res call({List result}); 53 | } 54 | 55 | /// @nodoc 56 | class _$ExchangesResponseCopyWithImpl<$Res> 57 | implements $ExchangesResponseCopyWith<$Res> { 58 | _$ExchangesResponseCopyWithImpl(this._value, this._then); 59 | 60 | final ExchangesResponse _value; 61 | // ignore: unused_field 62 | final $Res Function(ExchangesResponse) _then; 63 | 64 | @override 65 | $Res call({ 66 | Object? result = freezed, 67 | }) { 68 | return _then(_value.copyWith( 69 | result: result == freezed 70 | ? _value.result 71 | : result // ignore: cast_nullable_to_non_nullable 72 | as List, 73 | )); 74 | } 75 | } 76 | 77 | /// @nodoc 78 | abstract class _$ExchangesResponseCopyWith<$Res> 79 | implements $ExchangesResponseCopyWith<$Res> { 80 | factory _$ExchangesResponseCopyWith( 81 | _ExchangesResponse value, $Res Function(_ExchangesResponse) then) = 82 | __$ExchangesResponseCopyWithImpl<$Res>; 83 | @override 84 | $Res call({List result}); 85 | } 86 | 87 | /// @nodoc 88 | class __$ExchangesResponseCopyWithImpl<$Res> 89 | extends _$ExchangesResponseCopyWithImpl<$Res> 90 | implements _$ExchangesResponseCopyWith<$Res> { 91 | __$ExchangesResponseCopyWithImpl( 92 | _ExchangesResponse _value, $Res Function(_ExchangesResponse) _then) 93 | : super(_value, (v) => _then(v as _ExchangesResponse)); 94 | 95 | @override 96 | _ExchangesResponse get _value => super._value as _ExchangesResponse; 97 | 98 | @override 99 | $Res call({ 100 | Object? result = freezed, 101 | }) { 102 | return _then(_ExchangesResponse( 103 | result: result == freezed 104 | ? _value.result 105 | : result // ignore: cast_nullable_to_non_nullable 106 | as List, 107 | )); 108 | } 109 | } 110 | 111 | /// @nodoc 112 | @JsonSerializable() 113 | class _$_ExchangesResponse implements _ExchangesResponse { 114 | const _$_ExchangesResponse({required this.result}); 115 | 116 | factory _$_ExchangesResponse.fromJson(Map json) => 117 | _$_$_ExchangesResponseFromJson(json); 118 | 119 | @override 120 | final List result; 121 | 122 | @override 123 | String toString() { 124 | return 'ExchangesResponse(result: $result)'; 125 | } 126 | 127 | @override 128 | bool operator ==(dynamic other) { 129 | return identical(this, other) || 130 | (other is _ExchangesResponse && 131 | (identical(other.result, result) || 132 | const DeepCollectionEquality().equals(other.result, result))); 133 | } 134 | 135 | @override 136 | int get hashCode => 137 | runtimeType.hashCode ^ const DeepCollectionEquality().hash(result); 138 | 139 | @JsonKey(ignore: true) 140 | @override 141 | _$ExchangesResponseCopyWith<_ExchangesResponse> get copyWith => 142 | __$ExchangesResponseCopyWithImpl<_ExchangesResponse>(this, _$identity); 143 | 144 | @override 145 | Map toJson() { 146 | return _$_$_ExchangesResponseToJson(this); 147 | } 148 | } 149 | 150 | abstract class _ExchangesResponse implements ExchangesResponse { 151 | const factory _ExchangesResponse({required List result}) = 152 | _$_ExchangesResponse; 153 | 154 | factory _ExchangesResponse.fromJson(Map json) = 155 | _$_ExchangesResponse.fromJson; 156 | 157 | @override 158 | List get result => throw _privateConstructorUsedError; 159 | @override 160 | @JsonKey(ignore: true) 161 | _$ExchangesResponseCopyWith<_ExchangesResponse> get copyWith => 162 | throw _privateConstructorUsedError; 163 | } 164 | -------------------------------------------------------------------------------- /lib/models/exchanges/exchanges_response/exchanges_response.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'exchanges_response.dart'; 4 | 5 | // ************************************************************************** 6 | // JsonSerializableGenerator 7 | // ************************************************************************** 8 | 9 | _$_ExchangesResponse _$_$_ExchangesResponseFromJson(Map json) { 10 | return _$_ExchangesResponse( 11 | result: (json['result'] as List) 12 | .map((e) => Exchange.fromJson(e as Map)) 13 | .toList(), 14 | ); 15 | } 16 | 17 | Map _$_$_ExchangesResponseToJson( 18 | _$_ExchangesResponse instance) => 19 | { 20 | 'result': instance.result, 21 | }; 22 | -------------------------------------------------------------------------------- /lib/models/graph/graph/graph.dart: -------------------------------------------------------------------------------- 1 | import 'package:freezed_annotation/freezed_annotation.dart'; 2 | 3 | import '../pair_graph/pair_graph.dart'; 4 | 5 | part 'graph.freezed.dart'; 6 | 7 | @freezed 8 | abstract class Graph with _$Graph { 9 | const factory Graph({required List pairs}) = _Graph; 10 | 11 | factory Graph.fromJson(dynamic json) { 12 | List pairs = []; 13 | json.forEach((k, v) { 14 | pairs.add(PairGraph.fromJson(v, k)); 15 | }); 16 | return Graph(pairs: pairs); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /lib/models/graph/graph/graph.freezed.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | // ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides 3 | 4 | part of 'graph.dart'; 5 | 6 | // ************************************************************************** 7 | // FreezedGenerator 8 | // ************************************************************************** 9 | 10 | T _$identity(T value) => value; 11 | 12 | final _privateConstructorUsedError = UnsupportedError( 13 | 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more informations: https://github.com/rrousselGit/freezed#custom-getters-and-methods'); 14 | 15 | /// @nodoc 16 | class _$GraphTearOff { 17 | const _$GraphTearOff(); 18 | 19 | _Graph call({required List pairs}) { 20 | return _Graph( 21 | pairs: pairs, 22 | ); 23 | } 24 | } 25 | 26 | /// @nodoc 27 | const $Graph = _$GraphTearOff(); 28 | 29 | /// @nodoc 30 | mixin _$Graph { 31 | List get pairs => throw _privateConstructorUsedError; 32 | 33 | @JsonKey(ignore: true) 34 | $GraphCopyWith get copyWith => throw _privateConstructorUsedError; 35 | } 36 | 37 | /// @nodoc 38 | abstract class $GraphCopyWith<$Res> { 39 | factory $GraphCopyWith(Graph value, $Res Function(Graph) then) = 40 | _$GraphCopyWithImpl<$Res>; 41 | $Res call({List pairs}); 42 | } 43 | 44 | /// @nodoc 45 | class _$GraphCopyWithImpl<$Res> implements $GraphCopyWith<$Res> { 46 | _$GraphCopyWithImpl(this._value, this._then); 47 | 48 | final Graph _value; 49 | // ignore: unused_field 50 | final $Res Function(Graph) _then; 51 | 52 | @override 53 | $Res call({ 54 | Object? pairs = freezed, 55 | }) { 56 | return _then(_value.copyWith( 57 | pairs: pairs == freezed 58 | ? _value.pairs 59 | : pairs // ignore: cast_nullable_to_non_nullable 60 | as List, 61 | )); 62 | } 63 | } 64 | 65 | /// @nodoc 66 | abstract class _$GraphCopyWith<$Res> implements $GraphCopyWith<$Res> { 67 | factory _$GraphCopyWith(_Graph value, $Res Function(_Graph) then) = 68 | __$GraphCopyWithImpl<$Res>; 69 | @override 70 | $Res call({List pairs}); 71 | } 72 | 73 | /// @nodoc 74 | class __$GraphCopyWithImpl<$Res> extends _$GraphCopyWithImpl<$Res> 75 | implements _$GraphCopyWith<$Res> { 76 | __$GraphCopyWithImpl(_Graph _value, $Res Function(_Graph) _then) 77 | : super(_value, (v) => _then(v as _Graph)); 78 | 79 | @override 80 | _Graph get _value => super._value as _Graph; 81 | 82 | @override 83 | $Res call({ 84 | Object? pairs = freezed, 85 | }) { 86 | return _then(_Graph( 87 | pairs: pairs == freezed 88 | ? _value.pairs 89 | : pairs // ignore: cast_nullable_to_non_nullable 90 | as List, 91 | )); 92 | } 93 | } 94 | 95 | /// @nodoc 96 | 97 | class _$_Graph implements _Graph { 98 | const _$_Graph({required this.pairs}); 99 | 100 | @override 101 | final List pairs; 102 | 103 | @override 104 | String toString() { 105 | return 'Graph(pairs: $pairs)'; 106 | } 107 | 108 | @override 109 | bool operator ==(dynamic other) { 110 | return identical(this, other) || 111 | (other is _Graph && 112 | (identical(other.pairs, pairs) || 113 | const DeepCollectionEquality().equals(other.pairs, pairs))); 114 | } 115 | 116 | @override 117 | int get hashCode => 118 | runtimeType.hashCode ^ const DeepCollectionEquality().hash(pairs); 119 | 120 | @JsonKey(ignore: true) 121 | @override 122 | _$GraphCopyWith<_Graph> get copyWith => 123 | __$GraphCopyWithImpl<_Graph>(this, _$identity); 124 | } 125 | 126 | abstract class _Graph implements Graph { 127 | const factory _Graph({required List pairs}) = _$_Graph; 128 | 129 | @override 130 | List get pairs => throw _privateConstructorUsedError; 131 | @override 132 | @JsonKey(ignore: true) 133 | _$GraphCopyWith<_Graph> get copyWith => throw _privateConstructorUsedError; 134 | } 135 | -------------------------------------------------------------------------------- /lib/models/graph/graph_response/graph_response.dart: -------------------------------------------------------------------------------- 1 | import 'package:cryptocurrency_app/models/allowance/allowance.dart'; 2 | import 'package:cryptocurrency_app/models/graph/graph/graph.dart'; 3 | import 'package:freezed_annotation/freezed_annotation.dart'; 4 | 5 | part 'graph_response.freezed.dart'; 6 | 7 | @freezed 8 | abstract class GraphResponse with _$GraphResponse { 9 | const factory GraphResponse( 10 | {required Graph result, required Allowance allowance}) = _GraphResponse; 11 | 12 | factory GraphResponse.fromJson(Map json) { 13 | final result = Graph.fromJson(json['result']); 14 | final allowance = Allowance.fromJson(json['allowance']); 15 | return GraphResponse(result: result, allowance: allowance); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /lib/models/graph/pair_graph/pair_graph.dart: -------------------------------------------------------------------------------- 1 | import 'package:freezed_annotation/freezed_annotation.dart'; 2 | 3 | import '../points/points.dart'; 4 | 5 | part 'pair_graph.freezed.dart'; 6 | 7 | @freezed 8 | abstract class PairGraph with _$PairGraph { 9 | const factory PairGraph( 10 | {required String period, required List points}) = _PairGraph; 11 | 12 | factory PairGraph.fromJson(dynamic json, String period) { 13 | List points = []; 14 | json.forEach((v) { 15 | points.add(Points.fromJson(v)); 16 | }); 17 | return PairGraph(period: period, points: points); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /lib/models/graph/pair_graph/pair_graph.freezed.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | // ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides 3 | 4 | part of 'pair_graph.dart'; 5 | 6 | // ************************************************************************** 7 | // FreezedGenerator 8 | // ************************************************************************** 9 | 10 | T _$identity(T value) => value; 11 | 12 | final _privateConstructorUsedError = UnsupportedError( 13 | 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more informations: https://github.com/rrousselGit/freezed#custom-getters-and-methods'); 14 | 15 | /// @nodoc 16 | class _$PairGraphTearOff { 17 | const _$PairGraphTearOff(); 18 | 19 | _PairGraph call({required String period, required List points}) { 20 | return _PairGraph( 21 | period: period, 22 | points: points, 23 | ); 24 | } 25 | } 26 | 27 | /// @nodoc 28 | const $PairGraph = _$PairGraphTearOff(); 29 | 30 | /// @nodoc 31 | mixin _$PairGraph { 32 | String get period => throw _privateConstructorUsedError; 33 | List get points => throw _privateConstructorUsedError; 34 | 35 | @JsonKey(ignore: true) 36 | $PairGraphCopyWith get copyWith => 37 | throw _privateConstructorUsedError; 38 | } 39 | 40 | /// @nodoc 41 | abstract class $PairGraphCopyWith<$Res> { 42 | factory $PairGraphCopyWith(PairGraph value, $Res Function(PairGraph) then) = 43 | _$PairGraphCopyWithImpl<$Res>; 44 | $Res call({String period, List points}); 45 | } 46 | 47 | /// @nodoc 48 | class _$PairGraphCopyWithImpl<$Res> implements $PairGraphCopyWith<$Res> { 49 | _$PairGraphCopyWithImpl(this._value, this._then); 50 | 51 | final PairGraph _value; 52 | // ignore: unused_field 53 | final $Res Function(PairGraph) _then; 54 | 55 | @override 56 | $Res call({ 57 | Object? period = freezed, 58 | Object? points = freezed, 59 | }) { 60 | return _then(_value.copyWith( 61 | period: period == freezed 62 | ? _value.period 63 | : period // ignore: cast_nullable_to_non_nullable 64 | as String, 65 | points: points == freezed 66 | ? _value.points 67 | : points // ignore: cast_nullable_to_non_nullable 68 | as List, 69 | )); 70 | } 71 | } 72 | 73 | /// @nodoc 74 | abstract class _$PairGraphCopyWith<$Res> implements $PairGraphCopyWith<$Res> { 75 | factory _$PairGraphCopyWith( 76 | _PairGraph value, $Res Function(_PairGraph) then) = 77 | __$PairGraphCopyWithImpl<$Res>; 78 | @override 79 | $Res call({String period, List points}); 80 | } 81 | 82 | /// @nodoc 83 | class __$PairGraphCopyWithImpl<$Res> extends _$PairGraphCopyWithImpl<$Res> 84 | implements _$PairGraphCopyWith<$Res> { 85 | __$PairGraphCopyWithImpl(_PairGraph _value, $Res Function(_PairGraph) _then) 86 | : super(_value, (v) => _then(v as _PairGraph)); 87 | 88 | @override 89 | _PairGraph get _value => super._value as _PairGraph; 90 | 91 | @override 92 | $Res call({ 93 | Object? period = freezed, 94 | Object? points = freezed, 95 | }) { 96 | return _then(_PairGraph( 97 | period: period == freezed 98 | ? _value.period 99 | : period // ignore: cast_nullable_to_non_nullable 100 | as String, 101 | points: points == freezed 102 | ? _value.points 103 | : points // ignore: cast_nullable_to_non_nullable 104 | as List, 105 | )); 106 | } 107 | } 108 | 109 | /// @nodoc 110 | 111 | class _$_PairGraph implements _PairGraph { 112 | const _$_PairGraph({required this.period, required this.points}); 113 | 114 | @override 115 | final String period; 116 | @override 117 | final List points; 118 | 119 | @override 120 | String toString() { 121 | return 'PairGraph(period: $period, points: $points)'; 122 | } 123 | 124 | @override 125 | bool operator ==(dynamic other) { 126 | return identical(this, other) || 127 | (other is _PairGraph && 128 | (identical(other.period, period) || 129 | const DeepCollectionEquality().equals(other.period, period)) && 130 | (identical(other.points, points) || 131 | const DeepCollectionEquality().equals(other.points, points))); 132 | } 133 | 134 | @override 135 | int get hashCode => 136 | runtimeType.hashCode ^ 137 | const DeepCollectionEquality().hash(period) ^ 138 | const DeepCollectionEquality().hash(points); 139 | 140 | @JsonKey(ignore: true) 141 | @override 142 | _$PairGraphCopyWith<_PairGraph> get copyWith => 143 | __$PairGraphCopyWithImpl<_PairGraph>(this, _$identity); 144 | } 145 | 146 | abstract class _PairGraph implements PairGraph { 147 | const factory _PairGraph( 148 | {required String period, required List points}) = _$_PairGraph; 149 | 150 | @override 151 | String get period => throw _privateConstructorUsedError; 152 | @override 153 | List get points => throw _privateConstructorUsedError; 154 | @override 155 | @JsonKey(ignore: true) 156 | _$PairGraphCopyWith<_PairGraph> get copyWith => 157 | throw _privateConstructorUsedError; 158 | } 159 | -------------------------------------------------------------------------------- /lib/models/graph/points/points.dart: -------------------------------------------------------------------------------- 1 | import 'package:freezed_annotation/freezed_annotation.dart'; 2 | 3 | part 'points.freezed.dart'; 4 | 5 | @freezed 6 | abstract class Points with _$Points { 7 | const factory Points( 8 | {required double closeTime, 9 | required double openTime, 10 | required double highPrice, 11 | required double lowPrice, 12 | required double closePrice, 13 | required double volume, 14 | required double quoteVolume}) = _Points; 15 | 16 | factory Points.fromJson(dynamic json) { 17 | return _Points( 18 | closeTime: json[0].toDouble(), 19 | openTime: json[1].toDouble(), 20 | highPrice: json[2].toDouble(), 21 | lowPrice: json[3].toDouble(), 22 | closePrice: json[4].toDouble(), 23 | volume: json[5].toDouble(), 24 | quoteVolume: json[6].toDouble(), 25 | ); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /lib/models/markets/favorite_pair/favorite_pair.dart: -------------------------------------------------------------------------------- 1 | import 'package:cryptocurrency_app/models/markets/pair/pair.dart'; 2 | import 'package:cryptocurrency_app/models/pair/pair_summary/pair_summary.dart'; 3 | import 'package:freezed_annotation/freezed_annotation.dart'; 4 | part 'favorite_pair.freezed.dart'; 5 | part 'favorite_pair.g.dart'; 6 | 7 | @freezed 8 | abstract class FavoritePair with _$FavoritePair { 9 | const factory FavoritePair( 10 | {required Pair pair, required PairSummary pairSummary}) = _FavoritePair; 11 | 12 | factory FavoritePair.fromJson(Map json) => 13 | _$FavoritePairFromJson(json); 14 | } 15 | -------------------------------------------------------------------------------- /lib/models/markets/favorite_pair/favorite_pair.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'favorite_pair.dart'; 4 | 5 | // ************************************************************************** 6 | // JsonSerializableGenerator 7 | // ************************************************************************** 8 | 9 | _$_FavoritePair _$_$_FavoritePairFromJson(Map json) { 10 | return _$_FavoritePair( 11 | pair: Pair.fromJson(json['pair'] as Map), 12 | pairSummary: 13 | PairSummary.fromJson(json['pairSummary'] as Map), 14 | ); 15 | } 16 | 17 | Map _$_$_FavoritePairToJson(_$_FavoritePair instance) => 18 | { 19 | 'pair': instance.pair, 20 | 'pairSummary': instance.pairSummary, 21 | }; 22 | -------------------------------------------------------------------------------- /lib/models/markets/market_response/market_response.dart: -------------------------------------------------------------------------------- 1 | import 'package:cryptocurrency_app/models/allowance/allowance.dart'; 2 | import 'package:cryptocurrency_app/models/markets/pair/pair.dart'; 3 | import 'package:freezed_annotation/freezed_annotation.dart'; 4 | part 'market_response.g.dart'; 5 | part 'market_response.freezed.dart'; 6 | 7 | @freezed 8 | abstract class MarketResponse with _$MarketResponse { 9 | const factory MarketResponse( 10 | {required List result, 11 | required Allowance allowance}) = _MarketResponse; 12 | factory MarketResponse.fromJson(Map json) => 13 | _$MarketResponseFromJson(json); 14 | } 15 | -------------------------------------------------------------------------------- /lib/models/markets/market_response/market_response.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'market_response.dart'; 4 | 5 | // ************************************************************************** 6 | // JsonSerializableGenerator 7 | // ************************************************************************** 8 | 9 | _$_MarketResponse _$_$_MarketResponseFromJson(Map json) { 10 | return _$_MarketResponse( 11 | result: (json['result'] as List) 12 | .map((e) => Pair.fromJson(e as Map)) 13 | .toList(), 14 | allowance: Allowance.fromJson(json['allowance'] as Map), 15 | ); 16 | } 17 | 18 | Map _$_$_MarketResponseToJson(_$_MarketResponse instance) => 19 | { 20 | 'result': instance.result, 21 | 'allowance': instance.allowance, 22 | }; 23 | -------------------------------------------------------------------------------- /lib/models/markets/pair/pair.dart: -------------------------------------------------------------------------------- 1 | import 'package:freezed_annotation/freezed_annotation.dart'; 2 | part 'pair.g.dart'; 3 | part 'pair.freezed.dart'; 4 | 5 | @freezed 6 | abstract class Pair with _$Pair { 7 | const factory Pair({ 8 | int? id, 9 | required String exchange, 10 | required String pair, 11 | bool? active, 12 | String? route, 13 | }) = _Pair; 14 | 15 | factory Pair.fromJson(Map json) => _$PairFromJson(json); 16 | } 17 | -------------------------------------------------------------------------------- /lib/models/markets/pair/pair.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'pair.dart'; 4 | 5 | // ************************************************************************** 6 | // JsonSerializableGenerator 7 | // ************************************************************************** 8 | 9 | _$_Pair _$_$_PairFromJson(Map json) { 10 | return _$_Pair( 11 | id: json['id'] as int?, 12 | exchange: json['exchange'] as String, 13 | pair: json['pair'] as String, 14 | active: json['active'] as bool?, 15 | route: json['route'] as String?, 16 | ); 17 | } 18 | 19 | Map _$_$_PairToJson(_$_Pair instance) => { 20 | 'id': instance.id, 21 | 'exchange': instance.exchange, 22 | 'pair': instance.pair, 23 | 'active': instance.active, 24 | 'route': instance.route, 25 | }; 26 | -------------------------------------------------------------------------------- /lib/models/orderbook/orderbook/orderbook.dart: -------------------------------------------------------------------------------- 1 | import 'package:cryptocurrency_app/models/orderbook/price/price.dart'; 2 | import 'package:freezed_annotation/freezed_annotation.dart'; 3 | part 'orderbook.freezed.dart'; 4 | 5 | @freezed 6 | abstract class OrderBook with _$OrderBook { 7 | const factory OrderBook( 8 | List asks, 9 | List bids, 10 | int seqNum, 11 | ) = _OrderBook; 12 | 13 | factory OrderBook.fromJson(Map json) { 14 | List asks = []; 15 | json['asks'].forEach((v) { 16 | asks.add(Price( 17 | price: double.parse(v[0].toString()), 18 | amount: double.parse(v[1].toString()))); 19 | }); 20 | 21 | List bids = []; 22 | json['bids'].forEach((v) { 23 | bids.add( 24 | Price( 25 | price: double.parse(v[0].toString()), 26 | amount: double.parse( 27 | v[1].toString(), 28 | ), 29 | ), 30 | ); 31 | }); 32 | final seqNum = json['seqNum']; 33 | return OrderBook(asks, bids, seqNum); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /lib/models/orderbook/orderbook_response/orderbook_response.dart: -------------------------------------------------------------------------------- 1 | import 'package:cryptocurrency_app/models/orderbook/orderbook/orderbook.dart'; 2 | import 'package:freezed_annotation/freezed_annotation.dart'; 3 | part 'orderbook_response.freezed.dart'; 4 | 5 | @freezed 6 | abstract class OrderBookResponse with _$OrderBookResponse { 7 | const factory OrderBookResponse(OrderBook result) = _OrderBookResponse; 8 | 9 | factory OrderBookResponse.fromJson(Map json) { 10 | final result = new OrderBook.fromJson(json['result']); 11 | return OrderBookResponse(result); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /lib/models/orderbook/orderbook_response/orderbook_response.freezed.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | // ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides 3 | 4 | part of 'orderbook_response.dart'; 5 | 6 | // ************************************************************************** 7 | // FreezedGenerator 8 | // ************************************************************************** 9 | 10 | T _$identity(T value) => value; 11 | 12 | final _privateConstructorUsedError = UnsupportedError( 13 | 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more informations: https://github.com/rrousselGit/freezed#custom-getters-and-methods'); 14 | 15 | /// @nodoc 16 | class _$OrderBookResponseTearOff { 17 | const _$OrderBookResponseTearOff(); 18 | 19 | _OrderBookResponse call(OrderBook result) { 20 | return _OrderBookResponse( 21 | result, 22 | ); 23 | } 24 | } 25 | 26 | /// @nodoc 27 | const $OrderBookResponse = _$OrderBookResponseTearOff(); 28 | 29 | /// @nodoc 30 | mixin _$OrderBookResponse { 31 | OrderBook get result => throw _privateConstructorUsedError; 32 | 33 | @JsonKey(ignore: true) 34 | $OrderBookResponseCopyWith get copyWith => 35 | throw _privateConstructorUsedError; 36 | } 37 | 38 | /// @nodoc 39 | abstract class $OrderBookResponseCopyWith<$Res> { 40 | factory $OrderBookResponseCopyWith( 41 | OrderBookResponse value, $Res Function(OrderBookResponse) then) = 42 | _$OrderBookResponseCopyWithImpl<$Res>; 43 | $Res call({OrderBook result}); 44 | 45 | $OrderBookCopyWith<$Res> get result; 46 | } 47 | 48 | /// @nodoc 49 | class _$OrderBookResponseCopyWithImpl<$Res> 50 | implements $OrderBookResponseCopyWith<$Res> { 51 | _$OrderBookResponseCopyWithImpl(this._value, this._then); 52 | 53 | final OrderBookResponse _value; 54 | // ignore: unused_field 55 | final $Res Function(OrderBookResponse) _then; 56 | 57 | @override 58 | $Res call({ 59 | Object? result = freezed, 60 | }) { 61 | return _then(_value.copyWith( 62 | result: result == freezed 63 | ? _value.result 64 | : result // ignore: cast_nullable_to_non_nullable 65 | as OrderBook, 66 | )); 67 | } 68 | 69 | @override 70 | $OrderBookCopyWith<$Res> get result { 71 | return $OrderBookCopyWith<$Res>(_value.result, (value) { 72 | return _then(_value.copyWith(result: value)); 73 | }); 74 | } 75 | } 76 | 77 | /// @nodoc 78 | abstract class _$OrderBookResponseCopyWith<$Res> 79 | implements $OrderBookResponseCopyWith<$Res> { 80 | factory _$OrderBookResponseCopyWith( 81 | _OrderBookResponse value, $Res Function(_OrderBookResponse) then) = 82 | __$OrderBookResponseCopyWithImpl<$Res>; 83 | @override 84 | $Res call({OrderBook result}); 85 | 86 | @override 87 | $OrderBookCopyWith<$Res> get result; 88 | } 89 | 90 | /// @nodoc 91 | class __$OrderBookResponseCopyWithImpl<$Res> 92 | extends _$OrderBookResponseCopyWithImpl<$Res> 93 | implements _$OrderBookResponseCopyWith<$Res> { 94 | __$OrderBookResponseCopyWithImpl( 95 | _OrderBookResponse _value, $Res Function(_OrderBookResponse) _then) 96 | : super(_value, (v) => _then(v as _OrderBookResponse)); 97 | 98 | @override 99 | _OrderBookResponse get _value => super._value as _OrderBookResponse; 100 | 101 | @override 102 | $Res call({ 103 | Object? result = freezed, 104 | }) { 105 | return _then(_OrderBookResponse( 106 | result == freezed 107 | ? _value.result 108 | : result // ignore: cast_nullable_to_non_nullable 109 | as OrderBook, 110 | )); 111 | } 112 | } 113 | 114 | /// @nodoc 115 | 116 | class _$_OrderBookResponse implements _OrderBookResponse { 117 | const _$_OrderBookResponse(this.result); 118 | 119 | @override 120 | final OrderBook result; 121 | 122 | @override 123 | String toString() { 124 | return 'OrderBookResponse(result: $result)'; 125 | } 126 | 127 | @override 128 | bool operator ==(dynamic other) { 129 | return identical(this, other) || 130 | (other is _OrderBookResponse && 131 | (identical(other.result, result) || 132 | const DeepCollectionEquality().equals(other.result, result))); 133 | } 134 | 135 | @override 136 | int get hashCode => 137 | runtimeType.hashCode ^ const DeepCollectionEquality().hash(result); 138 | 139 | @JsonKey(ignore: true) 140 | @override 141 | _$OrderBookResponseCopyWith<_OrderBookResponse> get copyWith => 142 | __$OrderBookResponseCopyWithImpl<_OrderBookResponse>(this, _$identity); 143 | } 144 | 145 | abstract class _OrderBookResponse implements OrderBookResponse { 146 | const factory _OrderBookResponse(OrderBook result) = _$_OrderBookResponse; 147 | 148 | @override 149 | OrderBook get result => throw _privateConstructorUsedError; 150 | @override 151 | @JsonKey(ignore: true) 152 | _$OrderBookResponseCopyWith<_OrderBookResponse> get copyWith => 153 | throw _privateConstructorUsedError; 154 | } 155 | -------------------------------------------------------------------------------- /lib/models/orderbook/price/price.dart: -------------------------------------------------------------------------------- 1 | import 'package:freezed_annotation/freezed_annotation.dart'; 2 | part 'price.freezed.dart'; 3 | 4 | @freezed 5 | abstract class Price with _$Price { 6 | const factory Price({required double price, required double amount}) = _Price; 7 | } 8 | -------------------------------------------------------------------------------- /lib/models/orderbook/price/price.freezed.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | // ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides 3 | 4 | part of 'price.dart'; 5 | 6 | // ************************************************************************** 7 | // FreezedGenerator 8 | // ************************************************************************** 9 | 10 | T _$identity(T value) => value; 11 | 12 | final _privateConstructorUsedError = UnsupportedError( 13 | 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more informations: https://github.com/rrousselGit/freezed#custom-getters-and-methods'); 14 | 15 | /// @nodoc 16 | class _$PriceTearOff { 17 | const _$PriceTearOff(); 18 | 19 | _Price call({required double price, required double amount}) { 20 | return _Price( 21 | price: price, 22 | amount: amount, 23 | ); 24 | } 25 | } 26 | 27 | /// @nodoc 28 | const $Price = _$PriceTearOff(); 29 | 30 | /// @nodoc 31 | mixin _$Price { 32 | double get price => throw _privateConstructorUsedError; 33 | double get amount => throw _privateConstructorUsedError; 34 | 35 | @JsonKey(ignore: true) 36 | $PriceCopyWith get copyWith => throw _privateConstructorUsedError; 37 | } 38 | 39 | /// @nodoc 40 | abstract class $PriceCopyWith<$Res> { 41 | factory $PriceCopyWith(Price value, $Res Function(Price) then) = 42 | _$PriceCopyWithImpl<$Res>; 43 | $Res call({double price, double amount}); 44 | } 45 | 46 | /// @nodoc 47 | class _$PriceCopyWithImpl<$Res> implements $PriceCopyWith<$Res> { 48 | _$PriceCopyWithImpl(this._value, this._then); 49 | 50 | final Price _value; 51 | // ignore: unused_field 52 | final $Res Function(Price) _then; 53 | 54 | @override 55 | $Res call({ 56 | Object? price = freezed, 57 | Object? amount = freezed, 58 | }) { 59 | return _then(_value.copyWith( 60 | price: price == freezed 61 | ? _value.price 62 | : price // ignore: cast_nullable_to_non_nullable 63 | as double, 64 | amount: amount == freezed 65 | ? _value.amount 66 | : amount // ignore: cast_nullable_to_non_nullable 67 | as double, 68 | )); 69 | } 70 | } 71 | 72 | /// @nodoc 73 | abstract class _$PriceCopyWith<$Res> implements $PriceCopyWith<$Res> { 74 | factory _$PriceCopyWith(_Price value, $Res Function(_Price) then) = 75 | __$PriceCopyWithImpl<$Res>; 76 | @override 77 | $Res call({double price, double amount}); 78 | } 79 | 80 | /// @nodoc 81 | class __$PriceCopyWithImpl<$Res> extends _$PriceCopyWithImpl<$Res> 82 | implements _$PriceCopyWith<$Res> { 83 | __$PriceCopyWithImpl(_Price _value, $Res Function(_Price) _then) 84 | : super(_value, (v) => _then(v as _Price)); 85 | 86 | @override 87 | _Price get _value => super._value as _Price; 88 | 89 | @override 90 | $Res call({ 91 | Object? price = freezed, 92 | Object? amount = freezed, 93 | }) { 94 | return _then(_Price( 95 | price: price == freezed 96 | ? _value.price 97 | : price // ignore: cast_nullable_to_non_nullable 98 | as double, 99 | amount: amount == freezed 100 | ? _value.amount 101 | : amount // ignore: cast_nullable_to_non_nullable 102 | as double, 103 | )); 104 | } 105 | } 106 | 107 | /// @nodoc 108 | 109 | class _$_Price implements _Price { 110 | const _$_Price({required this.price, required this.amount}); 111 | 112 | @override 113 | final double price; 114 | @override 115 | final double amount; 116 | 117 | @override 118 | String toString() { 119 | return 'Price(price: $price, amount: $amount)'; 120 | } 121 | 122 | @override 123 | bool operator ==(dynamic other) { 124 | return identical(this, other) || 125 | (other is _Price && 126 | (identical(other.price, price) || 127 | const DeepCollectionEquality().equals(other.price, price)) && 128 | (identical(other.amount, amount) || 129 | const DeepCollectionEquality().equals(other.amount, amount))); 130 | } 131 | 132 | @override 133 | int get hashCode => 134 | runtimeType.hashCode ^ 135 | const DeepCollectionEquality().hash(price) ^ 136 | const DeepCollectionEquality().hash(amount); 137 | 138 | @JsonKey(ignore: true) 139 | @override 140 | _$PriceCopyWith<_Price> get copyWith => 141 | __$PriceCopyWithImpl<_Price>(this, _$identity); 142 | } 143 | 144 | abstract class _Price implements Price { 145 | const factory _Price({required double price, required double amount}) = 146 | _$_Price; 147 | 148 | @override 149 | double get price => throw _privateConstructorUsedError; 150 | @override 151 | double get amount => throw _privateConstructorUsedError; 152 | @override 153 | @JsonKey(ignore: true) 154 | _$PriceCopyWith<_Price> get copyWith => throw _privateConstructorUsedError; 155 | } 156 | -------------------------------------------------------------------------------- /lib/models/pair/change/change.dart: -------------------------------------------------------------------------------- 1 | import 'package:freezed_annotation/freezed_annotation.dart'; 2 | part 'change.freezed.dart'; 3 | part 'change.g.dart'; 4 | 5 | @freezed 6 | abstract class Change with _$Change { 7 | const factory Change({required double percentage, required double absolute}) = 8 | _Change; 9 | 10 | factory Change.fromJson(Map json) => _$ChangeFromJson(json); 11 | } 12 | -------------------------------------------------------------------------------- /lib/models/pair/change/change.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'change.dart'; 4 | 5 | // ************************************************************************** 6 | // JsonSerializableGenerator 7 | // ************************************************************************** 8 | 9 | _$_Change _$_$_ChangeFromJson(Map json) { 10 | return _$_Change( 11 | percentage: (json['percentage'] as num).toDouble(), 12 | absolute: (json['absolute'] as num).toDouble(), 13 | ); 14 | } 15 | 16 | Map _$_$_ChangeToJson(_$_Change instance) => { 17 | 'percentage': instance.percentage, 18 | 'absolute': instance.absolute, 19 | }; 20 | -------------------------------------------------------------------------------- /lib/models/pair/pair_response/pair_response.dart: -------------------------------------------------------------------------------- 1 | import 'package:cryptocurrency_app/models/allowance/allowance.dart'; 2 | import 'package:cryptocurrency_app/models/pair/pair_summary/pair_summary.dart'; 3 | import 'package:freezed_annotation/freezed_annotation.dart'; 4 | 5 | part 'pair_response.freezed.dart'; 6 | part 'pair_response.g.dart'; 7 | 8 | @freezed 9 | abstract class PairResponse with _$PairResponse { 10 | const factory PairResponse(PairSummary result, Allowance allowance) = 11 | _PairResponse; 12 | 13 | factory PairResponse.fromJson(Map json) => 14 | _$PairResponseFromJson(json); 15 | } 16 | -------------------------------------------------------------------------------- /lib/models/pair/pair_response/pair_response.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'pair_response.dart'; 4 | 5 | // ************************************************************************** 6 | // JsonSerializableGenerator 7 | // ************************************************************************** 8 | 9 | _$_PairResponse _$_$_PairResponseFromJson(Map json) { 10 | return _$_PairResponse( 11 | PairSummary.fromJson(json['result'] as Map), 12 | Allowance.fromJson(json['allowance'] as Map), 13 | ); 14 | } 15 | 16 | Map _$_$_PairResponseToJson(_$_PairResponse instance) => 17 | { 18 | 'result': instance.result, 19 | 'allowance': instance.allowance, 20 | }; 21 | -------------------------------------------------------------------------------- /lib/models/pair/pair_summary/pair_summary.dart: -------------------------------------------------------------------------------- 1 | import 'package:cryptocurrency_app/models/pair/price/price.dart'; 2 | import 'package:freezed_annotation/freezed_annotation.dart'; 3 | 4 | part 'pair_summary.freezed.dart'; 5 | part 'pair_summary.g.dart'; 6 | 7 | @freezed 8 | abstract class PairSummary with _$PairSummary { 9 | const factory PairSummary({ 10 | required Price price, 11 | required double volume, 12 | required double volumeQuote, 13 | }) = _PairSummary; 14 | 15 | factory PairSummary.fromJson(Map json) => 16 | _$PairSummaryFromJson(json); 17 | } 18 | -------------------------------------------------------------------------------- /lib/models/pair/pair_summary/pair_summary.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'pair_summary.dart'; 4 | 5 | // ************************************************************************** 6 | // JsonSerializableGenerator 7 | // ************************************************************************** 8 | 9 | _$_PairSummary _$_$_PairSummaryFromJson(Map json) { 10 | return _$_PairSummary( 11 | price: Price.fromJson(json['price'] as Map), 12 | volume: (json['volume'] as num).toDouble(), 13 | volumeQuote: (json['volumeQuote'] as num).toDouble(), 14 | ); 15 | } 16 | 17 | Map _$_$_PairSummaryToJson(_$_PairSummary instance) => 18 | { 19 | 'price': instance.price, 20 | 'volume': instance.volume, 21 | 'volumeQuote': instance.volumeQuote, 22 | }; 23 | -------------------------------------------------------------------------------- /lib/models/pair/price/price.dart: -------------------------------------------------------------------------------- 1 | import 'package:cryptocurrency_app/models/pair/change/change.dart'; 2 | import 'package:freezed_annotation/freezed_annotation.dart'; 3 | part 'price.freezed.dart'; 4 | part 'price.g.dart'; 5 | 6 | @freezed 7 | abstract class Price with _$Price { 8 | const factory Price({ 9 | required double last, 10 | required double high, 11 | required double low, 12 | required Change change, 13 | }) = _Price; 14 | 15 | factory Price.fromJson(Map json) => _$PriceFromJson(json); 16 | } 17 | -------------------------------------------------------------------------------- /lib/models/pair/price/price.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'price.dart'; 4 | 5 | // ************************************************************************** 6 | // JsonSerializableGenerator 7 | // ************************************************************************** 8 | 9 | _$_Price _$_$_PriceFromJson(Map json) { 10 | return _$_Price( 11 | last: (json['last'] as num).toDouble(), 12 | high: (json['high'] as num).toDouble(), 13 | low: (json['low'] as num).toDouble(), 14 | change: Change.fromJson(json['change'] as Map), 15 | ); 16 | } 17 | 18 | Map _$_$_PriceToJson(_$_Price instance) => { 19 | 'last': instance.last, 20 | 'high': instance.high, 21 | 'low': instance.low, 22 | 'change': instance.change, 23 | }; 24 | -------------------------------------------------------------------------------- /lib/models/settings/settings_details/settings_details.dart: -------------------------------------------------------------------------------- 1 | import 'package:freezed_annotation/freezed_annotation.dart'; 2 | 3 | part 'settings_details.freezed.dart'; 4 | 5 | @freezed 6 | abstract class SettingsDetails with _$SettingsDetails { 7 | const factory SettingsDetails({ 8 | required String currentLanguage, 9 | required String favoriteExchange, 10 | required String favoritePair, 11 | required String themeMode, 12 | }) = _SettingsDetails; 13 | } 14 | -------------------------------------------------------------------------------- /lib/models/settings/settings_state/settings_state.dart: -------------------------------------------------------------------------------- 1 | import 'package:cryptocurrency_app/models/settings/settings_details/settings_details.dart'; 2 | import 'package:freezed_annotation/freezed_annotation.dart'; 3 | 4 | part 'settings_state.freezed.dart'; 5 | 6 | @freezed 7 | abstract class SettingsState with _$SettingsState { 8 | const factory SettingsState.initial() = _SettingsStateInitial; 9 | const factory SettingsState.loading() = _SettingsStateLoading; 10 | const factory SettingsState.data({required SettingsDetails details}) = 11 | _SetttingsStateData; 12 | const factory SettingsState.error({String? error}) = _SettingsStateError; 13 | } 14 | -------------------------------------------------------------------------------- /lib/models/trades/trade/trade.dart: -------------------------------------------------------------------------------- 1 | import 'package:freezed_annotation/freezed_annotation.dart'; 2 | part 'trade.freezed.dart'; 3 | 4 | @freezed 5 | abstract class Trade with _$Trade { 6 | const factory Trade( 7 | String id, String timestamp, String price, String amount) = _Trade; 8 | 9 | factory Trade.fromJson(List json) { 10 | final id = json[0].toString(); 11 | final timestamp = json[1].toString(); 12 | final price = json[2].toString(); 13 | final amount = json[3].toString(); 14 | return Trade(id, timestamp, price, amount); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /lib/models/trades/trades_response.dart/trades_response.dart: -------------------------------------------------------------------------------- 1 | import 'package:cryptocurrency_app/models/trades/trade/trade.dart'; 2 | import 'package:freezed_annotation/freezed_annotation.dart'; 3 | part 'trades_response.freezed.dart'; 4 | 5 | @freezed 6 | abstract class TradesResponse with _$TradesResponse { 7 | const factory TradesResponse({List? result}) = _TradesResponse; 8 | factory TradesResponse.fromJson(Map json) { 9 | final List result = []; 10 | json['result'].forEach((v) { 11 | result.add(new Trade.fromJson(v)); 12 | }); 13 | return TradesResponse(result: result); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /lib/models/trades/trades_response.dart/trades_response.freezed.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | // ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides 3 | 4 | part of 'trades_response.dart'; 5 | 6 | // ************************************************************************** 7 | // FreezedGenerator 8 | // ************************************************************************** 9 | 10 | T _$identity(T value) => value; 11 | 12 | final _privateConstructorUsedError = UnsupportedError( 13 | 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more informations: https://github.com/rrousselGit/freezed#custom-getters-and-methods'); 14 | 15 | /// @nodoc 16 | class _$TradesResponseTearOff { 17 | const _$TradesResponseTearOff(); 18 | 19 | _TradesResponse call({List? result}) { 20 | return _TradesResponse( 21 | result: result, 22 | ); 23 | } 24 | } 25 | 26 | /// @nodoc 27 | const $TradesResponse = _$TradesResponseTearOff(); 28 | 29 | /// @nodoc 30 | mixin _$TradesResponse { 31 | List? get result => throw _privateConstructorUsedError; 32 | 33 | @JsonKey(ignore: true) 34 | $TradesResponseCopyWith get copyWith => 35 | throw _privateConstructorUsedError; 36 | } 37 | 38 | /// @nodoc 39 | abstract class $TradesResponseCopyWith<$Res> { 40 | factory $TradesResponseCopyWith( 41 | TradesResponse value, $Res Function(TradesResponse) then) = 42 | _$TradesResponseCopyWithImpl<$Res>; 43 | $Res call({List? result}); 44 | } 45 | 46 | /// @nodoc 47 | class _$TradesResponseCopyWithImpl<$Res> 48 | implements $TradesResponseCopyWith<$Res> { 49 | _$TradesResponseCopyWithImpl(this._value, this._then); 50 | 51 | final TradesResponse _value; 52 | // ignore: unused_field 53 | final $Res Function(TradesResponse) _then; 54 | 55 | @override 56 | $Res call({ 57 | Object? result = freezed, 58 | }) { 59 | return _then(_value.copyWith( 60 | result: result == freezed 61 | ? _value.result 62 | : result // ignore: cast_nullable_to_non_nullable 63 | as List?, 64 | )); 65 | } 66 | } 67 | 68 | /// @nodoc 69 | abstract class _$TradesResponseCopyWith<$Res> 70 | implements $TradesResponseCopyWith<$Res> { 71 | factory _$TradesResponseCopyWith( 72 | _TradesResponse value, $Res Function(_TradesResponse) then) = 73 | __$TradesResponseCopyWithImpl<$Res>; 74 | @override 75 | $Res call({List? result}); 76 | } 77 | 78 | /// @nodoc 79 | class __$TradesResponseCopyWithImpl<$Res> 80 | extends _$TradesResponseCopyWithImpl<$Res> 81 | implements _$TradesResponseCopyWith<$Res> { 82 | __$TradesResponseCopyWithImpl( 83 | _TradesResponse _value, $Res Function(_TradesResponse) _then) 84 | : super(_value, (v) => _then(v as _TradesResponse)); 85 | 86 | @override 87 | _TradesResponse get _value => super._value as _TradesResponse; 88 | 89 | @override 90 | $Res call({ 91 | Object? result = freezed, 92 | }) { 93 | return _then(_TradesResponse( 94 | result: result == freezed 95 | ? _value.result 96 | : result // ignore: cast_nullable_to_non_nullable 97 | as List?, 98 | )); 99 | } 100 | } 101 | 102 | /// @nodoc 103 | 104 | class _$_TradesResponse implements _TradesResponse { 105 | const _$_TradesResponse({this.result}); 106 | 107 | @override 108 | final List? result; 109 | 110 | @override 111 | String toString() { 112 | return 'TradesResponse(result: $result)'; 113 | } 114 | 115 | @override 116 | bool operator ==(dynamic other) { 117 | return identical(this, other) || 118 | (other is _TradesResponse && 119 | (identical(other.result, result) || 120 | const DeepCollectionEquality().equals(other.result, result))); 121 | } 122 | 123 | @override 124 | int get hashCode => 125 | runtimeType.hashCode ^ const DeepCollectionEquality().hash(result); 126 | 127 | @JsonKey(ignore: true) 128 | @override 129 | _$TradesResponseCopyWith<_TradesResponse> get copyWith => 130 | __$TradesResponseCopyWithImpl<_TradesResponse>(this, _$identity); 131 | } 132 | 133 | abstract class _TradesResponse implements TradesResponse { 134 | const factory _TradesResponse({List? result}) = _$_TradesResponse; 135 | 136 | @override 137 | List? get result => throw _privateConstructorUsedError; 138 | @override 139 | @JsonKey(ignore: true) 140 | _$TradesResponseCopyWith<_TradesResponse> get copyWith => 141 | throw _privateConstructorUsedError; 142 | } 143 | -------------------------------------------------------------------------------- /lib/provider/crypto_provider.dart: -------------------------------------------------------------------------------- 1 | import 'package:cryptocurrency_app/constants/exceptions.dart'; 2 | import 'package:cryptocurrency_app/provider/time_provider.dart'; 3 | import 'package:cryptocurrency_app/models/exchanges/exchange/exchange.dart'; 4 | import 'package:cryptocurrency_app/models/graph/graph/graph.dart'; 5 | import 'package:cryptocurrency_app/models/markets/favorite_pair/favorite_pair.dart'; 6 | import 'package:cryptocurrency_app/models/markets/pair/pair.dart'; 7 | import 'package:cryptocurrency_app/models/orderbook/orderbook/orderbook.dart'; 8 | import 'package:cryptocurrency_app/models/pair/pair_summary/pair_summary.dart'; 9 | import 'package:cryptocurrency_app/models/trades/trade/trade.dart'; 10 | import 'package:cryptocurrency_app/provider/settings_provider.dart'; 11 | import 'package:cryptocurrency_app/repository/crypto_repository.dart'; 12 | import 'package:dio/dio.dart'; 13 | import 'package:hooks_riverpod/hooks_riverpod.dart'; 14 | import '../../generated/locale_keys.g.dart'; 15 | 16 | final pairsProvider = FutureProvider>((ref) async { 17 | final settings = ref.watch(cryptoSettings); 18 | String exchangeName = settings.maybeWhen( 19 | data: (details) => details.favoriteExchange, 20 | orElse: () => 21 | throw DataException(message: LocaleKeys.errorSomethingWentWrong)); 22 | List pairs = await ref.read(cryptoRepository).getPairs(exchangeName); 23 | return pairs; 24 | }); 25 | 26 | final searchTextProvider = StateProvider((ref) => ""); 27 | 28 | final pairsSearchProvider = FutureProvider>((ref) async { 29 | final pairs = ref.watch(pairsProvider); 30 | final search = ref.watch(searchTextProvider); 31 | 32 | List list = []; 33 | pairs.maybeWhen( 34 | data: (data) { 35 | if (search.isNotEmpty) 36 | list = 37 | data.where((element) => element.pair.contains(search)).toList(); 38 | else 39 | list = data; 40 | }, 41 | orElse: () => {}); 42 | return list; 43 | }); 44 | 45 | final exchangesProvider = FutureProvider>((ref) async { 46 | final cancelToken = CancelToken(); 47 | ref.onDispose(() => cancelToken.cancel()); 48 | 49 | List exchanges = 50 | await ref.read(cryptoRepository).getExchanges(cancelToken: cancelToken); 51 | return exchanges; 52 | }); 53 | 54 | final favoritePairProvider = FutureProvider((ref) async { 55 | final cancelToken = CancelToken(); 56 | ref.onDispose(() => cancelToken.cancel()); 57 | 58 | final settings = ref.watch(cryptoSettings); 59 | String exchangeName = settings.maybeWhen( 60 | data: (details) => details.favoriteExchange, orElse: () => ""); 61 | String pairName = settings.maybeWhen( 62 | data: (details) => details.favoritePair, orElse: () => ""); 63 | 64 | if (exchangeName.isEmpty || pairName.isEmpty) { 65 | throw DataException(message: LocaleKeys.errorSomethingWentWrong); 66 | } 67 | 68 | Pair pair = Pair(pair: pairName, exchange: exchangeName); 69 | try { 70 | PairSummary pairSummary = await ref 71 | .read(cryptoRepository) 72 | .getPairSummary(exchangeName, pairName, cancelToken: cancelToken); 73 | return FavoritePair(pair: pair, pairSummary: pairSummary); 74 | } on DataException catch (error) { 75 | if (error.message == LocaleKeys.errorRequestNotFound) { 76 | ref.read(cryptoSettings.notifier).verifyFavoritePair(); 77 | } 78 | throw error; 79 | } 80 | }); 81 | 82 | final pairSummaryProvider = 83 | FutureProvider.family((ref, pair) async { 84 | final cancelToken = CancelToken(); 85 | ref.onDispose(() => cancelToken.cancel()); 86 | 87 | final pairSummary = await ref 88 | .read(cryptoRepository) 89 | .getPairSummary(pair.exchange, pair.pair, cancelToken: cancelToken); 90 | return pairSummary; 91 | }); 92 | 93 | final pairOrderBookProvider = 94 | FutureProvider.family((ref, pair) async { 95 | final cancelToken = CancelToken(); 96 | ref.onDispose(() => cancelToken.cancel()); 97 | 98 | final orderBook = await ref 99 | .read(cryptoRepository) 100 | .getOrderBook(pair.exchange, pair.pair, cancelToken: cancelToken); 101 | 102 | return orderBook; 103 | }); 104 | 105 | final tradesProvider = 106 | FutureProvider.family, Pair>((ref, pair) async { 107 | final cancelToken = CancelToken(); 108 | ref.onDispose(() => cancelToken.cancel()); 109 | 110 | final trades = await ref 111 | .read(cryptoRepository) 112 | .getTrades(pair.exchange, pair.pair, cancelToken: cancelToken); 113 | return trades; 114 | }); 115 | 116 | final graphDataProvider = FutureProvider.family((ref, pair) async { 117 | String interval = ref.watch(timeDataProvider).periods; 118 | String fromHours = ref.watch(timeDataProvider).before; 119 | String before = ""; 120 | if (fromHours.isNotEmpty) { 121 | before = (DateTime.now() 122 | .subtract(Duration(hours: int.parse(fromHours))) 123 | .toUtc() 124 | .millisecondsSinceEpoch ~/ 125 | 1000) 126 | .toString(); 127 | } 128 | 129 | final graph = await ref.read(cryptoRepository).getPairGraph( 130 | pair.exchange, pair.pair, 131 | periods: interval, before: before); 132 | return graph; 133 | }); 134 | -------------------------------------------------------------------------------- /lib/provider/navigation_provider.dart: -------------------------------------------------------------------------------- 1 | import 'package:hooks_riverpod/hooks_riverpod.dart'; 2 | 3 | final navigationProvider = StateNotifierProvider( 4 | (ref) => NavigationNotifier()); 5 | 6 | enum NavigationBarEvent { HOME, SEARCH, SETTINGS } 7 | 8 | class NavigationNotifier extends StateNotifier { 9 | NavigationNotifier() : super(defaultPage); 10 | 11 | static const defaultPage = PageModel(NavigationBarEvent.HOME, 0); 12 | 13 | void selectPage(int i) { 14 | switch (i) { 15 | case 0: 16 | state = PageModel(NavigationBarEvent.HOME, i); 17 | break; 18 | case 1: 19 | state = PageModel(NavigationBarEvent.SEARCH, i); 20 | break; 21 | case 2: 22 | state = PageModel(NavigationBarEvent.SETTINGS, i); 23 | break; 24 | } 25 | } 26 | } 27 | 28 | class PageModel { 29 | const PageModel(this.page, this.index); 30 | final NavigationBarEvent page; 31 | final index; 32 | } 33 | -------------------------------------------------------------------------------- /lib/provider/settings_provider.dart: -------------------------------------------------------------------------------- 1 | import 'package:cryptocurrency_app/constants/exceptions.dart'; 2 | import 'package:cryptocurrency_app/constants/utils.dart' as Utils; 3 | import 'package:cryptocurrency_app/models/markets/pair/pair.dart'; 4 | import 'package:cryptocurrency_app/models/settings/settings_details/settings_details.dart'; 5 | import 'package:cryptocurrency_app/models/settings/settings_state/settings_state.dart'; 6 | import 'package:cryptocurrency_app/repository/crypto_repository.dart'; 7 | import 'package:flutter_secure_storage/flutter_secure_storage.dart'; 8 | import 'package:hooks_riverpod/hooks_riverpod.dart'; 9 | import '../../generated/locale_keys.g.dart'; 10 | 11 | final flutterDatabase = 12 | Provider((ref) => FlutterSecureStorage()); 13 | 14 | final cryptoSettings = StateNotifierProvider( 15 | (ref) => SettingsNotifier(ref.read)); 16 | 17 | class SettingsNotifier extends StateNotifier { 18 | final Reader read; 19 | 20 | late SettingsDetails details; 21 | 22 | SettingsNotifier(this.read) : super(SettingsState.initial()) { 23 | loadData(); 24 | } 25 | 26 | void loadData() async { 27 | state = SettingsState.loading(); 28 | final language = (await read(flutterDatabase).read(key: "language")) ?? 29 | Utils.defaultLenguage; 30 | final exchange = (await read(flutterDatabase).read(key: "exchange")) ?? 31 | Utils.defaultExchange; 32 | final pair = 33 | (await read(flutterDatabase).read(key: "pair")) ?? Utils.defaultPair; 34 | final theme = 35 | (await read(flutterDatabase).read(key: "theme")) ?? Utils.defaultTheme; 36 | details = SettingsDetails( 37 | currentLanguage: language, 38 | favoriteExchange: exchange, 39 | favoritePair: pair, 40 | themeMode: theme); 41 | state = SettingsState.data(details: details); 42 | } 43 | 44 | void setLenguage(String language) async { 45 | state = SettingsState.loading(); 46 | await read(flutterDatabase).write(key: "language", value: language); 47 | details = details.copyWith(currentLanguage: language); 48 | state = SettingsState.data(details: details); 49 | } 50 | 51 | Future setFavoriteExchange(String exchange) async { 52 | state = SettingsState.loading(); 53 | await read(flutterDatabase).write(key: "exchange", value: exchange); 54 | details = details.copyWith(favoriteExchange: exchange); 55 | state = SettingsState.data(details: details); 56 | verifyFavoritePair(); 57 | } 58 | 59 | Future verifyFavoritePair() async { 60 | try { 61 | await read(cryptoRepository) 62 | .getPairSummary(details.favoriteExchange, details.favoritePair); 63 | } on DataException catch (error) { 64 | if (error.message == LocaleKeys.errorRequestNotFound) { 65 | List pairs = 66 | await read(cryptoRepository).getPairs(details.favoriteExchange); 67 | setFavoritePair(pairs.first.pair); 68 | } 69 | } 70 | } 71 | 72 | Future setFavoritePair(String pair) async { 73 | state = SettingsState.loading(); 74 | await read(flutterDatabase).write(key: "pair", value: pair); 75 | details = details.copyWith(favoritePair: pair); 76 | state = SettingsState.data(details: details); 77 | } 78 | 79 | void setTheme(String theme) async { 80 | state = SettingsState.loading(); 81 | await read(flutterDatabase).write(key: "theme", value: theme); 82 | details = details.copyWith(themeMode: theme); 83 | state = SettingsState.data(details: details); 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /lib/provider/time_provider.dart: -------------------------------------------------------------------------------- 1 | import 'package:hooks_riverpod/hooks_riverpod.dart'; 2 | 3 | class TimeGraphData { 4 | String name; 5 | String periods; 6 | String before; 7 | TimeGraphData(this.name, this.periods, this.before); 8 | } 9 | 10 | final timeList = [ 11 | TimeGraphData("1H", "60", "1"), 12 | TimeGraphData("1D", "300", "24"), 13 | TimeGraphData("1W", "1800", "168"), 14 | TimeGraphData("1M", "3600", "730"), 15 | TimeGraphData("1Y", "86400", "8760"), 16 | TimeGraphData("ALL", "", "") 17 | ]; 18 | 19 | final timeDataProvider = 20 | StateProvider((ref) => TimeGraphData("1M", "60", "12")); 21 | -------------------------------------------------------------------------------- /lib/repository/crypto_repository.dart: -------------------------------------------------------------------------------- 1 | import 'package:cryptocurrency_app/constants/exceptions.dart'; 2 | import 'package:cryptocurrency_app/models/exchanges/exchange/exchange.dart'; 3 | import 'package:cryptocurrency_app/models/exchanges/exchanges_response/exchanges_response.dart'; 4 | import 'package:cryptocurrency_app/models/graph/graph/graph.dart'; 5 | import 'package:cryptocurrency_app/models/graph/graph_response/graph_response.dart'; 6 | import 'package:cryptocurrency_app/models/markets/market_response/market_response.dart'; 7 | import 'package:cryptocurrency_app/models/markets/pair/pair.dart'; 8 | import 'package:cryptocurrency_app/models/orderbook/orderbook/orderbook.dart'; 9 | import 'package:cryptocurrency_app/models/orderbook/orderbook_response/orderbook_response.dart'; 10 | import 'package:cryptocurrency_app/models/pair/pair_response/pair_response.dart'; 11 | import 'package:cryptocurrency_app/models/pair/pair_summary/pair_summary.dart'; 12 | import 'package:cryptocurrency_app/models/trades/trade/trade.dart'; 13 | import 'package:cryptocurrency_app/models/trades/trades_response.dart/trades_response.dart'; 14 | import 'package:dio/dio.dart'; 15 | import 'package:hooks_riverpod/hooks_riverpod.dart'; 16 | 17 | import 'package:flutter_dotenv/flutter_dotenv.dart'; 18 | 19 | final clientProvider = Provider((ref) => Dio(BaseOptions(headers: { 20 | "X-CW-API-Key": dotenv.env['API_KEY'], 21 | }, baseUrl: 'https://api.cryptowat.ch'))); 22 | 23 | final cryptoRepository = 24 | Provider((ref) => CryptoRepositoryAPI(ref.read)); 25 | 26 | abstract class CryptoRepository { 27 | Future> getPairs(String market); 28 | Future getPairSummary(String makeret, String pair); 29 | Future getPairGraph(String market, String pair, 30 | {String periods, String after, String before}); 31 | Future> getExchanges(); 32 | Future getOrderBook(String market, String pair); 33 | Future> getTrades(String market, String pair); 34 | } 35 | 36 | class CryptoRepositoryAPI implements CryptoRepository { 37 | final Reader read; 38 | CryptoRepositoryAPI(this.read); 39 | 40 | @override 41 | Future> getPairs(String market, {CancelToken? cancelToken}) async { 42 | try { 43 | final response = await read(clientProvider) 44 | .get('/markets/$market', cancelToken: cancelToken); 45 | return MarketResponse.fromJson(response.data).result; 46 | } on DioError catch (error) { 47 | throw DataException.fromDioError(error); 48 | } 49 | } 50 | 51 | @override 52 | Future getPairSummary(String market, String pair, 53 | {CancelToken? cancelToken}) async { 54 | try { 55 | final response = await read(clientProvider) 56 | .get('/markets/$market/$pair/summary', cancelToken: cancelToken); 57 | return PairResponse.fromJson(response.data).result; 58 | } on DioError catch (error) { 59 | throw DataException.fromDioError(error); 60 | } 61 | } 62 | 63 | @override 64 | Future getOrderBook(String market, String pair, 65 | {CancelToken? cancelToken}) async { 66 | try { 67 | final response = await read(clientProvider) 68 | .get('/markets/$market/$pair/orderbook', cancelToken: cancelToken); 69 | return OrderBookResponse.fromJson(response.data).result; 70 | } on DioError catch (error) { 71 | throw DataException.fromDioError(error); 72 | } 73 | } 74 | 75 | @override 76 | Future> getTrades(String market, String pair, 77 | {CancelToken? cancelToken}) async { 78 | try { 79 | final response = await read(clientProvider) 80 | .get('/markets/$market/$pair/trades', cancelToken: cancelToken); 81 | return TradesResponse.fromJson(response.data).result!; 82 | } on DioError catch (error) { 83 | throw DataException.fromDioError(error); 84 | } 85 | } 86 | 87 | @override 88 | Future getPairGraph(String market, String pair, 89 | {String periods = "", 90 | String after = "", 91 | String before = "", 92 | CancelToken? cancelToken}) async { 93 | try { 94 | final response = await read(clientProvider).get( 95 | '/markets/$market/$pair/ohlc', 96 | queryParameters: { 97 | "periods": periods, 98 | "after": after, 99 | "before": before 100 | }, 101 | cancelToken: cancelToken); 102 | return GraphResponse.fromJson(response.data).result; 103 | } on DioError catch (error) { 104 | throw DataException.fromDioError(error); 105 | } 106 | } 107 | 108 | @override 109 | Future> getExchanges({CancelToken? cancelToken}) async { 110 | try { 111 | final response = await read(clientProvider) 112 | .get('/exchanges', cancelToken: cancelToken); 113 | return ExchangesResponse.fromJson(response.data).result; 114 | } on DioError catch (error) { 115 | throw DataException.fromDioError(error); 116 | } 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /lib/ui/home.dart: -------------------------------------------------------------------------------- 1 | import 'package:cryptocurrency_app/constants/keys.dart'; 2 | import 'package:cryptocurrency_app/provider/navigation_provider.dart'; 3 | import 'package:cryptocurrency_app/ui/screens/home.dart'; 4 | import 'package:cryptocurrency_app/ui/screens/search.dart'; 5 | import 'package:cryptocurrency_app/ui/screens/settings.dart'; 6 | import 'package:flutter/material.dart'; 7 | import 'package:hooks_riverpod/hooks_riverpod.dart'; 8 | import 'package:easy_localization/easy_localization.dart'; 9 | 10 | import '../generated/locale_keys.g.dart'; 11 | 12 | class Home extends HookConsumerWidget { 13 | const Home({Key? key}) : super(key: key); 14 | @override 15 | Widget build(BuildContext context, WidgetRef ref) { 16 | final PageModel navigation = ref.watch(navigationProvider); 17 | 18 | return Scaffold( 19 | body: currentScreen(navigation.index), 20 | bottomNavigationBar: BottomNavigationBar( 21 | key: Keys.NAV_BAR, 22 | currentIndex: navigation.index, 23 | onTap: (index) { 24 | ref.read(navigationProvider.notifier).selectPage(index); 25 | }, 26 | items: [ 27 | BottomNavigationBarItem( 28 | label: LocaleKeys.homeTitle.tr(), 29 | icon: Icon( 30 | Icons.home, 31 | key: Keys.NAV_HOME, 32 | ), 33 | ), 34 | BottomNavigationBarItem( 35 | label: LocaleKeys.searchTitle.tr(), 36 | icon: Icon( 37 | Icons.search, 38 | key: Keys.NAV_SEARCH, 39 | ), 40 | ), 41 | BottomNavigationBarItem( 42 | label: LocaleKeys.settingsTitle.tr(), 43 | icon: Icon( 44 | Icons.settings, 45 | key: Keys.NAV_SETTINGS, 46 | ), 47 | ), 48 | ]), 49 | ); 50 | } 51 | 52 | Widget currentScreen(int index) { 53 | switch (index) { 54 | case 0: 55 | return HomeScreen(); 56 | case 1: 57 | return SearchScreen(); 58 | case 2: 59 | return SettingScreen(); 60 | default: 61 | return HomeScreen(); 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /lib/ui/screens/details.dart: -------------------------------------------------------------------------------- 1 | import 'package:cryptocurrency_app/constants/keys.dart'; 2 | import 'package:cryptocurrency_app/constants/utils.dart' as Utils; 3 | import 'package:cryptocurrency_app/models/markets/pair/pair.dart'; 4 | import 'package:cryptocurrency_app/provider/crypto_provider.dart'; 5 | import 'package:cryptocurrency_app/ui/widgets/details/details_widget.dart'; 6 | import 'package:cryptocurrency_app/ui/widgets/details/time_bar_selector.dart'; 7 | import 'package:cryptocurrency_app/ui/widgets/line_chart.dart'; 8 | import 'package:cryptocurrency_app/ui/widgets/title_price.dart'; 9 | import 'package:flutter/material.dart'; 10 | import 'package:hooks_riverpod/hooks_riverpod.dart'; 11 | 12 | class DetailsScreen extends HookConsumerWidget { 13 | final Pair pair; 14 | DetailsScreen({required this.pair}); 15 | 16 | @override 17 | Widget build(BuildContext context, WidgetRef ref) { 18 | final graph = ref.watch(graphDataProvider(pair)); 19 | 20 | return Scaffold( 21 | key: Keys.DETAILS_SCREEN, 22 | appBar: AppBar( 23 | actions: [ 24 | Container( 25 | width: 120, 26 | margin: EdgeInsets.symmetric(vertical: 6, horizontal: 5), 27 | ) 28 | ], 29 | ), 30 | body: Container( 31 | child: SingleChildScrollView( 32 | child: Column( 33 | crossAxisAlignment: CrossAxisAlignment.start, 34 | children: [ 35 | SizedBox( 36 | height: 5, 37 | ), 38 | Container( 39 | padding: EdgeInsets.symmetric(horizontal: 15), 40 | child: TitlePrice(pair: pair)), 41 | SizedBox( 42 | height: 20, 43 | ), 44 | Container( 45 | height: 250, 46 | child: graph.when( 47 | data: (data) => 48 | LineChartWidget(data: Utils.getPoints(data)), 49 | loading: () => LineChartWidget(loading: true), 50 | error: (e, ex) => LineChartWidget(error: true)), 51 | ), 52 | SizedBox( 53 | height: 20, 54 | ), 55 | TimeBarSelector(), 56 | SizedBox( 57 | height: 15, 58 | ), 59 | DetailsWidget(pair: pair), 60 | SizedBox( 61 | height: 30, 62 | ), 63 | ], 64 | ), 65 | ), 66 | ), 67 | ); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /lib/ui/screens/home.dart: -------------------------------------------------------------------------------- 1 | import 'package:cryptocurrency_app/constants/keys.dart'; 2 | import 'package:cryptocurrency_app/provider/crypto_provider.dart'; 3 | import 'package:cryptocurrency_app/ui/widgets/favorite_pair.dart'; 4 | import 'package:cryptocurrency_app/ui/widgets/pair_tile.dart'; 5 | import 'package:flutter/material.dart'; 6 | import 'package:hooks_riverpod/hooks_riverpod.dart'; 7 | import 'package:easy_localization/easy_localization.dart'; 8 | import '../../generated/locale_keys.g.dart'; 9 | 10 | class HomeScreen extends HookConsumerWidget { 11 | @override 12 | Widget build(BuildContext context, WidgetRef ref) { 13 | final pairs = ref.watch(pairsProvider); 14 | final favoritePair = ref.watch(favoritePairProvider); 15 | return Container( 16 | key: Keys.HOME_SCREEN, 17 | child: Column( 18 | children: [ 19 | AppBar( 20 | toolbarHeight: 65, 21 | centerTitle: false, 22 | title: Text( 23 | LocaleKeys.homeTitle.tr(), 24 | style: TextStyle(color: Colors.white, fontSize: 25), 25 | ), 26 | actions: [ 27 | Container( 28 | margin: EdgeInsets.all(10), 29 | decoration: BoxDecoration( 30 | color: Colors.white, 31 | borderRadius: BorderRadius.circular(50)), 32 | width: 45, 33 | child: Icon( 34 | Icons.person_outline, 35 | size: 30, 36 | color: Colors.black, 37 | ), 38 | ), 39 | ], 40 | ), 41 | Expanded( 42 | child: Column( 43 | children: [ 44 | Container( 45 | height: 190, 46 | child: favoritePair.when( 47 | data: (data) { 48 | return FavoritePairWidget(data); 49 | }, 50 | loading: () => Center( 51 | child: CircularProgressIndicator(), 52 | ), 53 | error: (error, e) => Center( 54 | child: Text(error.toString().tr()), 55 | ), 56 | ), 57 | ), 58 | Expanded( 59 | child: pairs.when( 60 | data: (data) { 61 | return Container( 62 | child: ListView.builder( 63 | padding: EdgeInsets.only(top: 0.0), 64 | itemCount: data.length, 65 | itemBuilder: (ctx, int idx) => ProviderScope( 66 | overrides: [ 67 | currentPair.overrideWithValue(data[idx]), 68 | ], 69 | child: const PairTile(), 70 | ), 71 | ), 72 | ); 73 | }, 74 | loading: () => Center( 75 | child: CircularProgressIndicator(), 76 | ), 77 | error: (error, e) => Center( 78 | child: Text(error.toString().tr()), 79 | ), 80 | ), 81 | ) 82 | ], 83 | ), 84 | ), 85 | ], 86 | ), 87 | ); 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /lib/ui/screens/search.dart: -------------------------------------------------------------------------------- 1 | import 'package:cryptocurrency_app/constants/keys.dart'; 2 | import 'package:cryptocurrency_app/provider/crypto_provider.dart'; 3 | import 'package:cryptocurrency_app/ui/widgets/pair_tile.dart'; 4 | import 'package:flutter/material.dart'; 5 | import 'package:hooks_riverpod/hooks_riverpod.dart'; 6 | import 'package:easy_localization/easy_localization.dart'; 7 | import '../../generated/locale_keys.g.dart'; 8 | 9 | class SearchScreen extends HookConsumerWidget { 10 | @override 11 | Widget build(BuildContext context, WidgetRef ref) { 12 | final pairs = ref.watch(pairsSearchProvider); 13 | 14 | return Container( 15 | key: Keys.SEARCH_SCREEN, 16 | child: Column( 17 | children: [ 18 | AppBar( 19 | title: Text(LocaleKeys.searchTitle.tr()), 20 | ), 21 | Expanded( 22 | child: Column( 23 | children: [ 24 | Container( 25 | height: 50, 26 | padding: EdgeInsets.symmetric(horizontal: 15), 27 | decoration: BoxDecoration( 28 | borderRadius: BorderRadius.all(Radius.circular(5)), 29 | color: Theme.of(context).cardColor, 30 | ), 31 | child: TextFormField( 32 | key: Keys.SEARCH_TEXT_FIELD, 33 | initialValue: ref.read(searchTextProvider), 34 | style: TextStyle(color: Colors.white, fontSize: 21), 35 | decoration: new InputDecoration( 36 | prefixIcon: new Icon(Icons.search, 37 | color: Colors.white, size: 32), 38 | hintText: LocaleKeys.searchBar.tr(), 39 | hintStyle: new TextStyle(color: Colors.white), 40 | border: InputBorder.none), 41 | onChanged: (value) => 42 | {ref.read(searchTextProvider.notifier).state = value}, 43 | ), 44 | ), 45 | Expanded( 46 | child: pairs.maybeWhen( 47 | data: (data) { 48 | return Stack( 49 | children: [ 50 | ListView.builder( 51 | padding: EdgeInsets.zero, 52 | itemCount: data.length, 53 | itemBuilder: (ctx, int id) => ProviderScope( 54 | overrides: [ 55 | currentPair.overrideWithValue(data[id]), 56 | ], 57 | child: const PairTile(), 58 | ), 59 | ), 60 | if (data.length == 0) 61 | Center(child: Text(LocaleKeys.noResults.tr())) 62 | ], 63 | ); 64 | }, 65 | orElse: () => Center( 66 | child: CircularProgressIndicator(), 67 | )), 68 | ) 69 | ], 70 | ), 71 | ), 72 | ], 73 | ), 74 | ); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /lib/ui/widgets/details/details_widget.dart: -------------------------------------------------------------------------------- 1 | import 'package:cryptocurrency_app/models/markets/pair/pair.dart'; 2 | import 'package:cryptocurrency_app/provider/crypto_provider.dart'; 3 | import 'package:cryptocurrency_app/ui/widgets/details/summary_section.dart'; 4 | import 'package:flutter/material.dart'; 5 | import 'package:flutter_hooks/flutter_hooks.dart'; 6 | import 'package:hooks_riverpod/hooks_riverpod.dart'; 7 | import 'package:easy_localization/easy_localization.dart'; 8 | import '../../../generated/locale_keys.g.dart'; 9 | 10 | import 'ohlc_section.dart'; 11 | import 'order_book_section.dart'; 12 | import 'trades_section.dart'; 13 | 14 | class DetailsWidget extends HookConsumerWidget { 15 | final Pair pair; 16 | const DetailsWidget({Key? key, required this.pair}) : super(key: key); 17 | 18 | @override 19 | Widget build(BuildContext context, WidgetRef ref) { 20 | final _controller = useTabController(initialLength: 4); 21 | final graph = ref.watch(graphDataProvider(pair)); 22 | final summary = ref.watch(pairSummaryProvider(pair)); 23 | final orderBook = ref.watch(pairOrderBookProvider(pair)); 24 | final trades = ref.watch(tradesProvider(pair)); 25 | 26 | return Container( 27 | child: Column( 28 | children: [ 29 | TabBar( 30 | labelColor: Theme.of(context).focusColor, 31 | unselectedLabelColor: Theme.of(context).unselectedWidgetColor, 32 | unselectedLabelStyle: Theme.of(context).textTheme.headline4, 33 | labelStyle: Theme.of(context).textTheme.headline4, 34 | indicatorWeight: 4, 35 | indicatorSize: TabBarIndicatorSize.label, 36 | indicatorColor: Theme.of(context).focusColor, 37 | isScrollable: true, 38 | controller: _controller, 39 | tabs: [ 40 | Container( 41 | width: 100, 42 | child: Tab( 43 | text: LocaleKeys.summary.tr(), 44 | )), 45 | Tab( 46 | text: LocaleKeys.orderbook.tr(), 47 | ), 48 | Tab(text: LocaleKeys.trades.tr()), 49 | Tab( 50 | text: LocaleKeys.ohlc.tr(), 51 | ), 52 | ], 53 | ), 54 | Container( 55 | height: 300, 56 | child: TabBarView( 57 | controller: _controller, 58 | children: [ 59 | summary.when( 60 | data: (data) => SummarySection(data: data), 61 | loading: () => Center( 62 | child: CircularProgressIndicator(), 63 | ), 64 | error: (error, e) => Center( 65 | child: Text(error.toString().tr()), 66 | )), 67 | orderBook.when( 68 | data: (data) => OrderBookSection(data: data), 69 | loading: () => Center( 70 | child: CircularProgressIndicator(), 71 | ), 72 | error: (error, e) => Center( 73 | child: Text(error.toString().tr()), 74 | )), 75 | trades.when( 76 | data: (data) => TradesSection(data: data), 77 | loading: () => Center( 78 | child: CircularProgressIndicator(), 79 | ), 80 | error: (error, e) => Center( 81 | child: Text(error.toString().tr()), 82 | )), 83 | graph.when( 84 | data: (data) => OHLCSection( 85 | data: data, 86 | ), 87 | loading: () => Center( 88 | child: CircularProgressIndicator(), 89 | ), 90 | error: (error, e) => Center( 91 | child: Text(error.toString().tr()), 92 | )), 93 | ], 94 | ), 95 | ), 96 | ], 97 | ), 98 | ); 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /lib/ui/widgets/details/ohlc_section.dart: -------------------------------------------------------------------------------- 1 | import 'package:cryptocurrency_app/models/graph/graph/graph.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:flutter_candlesticks/flutter_candlesticks.dart'; 4 | 5 | class OHLCSection extends StatelessWidget { 6 | final Graph data; 7 | const OHLCSection({Key? key, required this.data}) : super(key: key); 8 | 9 | @override 10 | Widget build(BuildContext context) { 11 | return Container( 12 | margin: EdgeInsets.symmetric(horizontal: 30, vertical: 40), 13 | child: new OHLCVGraph( 14 | data: data.pairs[0].points 15 | .map((e) => { 16 | "open": e.openTime, 17 | "high": e.highPrice, 18 | "low": e.lowPrice, 19 | "close": e.closePrice, 20 | "volumeto": e.volume 21 | }) 22 | .toList(), 23 | enableGridLines: true, 24 | volumeProp: 0.2, 25 | gridLineAmount: 5, 26 | gridLineColor: Colors.grey[300]!, 27 | gridLineLabelColor: Colors.grey)); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /lib/ui/widgets/details/order_book_section.dart: -------------------------------------------------------------------------------- 1 | import 'package:cryptocurrency_app/models/orderbook/orderbook/orderbook.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:easy_localization/easy_localization.dart'; 4 | import '../../../generated/locale_keys.g.dart'; 5 | 6 | class OrderBookSection extends StatelessWidget { 7 | final OrderBook data; 8 | OrderBookSection({Key? key, required this.data}) : super(key: key); 9 | 10 | @override 11 | Widget build(BuildContext context) { 12 | return Container( 13 | padding: EdgeInsets.symmetric(horizontal: 20, vertical: 10), 14 | child: Column( 15 | mainAxisSize: MainAxisSize.max, 16 | children: [ 17 | Row( 18 | mainAxisAlignment: MainAxisAlignment.spaceAround, 19 | children: [ 20 | Text( 21 | LocaleKeys.bid.tr(), 22 | style: Theme.of(context).textTheme.subtitle2, 23 | ), 24 | Text( 25 | LocaleKeys.ask.tr(), 26 | style: Theme.of(context).textTheme.subtitle2, 27 | ) 28 | ], 29 | ), 30 | SizedBox( 31 | height: 10, 32 | ), 33 | Container( 34 | height: 220, 35 | child: Row( 36 | children: [ 37 | Flexible( 38 | flex: 1, 39 | child: ListView.builder( 40 | itemCount: data.bids.length, 41 | itemBuilder: (context, index) { 42 | return Container( 43 | margin: EdgeInsets.symmetric(vertical: 2), 44 | child: Row( 45 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 46 | children: [ 47 | Text( 48 | data.bids[index].amount.toString(), 49 | style: Theme.of(context).textTheme.subtitle1, 50 | ), 51 | Text( 52 | data.bids[index].price.toString(), 53 | style: Theme.of(context).textTheme.subtitle1, 54 | ) 55 | ], 56 | ), 57 | ); 58 | }, 59 | ), 60 | ), 61 | SizedBox( 62 | width: 30, 63 | ), 64 | Flexible( 65 | flex: 1, 66 | child: ListView.builder( 67 | itemCount: data.asks.length, 68 | itemBuilder: (context, index) { 69 | return Container( 70 | margin: EdgeInsets.symmetric(vertical: 2), 71 | child: Row( 72 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 73 | children: [ 74 | Text( 75 | data.asks[index].amount.toString(), 76 | style: Theme.of(context).textTheme.subtitle1, 77 | ), 78 | Text( 79 | data.asks[index].price.toString(), 80 | style: Theme.of(context).textTheme.subtitle1, 81 | ) 82 | ], 83 | ), 84 | ); 85 | }, 86 | ), 87 | ) 88 | ], 89 | ), 90 | ) 91 | ], 92 | ), 93 | ); 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /lib/ui/widgets/details/summary_section.dart: -------------------------------------------------------------------------------- 1 | import 'package:cryptocurrency_app/models/pair/pair_summary/pair_summary.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:easy_localization/easy_localization.dart'; 4 | import '../../../generated/locale_keys.g.dart'; 5 | 6 | class SummarySection extends StatelessWidget { 7 | final PairSummary data; 8 | const SummarySection({Key? key, required this.data}) : super(key: key); 9 | 10 | @override 11 | Widget build(BuildContext context) { 12 | return Container( 13 | padding: EdgeInsets.symmetric(horizontal: 20, vertical: 15), 14 | child: Column( 15 | children: [ 16 | Row( 17 | children: [ 18 | Text( 19 | LocaleKeys.price.tr(), 20 | style: Theme.of(context).textTheme.subtitle2, 21 | ) 22 | ], 23 | ), 24 | SizedBox(height: 5), 25 | Row( 26 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 27 | children: [ 28 | Text( 29 | LocaleKeys.last.tr(), 30 | style: Theme.of(context).textTheme.subtitle1, 31 | ), 32 | Text( 33 | data.price.last.toString(), 34 | style: Theme.of(context).textTheme.subtitle1, 35 | ) 36 | ], 37 | ), 38 | SizedBox(height: 5), 39 | Row( 40 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 41 | children: [ 42 | Text( 43 | LocaleKeys.high.tr(), 44 | style: Theme.of(context).textTheme.subtitle1, 45 | ), 46 | Text( 47 | data.price.high.toString(), 48 | style: Theme.of(context).textTheme.subtitle1, 49 | ) 50 | ], 51 | ), 52 | SizedBox(height: 5), 53 | Row( 54 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 55 | children: [ 56 | Text( 57 | LocaleKeys.low.tr(), 58 | style: Theme.of(context).textTheme.subtitle1, 59 | ), 60 | Text( 61 | data.price.low.toString(), 62 | style: Theme.of(context).textTheme.subtitle1, 63 | ) 64 | ], 65 | ), 66 | SizedBox(height: 5), 67 | Row( 68 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 69 | children: [ 70 | Text( 71 | LocaleKeys.change.tr(), 72 | style: Theme.of(context).textTheme.subtitle1, 73 | ), 74 | Text( 75 | data.price.change.absolute.toString(), 76 | style: Theme.of(context).textTheme.subtitle1, 77 | ) 78 | ], 79 | ), 80 | SizedBox(height: 10), 81 | Row( 82 | children: [ 83 | Text( 84 | LocaleKeys.volume.tr(), 85 | style: Theme.of(context).textTheme.subtitle2, 86 | ) 87 | ], 88 | ), 89 | SizedBox(height: 5), 90 | Row( 91 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 92 | children: [ 93 | Text( 94 | LocaleKeys.volume.tr(), 95 | style: Theme.of(context).textTheme.subtitle1, 96 | ), 97 | Text( 98 | data.volume.toString(), 99 | style: Theme.of(context).textTheme.subtitle1, 100 | ) 101 | ], 102 | ), 103 | SizedBox(height: 5), 104 | Row( 105 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 106 | children: [ 107 | Text( 108 | LocaleKeys.quoteVolume.tr(), 109 | style: Theme.of(context).textTheme.subtitle1, 110 | ), 111 | Text( 112 | data.volumeQuote.toString(), 113 | style: Theme.of(context).textTheme.subtitle1, 114 | ) 115 | ], 116 | ), 117 | ], 118 | ), 119 | ); 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /lib/ui/widgets/details/time_bar_selector.dart: -------------------------------------------------------------------------------- 1 | import 'package:cryptocurrency_app/provider/time_provider.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:hooks_riverpod/hooks_riverpod.dart'; 4 | 5 | class TimeBarSelector extends HookConsumerWidget { 6 | @override 7 | Widget build(BuildContext context, WidgetRef ref) { 8 | return Container( 9 | padding: EdgeInsets.symmetric(horizontal: 15), 10 | child: Row( 11 | mainAxisAlignment: MainAxisAlignment.spaceAround, 12 | crossAxisAlignment: CrossAxisAlignment.center, 13 | children: timeList 14 | .mapIndexed( 15 | (e, i) => InkWell( 16 | onTap: () { 17 | ref.read(timeDataProvider.notifier).state = e; 18 | }, 19 | child: Container( 20 | padding: EdgeInsets.symmetric(vertical: 3, horizontal: 8), 21 | decoration: BoxDecoration( 22 | color: ref.read(timeDataProvider.notifier).state.name == 23 | e.name 24 | ? Theme.of(context).cardColor 25 | : Colors.transparent, 26 | borderRadius: BorderRadius.all(Radius.circular(5))), 27 | child: Center( 28 | child: Text( 29 | e.name, 30 | style: ref.read(timeDataProvider.notifier).state.name == 31 | e.name 32 | ? Theme.of(context) 33 | .textTheme 34 | .headline3! 35 | .apply(color: Colors.white) 36 | : Theme.of(context).textTheme.headline4, 37 | ), 38 | ), 39 | ), 40 | ), 41 | ) 42 | .toList()), 43 | ); 44 | } 45 | } 46 | 47 | extension IndexedIterable on Iterable { 48 | Iterable mapIndexed(T Function(E e, int i) f) { 49 | var i = 0; 50 | return map((e) => f(e, i++)); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /lib/ui/widgets/details/trades_section.dart: -------------------------------------------------------------------------------- 1 | import 'package:cryptocurrency_app/models/trades/trade/trade.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:easy_localization/easy_localization.dart'; 4 | import '../../../generated/locale_keys.g.dart'; 5 | import '../../../constants/utils.dart' as Utils; 6 | 7 | class TradesSection extends StatelessWidget { 8 | final List data; 9 | const TradesSection({Key? key, required this.data}) : super(key: key); 10 | 11 | @override 12 | Widget build(BuildContext context) { 13 | return Container( 14 | padding: EdgeInsets.symmetric(horizontal: 20, vertical: 10), 15 | child: Column( 16 | children: [ 17 | Row( 18 | children: [ 19 | Expanded( 20 | child: Text( 21 | LocaleKeys.time.tr(), 22 | style: Theme.of(context).textTheme.subtitle2, 23 | ), 24 | ), 25 | Expanded( 26 | child: Text( 27 | LocaleKeys.price.tr(), 28 | textAlign: TextAlign.center, 29 | style: Theme.of(context).textTheme.subtitle2, 30 | ), 31 | ), 32 | Expanded( 33 | child: Text( 34 | LocaleKeys.amount.tr(), 35 | textAlign: TextAlign.right, 36 | style: Theme.of(context).textTheme.subtitle2, 37 | ), 38 | ) 39 | ], 40 | ), 41 | SizedBox( 42 | height: 4, 43 | ), 44 | Container( 45 | height: 250, 46 | child: ListView.builder( 47 | itemCount: 4, 48 | itemBuilder: (context, index) { 49 | return Container( 50 | padding: EdgeInsets.symmetric(vertical: 2), 51 | child: Row( 52 | children: [ 53 | Expanded( 54 | flex: 1, 55 | child: Text( 56 | Utils.epochToString(data[index].timestamp), 57 | style: Theme.of(context).textTheme.subtitle1, 58 | ), 59 | ), 60 | Expanded( 61 | flex: 1, 62 | child: Text( 63 | data[index].price, 64 | textAlign: TextAlign.center, 65 | style: Theme.of(context).textTheme.subtitle1, 66 | ), 67 | ), 68 | Expanded( 69 | flex: 1, 70 | child: Text( 71 | data[index].amount, 72 | textAlign: TextAlign.right, 73 | style: Theme.of(context).textTheme.subtitle1, 74 | ), 75 | ) 76 | ], 77 | ), 78 | ); 79 | }), 80 | ), 81 | ], 82 | ), 83 | ); 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /lib/ui/widgets/favorite_pair.dart: -------------------------------------------------------------------------------- 1 | import 'package:cryptocurrency_app/models/markets/favorite_pair/favorite_pair.dart'; 2 | import 'package:cryptocurrency_app/ui/screens/details.dart'; 3 | import 'package:cryptocurrency_app/ui/widgets/title_price.dart'; 4 | import 'package:flutter/material.dart'; 5 | import 'package:flutter_hooks/flutter_hooks.dart'; 6 | import 'package:easy_localization/easy_localization.dart'; 7 | import '../../generated/locale_keys.g.dart'; 8 | 9 | class FavoritePairWidget extends HookWidget { 10 | final FavoritePair data; 11 | FavoritePairWidget(this.data); 12 | 13 | @override 14 | Widget build(BuildContext context) { 15 | return Container( 16 | padding: EdgeInsets.symmetric(horizontal: 15), 17 | child: Column( 18 | crossAxisAlignment: CrossAxisAlignment.start, 19 | children: [ 20 | SizedBox( 21 | height: 5, 22 | ), 23 | TitlePrice(pair: data.pair), 24 | Container( 25 | margin: EdgeInsets.only(top: 10), 26 | color: Theme.of(context).dividerColor, 27 | height: 1, 28 | width: double.infinity, 29 | ), 30 | InkWell( 31 | onTap: () { 32 | Navigator.push( 33 | context, 34 | MaterialPageRoute( 35 | builder: (context) => DetailsScreen( 36 | pair: data.pair, 37 | ), 38 | ), 39 | ); 40 | }, 41 | child: Container( 42 | padding: EdgeInsets.symmetric(vertical: 10), 43 | child: Row( 44 | children: [ 45 | Icon( 46 | Icons.add_chart, 47 | size: 30, 48 | color: Theme.of(context).iconTheme.color, 49 | ), 50 | SizedBox(width: 10), 51 | Text(LocaleKeys.openChart.tr(), 52 | style: Theme.of(context).textTheme.headline3), 53 | ], 54 | ), 55 | ), 56 | ), 57 | Container( 58 | padding: EdgeInsets.symmetric(vertical: 5), 59 | color: Theme.of(context).dividerColor, 60 | height: 1, 61 | width: double.infinity, 62 | ), 63 | SizedBox( 64 | height: 10, 65 | ), 66 | ], 67 | ), 68 | ); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /lib/ui/widgets/line_chart.dart: -------------------------------------------------------------------------------- 1 | import 'package:fl_chart/fl_chart.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'dart:math'; 4 | import '../../generated/locale_keys.g.dart'; 5 | import 'package:easy_localization/easy_localization.dart'; 6 | import 'package:cryptocurrency_app/constants/utils.dart' as Utils; 7 | 8 | class LineChartWidget extends StatelessWidget { 9 | final List data; 10 | final Color color; 11 | final bool loading; 12 | final bool error; 13 | 14 | const LineChartWidget( 15 | {Key? key, 16 | this.data = const [], 17 | this.color = const Color(0xff02d39a), 18 | this.loading = false, 19 | this.error = false}) 20 | : super(key: key); 21 | 22 | @override 23 | Widget build(BuildContext context) { 24 | return Stack(alignment: AlignmentDirectional.center, children: [ 25 | Opacity( 26 | opacity: data.length > 0 && !loading & !error ? 1 : 0.3, 27 | child: Container( 28 | width: double.infinity, 29 | child: LineChart( 30 | mainData(data.length > 0 && !loading & !error 31 | ? data 32 | : Utils.demoGraphData), 33 | swapAnimationDuration: Duration(seconds: 0), 34 | ), 35 | ), 36 | ), 37 | if (loading) 38 | Center( 39 | child: CircularProgressIndicator(), 40 | ) 41 | else if (error || data.length == 0) 42 | Center( 43 | child: Text(LocaleKeys.noResults.tr(), 44 | style: Theme.of(context).textTheme.headline3), 45 | ) 46 | ]); 47 | } 48 | 49 | LineChartData mainData(List data) { 50 | return LineChartData( 51 | gridData: FlGridData( 52 | show: true, 53 | drawVerticalLine: false, 54 | drawHorizontalLine: false, 55 | horizontalInterval: 4, 56 | getDrawingHorizontalLine: (value) { 57 | return FlLine( 58 | color: const Color(0xff37434d), 59 | strokeWidth: 1, 60 | ); 61 | }, 62 | getDrawingVerticalLine: (value) { 63 | return FlLine( 64 | color: const Color(0xff37434d), 65 | strokeWidth: 1, 66 | ); 67 | }, 68 | ), 69 | titlesData: FlTitlesData( 70 | show: false, 71 | ), 72 | borderData: FlBorderData( 73 | show: false, 74 | ), 75 | minX: 0, 76 | maxX: data.length.toDouble() - 1, 77 | minY: data.reduce(min).toDouble(), 78 | maxY: data.reduce(max).toDouble(), 79 | lineBarsData: [ 80 | LineChartBarData( 81 | spots: listData(data), 82 | colors: [color], 83 | barWidth: 3, 84 | isStrokeCapRound: true, 85 | dotData: FlDotData( 86 | show: false, 87 | ), 88 | belowBarData: BarAreaData( 89 | show: true, 90 | gradientFrom: Offset(0, .9), 91 | gradientTo: Offset(0, 0.5), 92 | colors: [color.withOpacity(.01), color.withOpacity(.3)], 93 | ), 94 | ), 95 | ], 96 | ); 97 | } 98 | 99 | List listData(List data) { 100 | return data 101 | .mapIndexed((e, i) => FlSpot(i.toDouble(), e.toDouble())) 102 | .toList(); 103 | } 104 | } 105 | 106 | extension IndexedIterable on Iterable { 107 | Iterable mapIndexed(T Function(E e, int i) f) { 108 | var i = 0; 109 | return map((e) => f(e, i++)); 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /lib/ui/widgets/title_price.dart: -------------------------------------------------------------------------------- 1 | import 'package:auto_size_text/auto_size_text.dart'; 2 | import 'package:cryptocurrency_app/models/markets/pair/pair.dart'; 3 | import 'package:cryptocurrency_app/provider/crypto_provider.dart'; 4 | import 'package:flutter/material.dart'; 5 | import 'package:hooks_riverpod/hooks_riverpod.dart'; 6 | import 'package:easy_localization/easy_localization.dart'; 7 | 8 | class TitlePrice extends HookConsumerWidget { 9 | final Pair pair; 10 | 11 | TitlePrice({required this.pair}); 12 | 13 | @override 14 | Widget build(BuildContext context, WidgetRef ref) { 15 | final data = ref.watch(pairSummaryProvider(pair)); 16 | 17 | return data.when( 18 | data: (data) { 19 | return Container( 20 | child: Column( 21 | crossAxisAlignment: CrossAxisAlignment.start, 22 | children: [ 23 | AutoSizeText(pair.pair, 24 | maxLines: 1, style: Theme.of(context).textTheme.headline2), 25 | AutoSizeText(data.price.last.toString(), 26 | maxLines: 1, style: Theme.of(context).textTheme.headline1), 27 | if (true) 28 | Row(children: [ 29 | AutoSizeText(data.price.change.absolute.toStringAsFixed(5), 30 | textAlign: TextAlign.start, 31 | minFontSize: 0, 32 | stepGranularity: 0.1, 33 | maxLines: 1, 34 | style: TextStyle( 35 | color: data.price.change.absolute >= 0 36 | ? Colors.green 37 | : Colors.red, 38 | fontSize: 39 | Theme.of(context).textTheme.headline5?.fontSize, 40 | fontWeight: FontWeight.w800)), 41 | AutoSizeText( 42 | ' (${data.price.change.percentage.toStringAsFixed(2)}%)', 43 | textAlign: TextAlign.start, 44 | minFontSize: 0, 45 | stepGranularity: 0.1, 46 | maxLines: 1, 47 | style: Theme.of(context).textTheme.headline4), 48 | ]), 49 | ], 50 | ), 51 | ); 52 | }, 53 | loading: () => Center( 54 | child: CircularProgressIndicator(), 55 | ), 56 | error: (error, e) => Center( 57 | child: Text(error.toString().tr()), 58 | ), 59 | ); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: cryptocurrency_app 2 | description: A new Flutter project. 3 | 4 | # The following line prevents the package from being accidentally published to 5 | # pub.dev using `pub publish`. This is preferred for private packages. 6 | publish_to: "none" # Remove this line if you wish to publish to pub.dev 7 | 8 | # The following defines the version and build number for your application. 9 | # A version number is three numbers separated by dots, like 1.2.43 10 | # followed by an optional build number separated by a +. 11 | # Both the version and the builder number may be overridden in flutter 12 | # build by specifying --build-name and --build-number, respectively. 13 | # In Android, build-name is used as versionName while build-number used as versionCode. 14 | # Read more about Android versioning at https://developer.android.com/studio/publish/versioning 15 | # In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion. 16 | # Read more about iOS versioning at 17 | # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html 18 | version: 1.0.0+1 19 | 20 | environment: 21 | sdk: '>=2.12.0 <3.0.0' 22 | 23 | dependencies: 24 | flutter: 25 | sdk: flutter 26 | cupertino_icons: ^1.0.0 27 | hooks_riverpod: 2.0.0-dev.3 28 | flutter_riverpod: 2.0.0-dev.3 29 | dio: ^4.0.0 30 | flutter_dotenv: ^5.0.0 31 | fl_chart: ^0.36.1 32 | settings_ui: ^2.0.2 33 | auto_size_text: ^3.0.0-nullsafety.0 34 | flutter_secure_storage: ^5.0.2 35 | easy_localization: ^3.0.0 36 | freezed_annotation: 37 | flutter_candlesticks: 38 | git: https://github.com/salvadordeveloper/flutter-candlesticks.git 39 | mockito: ^5.0.9 40 | 41 | dev_dependencies: 42 | integration_test: 43 | sdk: flutter 44 | flutter_test: 45 | sdk: flutter 46 | http_mock_adapter: ^0.3.2 47 | build_runner: 48 | freezed: 49 | json_serializable: 50 | flutter_launcher_icons: ^0.9.0 51 | flutter_native_splash: ^1.1.8+4 52 | 53 | flutter_icons: 54 | android: "launcher_icon" 55 | ios: true 56 | image_path: "assets/icon/icon.png" 57 | image_path_ios: "assets/icon/icon_ios.png" 58 | 59 | flutter_native_splash: 60 | color: "#3D3D3D" 61 | image: assets/icon/icon.png 62 | color_dark: "#131313" 63 | 64 | # For information on the generic Dart part of this file, see the 65 | # following page: https://dart.dev/tools/pub/pubspec 66 | # The following section is specific to Flutter. 67 | flutter: 68 | # The following line ensures that the Material Icons font is 69 | # included with your application, so that you can use the icons in 70 | # the material Icons class. 71 | uses-material-design: true 72 | 73 | assets: 74 | - assets/translations/ 75 | - assets/icon/icon.png 76 | - .env 77 | # To add assets to your application, add an assets section, like this: 78 | # assets: 79 | # - images/a_dot_burr.jpeg 80 | # - images/a_dot_ham.jpeg 81 | # An image asset can refer to one or more resolution-specific "variants", see 82 | # https://flutter.dev/assets-and-images/#resolution-aware. 83 | # For details regarding adding assets from package dependencies, see 84 | # https://flutter.dev/assets-and-images/#from-packages 85 | # To add custom fonts to your application, add a fonts section here, 86 | # in this "flutter" section. Each entry in this list should have a 87 | # "family" key with the font family name, and a "fonts" key with a 88 | # list giving the asset and other descriptors for the font. For 89 | # example: 90 | # fonts: 91 | # - family: Schyler 92 | # fonts: 93 | # - asset: fonts/Schyler-Regular.ttf 94 | # - asset: fonts/Schyler-Italic.ttf 95 | # style: italic 96 | # - family: Trajan Pro 97 | # fonts: 98 | # - asset: fonts/TrajanPro.ttf 99 | # - asset: fonts/TrajanPro_Bold.ttf 100 | # weight: 700 101 | # 102 | # For details regarding fonts from package dependencies, 103 | # see https://flutter.dev/custom-fonts/#from-packages 104 | -------------------------------------------------------------------------------- /run_all_tests.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # start the app and write the uri to a file 4 | #flutter run --vmservice-out-file="test_driver/uri.txt" 5 | 6 | 7 | flutter drive --driver=test_driver/integration_test.dart --target=integration_test/main_test.dart -------------------------------------------------------------------------------- /screenshots/1_dark.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/salvadordeveloper/flutter-crypto-app/ff3e872d6d7dbb690781a263ee3ef42a332d17cc/screenshots/1_dark.jpeg -------------------------------------------------------------------------------- /screenshots/1_light.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/salvadordeveloper/flutter-crypto-app/ff3e872d6d7dbb690781a263ee3ef42a332d17cc/screenshots/1_light.jpeg -------------------------------------------------------------------------------- /screenshots/2_dark.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/salvadordeveloper/flutter-crypto-app/ff3e872d6d7dbb690781a263ee3ef42a332d17cc/screenshots/2_dark.jpeg -------------------------------------------------------------------------------- /screenshots/2_light.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/salvadordeveloper/flutter-crypto-app/ff3e872d6d7dbb690781a263ee3ef42a332d17cc/screenshots/2_light.jpeg -------------------------------------------------------------------------------- /screenshots/3_dark.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/salvadordeveloper/flutter-crypto-app/ff3e872d6d7dbb690781a263ee3ef42a332d17cc/screenshots/3_dark.jpeg -------------------------------------------------------------------------------- /screenshots/3_light.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/salvadordeveloper/flutter-crypto-app/ff3e872d6d7dbb690781a263ee3ef42a332d17cc/screenshots/3_light.jpeg -------------------------------------------------------------------------------- /screenshots/cover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/salvadordeveloper/flutter-crypto-app/ff3e872d6d7dbb690781a263ee3ef42a332d17cc/screenshots/cover.png -------------------------------------------------------------------------------- /test/data/api_data.dart: -------------------------------------------------------------------------------- 1 | class ApiData { 2 | static final Map exchanges = { 3 | "result": [ 4 | { 5 | "id": 17, 6 | "symbol": "mexbt", 7 | "name": "meXBT", 8 | "route": "https://api.cryptowat.ch/exchanges/mexbt", 9 | "active": false 10 | }, 11 | { 12 | "id": 62, 13 | "symbol": "coinone", 14 | "name": "Coinone", 15 | "route": "https://api.cryptowat.ch/exchanges/coinone", 16 | "active": true 17 | }, 18 | ], 19 | "allowance": {"cost": 0, "remaining": 100, "upgrade": ""} 20 | }; 21 | 22 | static final Map pairs = { 23 | "result": [ 24 | { 25 | "id": 579, 26 | "exchange": "binance", 27 | "pair": "btcusdt", 28 | "active": true, 29 | "route": "https://api.cryptowat.ch/markets/binance/btcusdt" 30 | }, 31 | { 32 | "id": 580, 33 | "exchange": "binance", 34 | "pair": "ethbtc", 35 | "active": true, 36 | "route": "https://api.cryptowat.ch/markets/binance/ethbtc" 37 | }, 38 | { 39 | "id": 581, 40 | "exchange": "binance", 41 | "pair": "ltcbtc", 42 | "active": true, 43 | "route": "https://api.cryptowat.ch/markets/binance/ltcbtc" 44 | }, 45 | { 46 | "id": 582, 47 | "exchange": "binance", 48 | "pair": "neobtc", 49 | "active": true, 50 | "route": "https://api.cryptowat.ch/markets/binance/neobtc" 51 | }, 52 | ], 53 | "allowance": {"cost": 0, "remaining": 100, "upgrade": ""} 54 | }; 55 | 56 | static final Map pair_btcusdt_summary = { 57 | "result": { 58 | "price": { 59 | "last": 35503.33, 60 | "high": 43861.94, 61 | "low": 30000, 62 | "change": {"percentage": -0.18764266402587584, "absolute": -8200.75} 63 | }, 64 | "volume": 257132.87322650044, 65 | "volumeQuote": 10096197214.14349 66 | }, 67 | "allowance": {"cost": 0, "remaining": 100, "upgrade": ""} 68 | }; 69 | 70 | static final Map pair_btcusdt_oderbook = { 71 | "result": { 72 | "asks": [ 73 | [35922.59, 0.004088], 74 | [35925.23, 0.003071], 75 | [35925.71, 0.012824], 76 | [35927.12, 0.000556], 77 | [35927.58, 0.2178], 78 | ], 79 | "bids": [ 80 | [35904.23, 0.153095], 81 | [35900.35, 0.082238], 82 | [35898, 0.12], 83 | [35897.99, 0.006152], 84 | [35897.68, 0.04332], 85 | ], 86 | "seqNum": 429614 87 | }, 88 | "allowance": {"cost": 0, "remaining": 100, "upgrade": ""} 89 | }; 90 | 91 | static final Map pair_btcusdt_trades = { 92 | "result": [ 93 | [0, 1621433110, 34452.66, 0.008464], 94 | [0, 1621433110, 34454.43, 0.016662], 95 | [0, 1621433110, 34485.69, 0.00476], 96 | [0, 1621433110, 34475.97, 0.000401], 97 | [0, 1621433110, 34456.09, 0.0011], 98 | [0, 1621433110, 34456.09, 0.004997], 99 | ], 100 | "allowance": {"cost": 0, "remaining": 100, "upgrade": ""} 101 | }; 102 | 103 | static final Map pair_btcusdt_graph = { 104 | "result": { 105 | "14400": [ 106 | [ 107 | 1607054400, 108 | 19422.34, 109 | 19527, 110 | 19122.74, 111 | 19162.62, 112 | 8683.588417, 113 | 167917416.81467284 114 | ], 115 | [ 116 | 1607097600, 117 | 18835.47, 118 | 19146.22, 119 | 18686.38, 120 | 18943.35, 121 | 14717.586675, 122 | 278732315.17076141 123 | ], 124 | [ 125 | 1607112000, 126 | 18944.06, 127 | 19078.68, 128 | 18817, 129 | 19038.73, 130 | 8799.851665, 131 | 166925728.42698553 132 | ], 133 | ] 134 | }, 135 | "allowance": {"cost": 0, "remaining": 100, "upgrade": ""} 136 | }; 137 | } 138 | -------------------------------------------------------------------------------- /test/exception_test.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | import 'dart:io'; 3 | 4 | import 'package:cryptocurrency_app/constants/exceptions.dart'; 5 | import 'package:dio/dio.dart'; 6 | import 'package:flutter/material.dart'; 7 | import 'package:flutter_test/flutter_test.dart'; 8 | import 'package:easy_localization/src/localization.dart'; 9 | import 'package:easy_localization/src/translations.dart'; 10 | import '../lib/generated/locale_keys.g.dart'; 11 | 12 | void main() { 13 | setUpAll(() { 14 | File('assets/translations/en.json').readAsString().then((String contents) { 15 | Map data = jsonDecode(contents); 16 | Localization.load(Locale('en'), translations: Translations(data)); 17 | }); 18 | }); 19 | 20 | test('Create DataException', () { 21 | final exception = 22 | DataException(message: LocaleKeys.errorSomethingWentWrong); 23 | expect(exception.toString(), LocaleKeys.errorSomethingWentWrong); 24 | }); 25 | 26 | test('Exception Dio Cancel', () { 27 | final error = DioError( 28 | requestOptions: RequestOptions(path: ""), type: DioErrorType.cancel); 29 | final exception = DataException.fromDioError(error); 30 | expect(exception.toString(), LocaleKeys.errorRequestCancelled); 31 | }); 32 | 33 | test('Exception Connection Timeout', () { 34 | final error = DioError( 35 | requestOptions: RequestOptions(path: ""), 36 | type: DioErrorType.connectTimeout); 37 | final exception = DataException.fromDioError(error); 38 | expect(exception.toString(), LocaleKeys.errorConnectionTimeout); 39 | }); 40 | 41 | test('Exception other', () { 42 | final error = DioError( 43 | requestOptions: RequestOptions(path: ""), type: DioErrorType.other); 44 | final exception = DataException.fromDioError(error); 45 | expect(exception.toString(), LocaleKeys.errorInternetConnection); 46 | }); 47 | 48 | test('Exception Receive Timeout', () { 49 | final error = DioError( 50 | requestOptions: RequestOptions(path: ""), 51 | type: DioErrorType.receiveTimeout); 52 | final exception = DataException.fromDioError(error); 53 | expect(exception.toString(), LocaleKeys.errorReceiveTimeout); 54 | }); 55 | 56 | test('Exception Response 400', () { 57 | final error = DioError( 58 | requestOptions: RequestOptions(path: ""), 59 | type: DioErrorType.response, 60 | response: Response( 61 | requestOptions: RequestOptions( 62 | path: "", 63 | ), 64 | statusCode: 400)); 65 | final exception = DataException.fromDioError(error); 66 | expect(exception.toString(), LocaleKeys.errorBadRequest); 67 | }); 68 | 69 | test('Exception Response 404', () { 70 | final error = DioError( 71 | requestOptions: RequestOptions(path: ""), 72 | type: DioErrorType.response, 73 | response: Response( 74 | requestOptions: RequestOptions( 75 | path: "", 76 | ), 77 | statusCode: 404)); 78 | final exception = DataException.fromDioError(error); 79 | expect(exception.toString(), LocaleKeys.errorRequestNotFound); 80 | }); 81 | 82 | test('Exception Response 500', () { 83 | final error = DioError( 84 | requestOptions: RequestOptions(path: ""), 85 | type: DioErrorType.response, 86 | response: Response( 87 | requestOptions: RequestOptions( 88 | path: "", 89 | ), 90 | statusCode: 500)); 91 | final exception = DataException.fromDioError(error); 92 | expect(exception.toString(), LocaleKeys.errorIntenalServer); 93 | }); 94 | 95 | test('Exception Send Timeout', () { 96 | final error = DioError( 97 | requestOptions: RequestOptions( 98 | path: "", 99 | ), 100 | type: DioErrorType.sendTimeout); 101 | final exception = DataException.fromDioError(error); 102 | expect(exception.toString(), LocaleKeys.errorSendTimeout); 103 | }); 104 | } 105 | -------------------------------------------------------------------------------- /test/widget_test.dart: -------------------------------------------------------------------------------- 1 | // This is a basic Flutter widget test. 2 | // 3 | // To perform an interaction with a widget in your test, use the WidgetTester 4 | // utility that Flutter provides. For example, you can send tap and scroll 5 | // gestures. You can also use WidgetTester to find child widgets in the widget 6 | // tree, read text, and verify that the values of widget properties are correct. 7 | 8 | void main() {} 9 | -------------------------------------------------------------------------------- /test_driver/integration_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:integration_test/integration_test_driver.dart'; 2 | 3 | Future main() => integrationDriver(); 4 | -------------------------------------------------------------------------------- /web/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/salvadordeveloper/flutter-crypto-app/ff3e872d6d7dbb690781a263ee3ef42a332d17cc/web/favicon.png -------------------------------------------------------------------------------- /web/icons/Icon-192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/salvadordeveloper/flutter-crypto-app/ff3e872d6d7dbb690781a263ee3ef42a332d17cc/web/icons/Icon-192.png -------------------------------------------------------------------------------- /web/icons/Icon-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/salvadordeveloper/flutter-crypto-app/ff3e872d6d7dbb690781a263ee3ef42a332d17cc/web/icons/Icon-512.png -------------------------------------------------------------------------------- /web/icons/Icon-maskable-192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/salvadordeveloper/flutter-crypto-app/ff3e872d6d7dbb690781a263ee3ef42a332d17cc/web/icons/Icon-maskable-192.png -------------------------------------------------------------------------------- /web/icons/Icon-maskable-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/salvadordeveloper/flutter-crypto-app/ff3e872d6d7dbb690781a263ee3ef42a332d17cc/web/icons/Icon-maskable-512.png -------------------------------------------------------------------------------- /web/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | flutter_crypto_app 30 | 31 | 32 | 33 | 36 | 100 | 101 | 102 | -------------------------------------------------------------------------------- /web/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "flutter_crypto_app", 3 | "short_name": "flutter_crypto_app", 4 | "start_url": ".", 5 | "display": "standalone", 6 | "background_color": "#0175C2", 7 | "theme_color": "#0175C2", 8 | "description": "A new Flutter project.", 9 | "orientation": "portrait-primary", 10 | "prefer_related_applications": false, 11 | "icons": [ 12 | { 13 | "src": "icons/Icon-192.png", 14 | "sizes": "192x192", 15 | "type": "image/png" 16 | }, 17 | { 18 | "src": "icons/Icon-512.png", 19 | "sizes": "512x512", 20 | "type": "image/png" 21 | }, 22 | { 23 | "src": "icons/Icon-maskable-192.png", 24 | "sizes": "192x192", 25 | "type": "image/png", 26 | "purpose": "maskable" 27 | }, 28 | { 29 | "src": "icons/Icon-maskable-512.png", 30 | "sizes": "512x512", 31 | "type": "image/png", 32 | "purpose": "maskable" 33 | } 34 | ] 35 | } 36 | --------------------------------------------------------------------------------