├── .gitignore ├── .metadata ├── README.md ├── analysis_options.yaml ├── android ├── .gitignore ├── app │ ├── build.gradle │ └── src │ │ ├── debug │ │ └── AndroidManifest.xml │ │ ├── main │ │ ├── AndroidManifest.xml │ │ ├── kotlin │ │ │ └── luisma │ │ │ │ └── crypto_tracker │ │ │ │ └── MainActivity.kt │ │ └── res │ │ │ ├── drawable-v21 │ │ │ └── launch_background.xml │ │ │ ├── drawable │ │ │ └── launch_background.xml │ │ │ ├── mipmap-hdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-mdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xhdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xxhdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xxxhdpi │ │ │ └── ic_launcher.png │ │ │ ├── values-night │ │ │ └── styles.xml │ │ │ └── values │ │ │ └── styles.xml │ │ └── profile │ │ └── AndroidManifest.xml ├── build.gradle ├── gradle.properties ├── gradle │ └── wrapper │ │ └── gradle-wrapper.properties └── settings.gradle ├── assets └── crypto_logos │ ├── ADA.png │ ├── BTC.png │ ├── DASH.png │ ├── DOGE.png │ ├── ETH.png │ ├── LTC.png │ ├── USDT.png │ ├── XMR.png │ └── XRP.png ├── ios ├── .gitignore ├── Flutter │ ├── AppFrameworkInfo.plist │ ├── Debug.xcconfig │ └── Release.xcconfig ├── 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 │ └── 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 ├── data │ ├── crypto_data_service │ │ ├── crypto_data_api_contracts.dart │ │ ├── crypto_data_constants.dart │ │ ├── crypto_data_model.dart │ │ └── crypto_data_service.dart │ └── theme_service.dart ├── main.dart └── ui │ ├── features │ ├── base_view │ │ ├── base_view.dart │ │ ├── base_view_state.dart │ │ └── base_vm.dart │ ├── main_view │ │ ├── main_view.dart │ │ ├── main_view_data.dart │ │ └── main_view_vm.dart │ └── theme_builder │ │ ├── theme_builder.dart │ │ └── theme_builder_vm.dart │ ├── ui_constants │ ├── assets.dart │ ├── kcolors.dart │ └── labels.dart │ ├── ui_helpers.dart │ └── widgets │ ├── crypto_card.dart │ ├── dialog_wrapper.dart │ ├── graph.dart │ ├── modal_error.dart │ └── modal_fiat_selector.dart ├── pubspec.lock └── pubspec.yaml /.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 | # Android Studio will place build artifacts here 44 | /android/app/debug 45 | /android/app/profile 46 | /android/app/release 47 | -------------------------------------------------------------------------------- /.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: "54e66469a933b60ddf175f858f82eaeb97e48c8d" 8 | channel: "stable" 9 | 10 | project_type: app 11 | 12 | # Tracks metadata for the flutter migrate command 13 | migration: 14 | platforms: 15 | - platform: root 16 | create_revision: 54e66469a933b60ddf175f858f82eaeb97e48c8d 17 | base_revision: 54e66469a933b60ddf175f858f82eaeb97e48c8d 18 | - platform: macos 19 | create_revision: 54e66469a933b60ddf175f858f82eaeb97e48c8d 20 | base_revision: 54e66469a933b60ddf175f858f82eaeb97e48c8d 21 | 22 | # User provided section 23 | 24 | # List of Local paths (relative to this file) that should be 25 | # ignored by the migrate tool. 26 | # 27 | # Files that are not part of the templates will be ignored by default. 28 | unmanaged_files: 29 | - 'lib/main.dart' 30 | - 'ios/Runner.xcodeproj/project.pbxproj' 31 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Zero Packages Crypto Tracker 2 | ## Crypto tracker Flutter app with zero packages consuming the [CoinGecko](https://www.coingecko.com/) api. 3 | 4 | This application collects the historical and current prices (in some fiat currencies) of the most popular cryptocurrencies with graphic display. 5 | 6 | Be sure to leave a star :sweat_smile:. 7 | 8 | https://user-images.githubusercontent.com/70621340/119499926-1260cd80-bd1c-11eb-8b67-be82a52ec316.mp4 9 | 10 |

11 | 12 | 13 | 14 |

15 | 16 | ### Features: 17 | * MVVM architecture: Based on inherited widget for state control in the view models. 18 | * Render Object: The graph is a LeafRenderObjectWidget. 19 | * Animations. 20 | * Dark and light mode. 21 | * Streams. 22 | 23 | ### Configuration: 24 | 1. Obtain your [CoinGecko Free Api Key](https://docs.coingecko.com/reference/setting-up-your-api-key), 25 | 2. Navigate to lib/data/crypto_data_service/crypto_data_api_contracts.dart and copy the key where it says 'YOUR_API_KEY_HERE'. 26 | 3. Run the app. 27 | 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /analysis_options.yaml: -------------------------------------------------------------------------------- 1 | # This file configures the analyzer, which statically analyzes Dart code to 2 | # check for errors, warnings, and lints. 3 | # 4 | # The issues identified by the analyzer are surfaced in the UI of Dart-enabled 5 | # IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be 6 | # invoked from the command line by running `flutter analyze`. 7 | 8 | # The following line activates a set of recommended lints for Flutter apps, 9 | # packages, and plugins designed to encourage good coding practices. 10 | # include: package:flutter_lints/flutter.yaml 11 | 12 | linter: 13 | # The lint rules applied to this project can be customized in the 14 | # section below to disable rules from the `package:flutter_lints/flutter.yaml` 15 | # included above or to enable additional rules. A list of all available lints 16 | # and their documentation is published at https://dart.dev/lints. 17 | # 18 | # Instead of disabling a lint rule for the entire project in the 19 | # section below, it can also be suppressed for a single line of code 20 | # or a specific dart file by using the `// ignore: name_of_lint` and 21 | # `// ignore_for_file: name_of_lint` syntax on the line or in the file 22 | # producing the lint. 23 | rules: 24 | # avoid_print: false # Uncomment to disable the `avoid_print` rule 25 | # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule 26 | 27 | # Additional information about this file can be found at 28 | # https://dart.dev/guides/language/analysis-options 29 | -------------------------------------------------------------------------------- /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 | defaultConfig { 36 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). 37 | applicationId "luisma.crypto_tracker" 38 | minSdkVersion flutter.minSdkVersion 39 | targetSdkVersion 31 40 | versionCode flutterVersionCode.toInteger() 41 | versionName flutterVersionName 42 | } 43 | 44 | buildTypes { 45 | release { 46 | // TODO: Add your own signing config for the release build. 47 | // Signing with the debug keys for now, so `flutter run --release` works. 48 | signingConfig signingConfigs.debug 49 | } 50 | } 51 | } 52 | 53 | flutter { 54 | source '../..' 55 | } 56 | 57 | dependencies { 58 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 59 | } 60 | -------------------------------------------------------------------------------- /android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 14 | 18 | 22 | 27 | 31 | 32 | 33 | 34 | 35 | 36 | 38 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /android/app/src/main/kotlin/luisma/crypto_tracker/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package luisma.crypto_tracker 2 | 3 | import io.flutter.embedding.android.FlutterActivity 4 | 5 | class MainActivity: FlutterActivity() { 6 | } 7 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-v21/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LuisMaGit/flutter-crypto/4f098d6eb027a92393b8d15efd12615f8f795059/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LuisMaGit/flutter-crypto/4f098d6eb027a92393b8d15efd12615f8f795059/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LuisMaGit/flutter-crypto/4f098d6eb027a92393b8d15efd12615f8f795059/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LuisMaGit/flutter-crypto/4f098d6eb027a92393b8d15efd12615f8f795059/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LuisMaGit/flutter-crypto/4f098d6eb027a92393b8d15efd12615f8f795059/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/values-night/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /android/app/src/profile/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | ext.kotlin_version = '1.3.50' 3 | repositories { 4 | google() 5 | jcenter() 6 | } 7 | 8 | dependencies { 9 | classpath 'com.android.tools.build:gradle:4.1.0' 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 | tasks.register("clean", 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 | #Fri Jun 23 08:50:38 CEST 2017 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-7.6.1-all.zip 7 | -------------------------------------------------------------------------------- /android/settings.gradle: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | def flutterSdkPath = { 3 | def properties = new Properties() 4 | file("local.properties").withInputStream { properties.load(it) } 5 | def flutterSdkPath = properties.getProperty("flutter.sdk") 6 | assert flutterSdkPath != null, "flutter.sdk not set in local.properties" 7 | return flutterSdkPath 8 | }() 9 | 10 | includeBuild("$flutterSdkPath/packages/flutter_tools/gradle") 11 | 12 | repositories { 13 | google() 14 | mavenCentral() 15 | gradlePluginPortal() 16 | } 17 | } 18 | 19 | plugins { 20 | id "dev.flutter.flutter-plugin-loader" version "1.0.0" 21 | id "com.android.application" version "7.1.2" apply false 22 | id "org.jetbrains.kotlin.android" version "1.8.0" apply false 23 | } 24 | 25 | include ":app" -------------------------------------------------------------------------------- /assets/crypto_logos/ADA.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LuisMaGit/flutter-crypto/4f098d6eb027a92393b8d15efd12615f8f795059/assets/crypto_logos/ADA.png -------------------------------------------------------------------------------- /assets/crypto_logos/BTC.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LuisMaGit/flutter-crypto/4f098d6eb027a92393b8d15efd12615f8f795059/assets/crypto_logos/BTC.png -------------------------------------------------------------------------------- /assets/crypto_logos/DASH.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LuisMaGit/flutter-crypto/4f098d6eb027a92393b8d15efd12615f8f795059/assets/crypto_logos/DASH.png -------------------------------------------------------------------------------- /assets/crypto_logos/DOGE.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LuisMaGit/flutter-crypto/4f098d6eb027a92393b8d15efd12615f8f795059/assets/crypto_logos/DOGE.png -------------------------------------------------------------------------------- /assets/crypto_logos/ETH.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LuisMaGit/flutter-crypto/4f098d6eb027a92393b8d15efd12615f8f795059/assets/crypto_logos/ETH.png -------------------------------------------------------------------------------- /assets/crypto_logos/LTC.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LuisMaGit/flutter-crypto/4f098d6eb027a92393b8d15efd12615f8f795059/assets/crypto_logos/LTC.png -------------------------------------------------------------------------------- /assets/crypto_logos/USDT.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LuisMaGit/flutter-crypto/4f098d6eb027a92393b8d15efd12615f8f795059/assets/crypto_logos/USDT.png -------------------------------------------------------------------------------- /assets/crypto_logos/XMR.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LuisMaGit/flutter-crypto/4f098d6eb027a92393b8d15efd12615f8f795059/assets/crypto_logos/XMR.png -------------------------------------------------------------------------------- /assets/crypto_logos/XRP.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LuisMaGit/flutter-crypto/4f098d6eb027a92393b8d15efd12615f8f795059/assets/crypto_logos/XRP.png -------------------------------------------------------------------------------- /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 | en 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 | 8.0 25 | 26 | 27 | -------------------------------------------------------------------------------- /ios/Flutter/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Generated.xcconfig" 2 | -------------------------------------------------------------------------------- /ios/Flutter/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Generated.xcconfig" 2 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; 11 | 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; 12 | 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; 13 | 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 14 | 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; 15 | 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; 16 | /* End PBXBuildFile section */ 17 | 18 | /* Begin PBXCopyFilesBuildPhase section */ 19 | 9705A1C41CF9048500538489 /* Embed Frameworks */ = { 20 | isa = PBXCopyFilesBuildPhase; 21 | buildActionMask = 2147483647; 22 | dstPath = ""; 23 | dstSubfolderSpec = 10; 24 | files = ( 25 | ); 26 | name = "Embed Frameworks"; 27 | runOnlyForDeploymentPostprocessing = 0; 28 | }; 29 | /* End PBXCopyFilesBuildPhase section */ 30 | 31 | /* Begin PBXFileReference section */ 32 | 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; 33 | 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; 34 | 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; 35 | 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; 36 | 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 37 | 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; 38 | 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; 39 | 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; 40 | 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; 41 | 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 42 | 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 43 | 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 44 | 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 45 | /* End PBXFileReference section */ 46 | 47 | /* Begin PBXFrameworksBuildPhase section */ 48 | 97C146EB1CF9000F007C117D /* Frameworks */ = { 49 | isa = PBXFrameworksBuildPhase; 50 | buildActionMask = 2147483647; 51 | files = ( 52 | ); 53 | runOnlyForDeploymentPostprocessing = 0; 54 | }; 55 | /* End PBXFrameworksBuildPhase section */ 56 | 57 | /* Begin PBXGroup section */ 58 | 9740EEB11CF90186004384FC /* Flutter */ = { 59 | isa = PBXGroup; 60 | children = ( 61 | 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, 62 | 9740EEB21CF90195004384FC /* Debug.xcconfig */, 63 | 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, 64 | 9740EEB31CF90195004384FC /* Generated.xcconfig */, 65 | ); 66 | name = Flutter; 67 | sourceTree = ""; 68 | }; 69 | 97C146E51CF9000F007C117D = { 70 | isa = PBXGroup; 71 | children = ( 72 | 9740EEB11CF90186004384FC /* Flutter */, 73 | 97C146F01CF9000F007C117D /* Runner */, 74 | 97C146EF1CF9000F007C117D /* Products */, 75 | ); 76 | sourceTree = ""; 77 | }; 78 | 97C146EF1CF9000F007C117D /* Products */ = { 79 | isa = PBXGroup; 80 | children = ( 81 | 97C146EE1CF9000F007C117D /* Runner.app */, 82 | ); 83 | name = Products; 84 | sourceTree = ""; 85 | }; 86 | 97C146F01CF9000F007C117D /* Runner */ = { 87 | isa = PBXGroup; 88 | children = ( 89 | 97C146FA1CF9000F007C117D /* Main.storyboard */, 90 | 97C146FD1CF9000F007C117D /* Assets.xcassets */, 91 | 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, 92 | 97C147021CF9000F007C117D /* Info.plist */, 93 | 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, 94 | 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, 95 | 74858FAE1ED2DC5600515810 /* AppDelegate.swift */, 96 | 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */, 97 | ); 98 | path = Runner; 99 | sourceTree = ""; 100 | }; 101 | /* End PBXGroup section */ 102 | 103 | /* Begin PBXNativeTarget section */ 104 | 97C146ED1CF9000F007C117D /* Runner */ = { 105 | isa = PBXNativeTarget; 106 | buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; 107 | buildPhases = ( 108 | 9740EEB61CF901F6004384FC /* Run Script */, 109 | 97C146EA1CF9000F007C117D /* Sources */, 110 | 97C146EB1CF9000F007C117D /* Frameworks */, 111 | 97C146EC1CF9000F007C117D /* Resources */, 112 | 9705A1C41CF9048500538489 /* Embed Frameworks */, 113 | 3B06AD1E1E4923F5004D2608 /* Thin Binary */, 114 | ); 115 | buildRules = ( 116 | ); 117 | dependencies = ( 118 | ); 119 | name = Runner; 120 | productName = Runner; 121 | productReference = 97C146EE1CF9000F007C117D /* Runner.app */; 122 | productType = "com.apple.product-type.application"; 123 | }; 124 | /* End PBXNativeTarget section */ 125 | 126 | /* Begin PBXProject section */ 127 | 97C146E61CF9000F007C117D /* Project object */ = { 128 | isa = PBXProject; 129 | attributes = { 130 | LastUpgradeCheck = 1020; 131 | ORGANIZATIONNAME = ""; 132 | TargetAttributes = { 133 | 97C146ED1CF9000F007C117D = { 134 | CreatedOnToolsVersion = 7.3.1; 135 | LastSwiftMigration = 1100; 136 | }; 137 | }; 138 | }; 139 | buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; 140 | compatibilityVersion = "Xcode 9.3"; 141 | developmentRegion = en; 142 | hasScannedForEncodings = 0; 143 | knownRegions = ( 144 | en, 145 | Base, 146 | ); 147 | mainGroup = 97C146E51CF9000F007C117D; 148 | productRefGroup = 97C146EF1CF9000F007C117D /* Products */; 149 | projectDirPath = ""; 150 | projectRoot = ""; 151 | targets = ( 152 | 97C146ED1CF9000F007C117D /* Runner */, 153 | ); 154 | }; 155 | /* End PBXProject section */ 156 | 157 | /* Begin PBXResourcesBuildPhase section */ 158 | 97C146EC1CF9000F007C117D /* Resources */ = { 159 | isa = PBXResourcesBuildPhase; 160 | buildActionMask = 2147483647; 161 | files = ( 162 | 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, 163 | 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, 164 | 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, 165 | 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, 166 | ); 167 | runOnlyForDeploymentPostprocessing = 0; 168 | }; 169 | /* End PBXResourcesBuildPhase section */ 170 | 171 | /* Begin PBXShellScriptBuildPhase section */ 172 | 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { 173 | isa = PBXShellScriptBuildPhase; 174 | buildActionMask = 2147483647; 175 | files = ( 176 | ); 177 | inputPaths = ( 178 | ); 179 | name = "Thin Binary"; 180 | outputPaths = ( 181 | ); 182 | runOnlyForDeploymentPostprocessing = 0; 183 | shellPath = /bin/sh; 184 | shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; 185 | }; 186 | 9740EEB61CF901F6004384FC /* Run Script */ = { 187 | isa = PBXShellScriptBuildPhase; 188 | buildActionMask = 2147483647; 189 | files = ( 190 | ); 191 | inputPaths = ( 192 | ); 193 | name = "Run Script"; 194 | outputPaths = ( 195 | ); 196 | runOnlyForDeploymentPostprocessing = 0; 197 | shellPath = /bin/sh; 198 | shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; 199 | }; 200 | /* End PBXShellScriptBuildPhase section */ 201 | 202 | /* Begin PBXSourcesBuildPhase section */ 203 | 97C146EA1CF9000F007C117D /* Sources */ = { 204 | isa = PBXSourcesBuildPhase; 205 | buildActionMask = 2147483647; 206 | files = ( 207 | 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, 208 | 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, 209 | ); 210 | runOnlyForDeploymentPostprocessing = 0; 211 | }; 212 | /* End PBXSourcesBuildPhase section */ 213 | 214 | /* Begin PBXVariantGroup section */ 215 | 97C146FA1CF9000F007C117D /* Main.storyboard */ = { 216 | isa = PBXVariantGroup; 217 | children = ( 218 | 97C146FB1CF9000F007C117D /* Base */, 219 | ); 220 | name = Main.storyboard; 221 | sourceTree = ""; 222 | }; 223 | 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { 224 | isa = PBXVariantGroup; 225 | children = ( 226 | 97C147001CF9000F007C117D /* Base */, 227 | ); 228 | name = LaunchScreen.storyboard; 229 | sourceTree = ""; 230 | }; 231 | /* End PBXVariantGroup section */ 232 | 233 | /* Begin XCBuildConfiguration section */ 234 | 249021D3217E4FDB00AE95B9 /* Profile */ = { 235 | isa = XCBuildConfiguration; 236 | buildSettings = { 237 | ALWAYS_SEARCH_USER_PATHS = NO; 238 | CLANG_ANALYZER_NONNULL = YES; 239 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 240 | CLANG_CXX_LIBRARY = "libc++"; 241 | CLANG_ENABLE_MODULES = YES; 242 | CLANG_ENABLE_OBJC_ARC = YES; 243 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 244 | CLANG_WARN_BOOL_CONVERSION = YES; 245 | CLANG_WARN_COMMA = YES; 246 | CLANG_WARN_CONSTANT_CONVERSION = YES; 247 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 248 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 249 | CLANG_WARN_EMPTY_BODY = YES; 250 | CLANG_WARN_ENUM_CONVERSION = YES; 251 | CLANG_WARN_INFINITE_RECURSION = YES; 252 | CLANG_WARN_INT_CONVERSION = YES; 253 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 254 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 255 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 256 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 257 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 258 | CLANG_WARN_STRICT_PROTOTYPES = YES; 259 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 260 | CLANG_WARN_UNREACHABLE_CODE = YES; 261 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 262 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 263 | COPY_PHASE_STRIP = NO; 264 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 265 | ENABLE_NS_ASSERTIONS = NO; 266 | ENABLE_STRICT_OBJC_MSGSEND = YES; 267 | GCC_C_LANGUAGE_STANDARD = gnu99; 268 | GCC_NO_COMMON_BLOCKS = YES; 269 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 270 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 271 | GCC_WARN_UNDECLARED_SELECTOR = YES; 272 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 273 | GCC_WARN_UNUSED_FUNCTION = YES; 274 | GCC_WARN_UNUSED_VARIABLE = YES; 275 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 276 | MTL_ENABLE_DEBUG_INFO = NO; 277 | SDKROOT = iphoneos; 278 | SUPPORTED_PLATFORMS = iphoneos; 279 | TARGETED_DEVICE_FAMILY = "1,2"; 280 | VALIDATE_PRODUCT = YES; 281 | }; 282 | name = Profile; 283 | }; 284 | 249021D4217E4FDB00AE95B9 /* Profile */ = { 285 | isa = XCBuildConfiguration; 286 | baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; 287 | buildSettings = { 288 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 289 | CLANG_ENABLE_MODULES = YES; 290 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; 291 | ENABLE_BITCODE = NO; 292 | INFOPLIST_FILE = Runner/Info.plist; 293 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 294 | PRODUCT_BUNDLE_IDENTIFIER = luisma.cryptoTracker; 295 | PRODUCT_NAME = "$(TARGET_NAME)"; 296 | SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; 297 | SWIFT_VERSION = 5.0; 298 | VERSIONING_SYSTEM = "apple-generic"; 299 | }; 300 | name = Profile; 301 | }; 302 | 97C147031CF9000F007C117D /* Debug */ = { 303 | isa = XCBuildConfiguration; 304 | buildSettings = { 305 | ALWAYS_SEARCH_USER_PATHS = NO; 306 | CLANG_ANALYZER_NONNULL = YES; 307 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 308 | CLANG_CXX_LIBRARY = "libc++"; 309 | CLANG_ENABLE_MODULES = YES; 310 | CLANG_ENABLE_OBJC_ARC = YES; 311 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 312 | CLANG_WARN_BOOL_CONVERSION = YES; 313 | CLANG_WARN_COMMA = YES; 314 | CLANG_WARN_CONSTANT_CONVERSION = YES; 315 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 316 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 317 | CLANG_WARN_EMPTY_BODY = YES; 318 | CLANG_WARN_ENUM_CONVERSION = YES; 319 | CLANG_WARN_INFINITE_RECURSION = YES; 320 | CLANG_WARN_INT_CONVERSION = YES; 321 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 322 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 323 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 324 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 325 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 326 | CLANG_WARN_STRICT_PROTOTYPES = YES; 327 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 328 | CLANG_WARN_UNREACHABLE_CODE = YES; 329 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 330 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 331 | COPY_PHASE_STRIP = NO; 332 | DEBUG_INFORMATION_FORMAT = dwarf; 333 | ENABLE_STRICT_OBJC_MSGSEND = YES; 334 | ENABLE_TESTABILITY = YES; 335 | GCC_C_LANGUAGE_STANDARD = gnu99; 336 | GCC_DYNAMIC_NO_PIC = NO; 337 | GCC_NO_COMMON_BLOCKS = YES; 338 | GCC_OPTIMIZATION_LEVEL = 0; 339 | GCC_PREPROCESSOR_DEFINITIONS = ( 340 | "DEBUG=1", 341 | "$(inherited)", 342 | ); 343 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 344 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 345 | GCC_WARN_UNDECLARED_SELECTOR = YES; 346 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 347 | GCC_WARN_UNUSED_FUNCTION = YES; 348 | GCC_WARN_UNUSED_VARIABLE = YES; 349 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 350 | MTL_ENABLE_DEBUG_INFO = YES; 351 | ONLY_ACTIVE_ARCH = YES; 352 | SDKROOT = iphoneos; 353 | TARGETED_DEVICE_FAMILY = "1,2"; 354 | }; 355 | name = Debug; 356 | }; 357 | 97C147041CF9000F007C117D /* Release */ = { 358 | isa = XCBuildConfiguration; 359 | buildSettings = { 360 | ALWAYS_SEARCH_USER_PATHS = NO; 361 | CLANG_ANALYZER_NONNULL = YES; 362 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 363 | CLANG_CXX_LIBRARY = "libc++"; 364 | CLANG_ENABLE_MODULES = YES; 365 | CLANG_ENABLE_OBJC_ARC = YES; 366 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 367 | CLANG_WARN_BOOL_CONVERSION = YES; 368 | CLANG_WARN_COMMA = YES; 369 | CLANG_WARN_CONSTANT_CONVERSION = YES; 370 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 371 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 372 | CLANG_WARN_EMPTY_BODY = YES; 373 | CLANG_WARN_ENUM_CONVERSION = YES; 374 | CLANG_WARN_INFINITE_RECURSION = YES; 375 | CLANG_WARN_INT_CONVERSION = YES; 376 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 377 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 378 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 379 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 380 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 381 | CLANG_WARN_STRICT_PROTOTYPES = YES; 382 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 383 | CLANG_WARN_UNREACHABLE_CODE = YES; 384 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 385 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 386 | COPY_PHASE_STRIP = NO; 387 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 388 | ENABLE_NS_ASSERTIONS = NO; 389 | ENABLE_STRICT_OBJC_MSGSEND = YES; 390 | GCC_C_LANGUAGE_STANDARD = gnu99; 391 | GCC_NO_COMMON_BLOCKS = YES; 392 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 393 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 394 | GCC_WARN_UNDECLARED_SELECTOR = YES; 395 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 396 | GCC_WARN_UNUSED_FUNCTION = YES; 397 | GCC_WARN_UNUSED_VARIABLE = YES; 398 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 399 | MTL_ENABLE_DEBUG_INFO = NO; 400 | SDKROOT = iphoneos; 401 | SUPPORTED_PLATFORMS = iphoneos; 402 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 403 | TARGETED_DEVICE_FAMILY = "1,2"; 404 | VALIDATE_PRODUCT = YES; 405 | }; 406 | name = Release; 407 | }; 408 | 97C147061CF9000F007C117D /* Debug */ = { 409 | isa = XCBuildConfiguration; 410 | baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; 411 | buildSettings = { 412 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 413 | CLANG_ENABLE_MODULES = YES; 414 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; 415 | ENABLE_BITCODE = NO; 416 | INFOPLIST_FILE = Runner/Info.plist; 417 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 418 | PRODUCT_BUNDLE_IDENTIFIER = luisma.cryptoTracker; 419 | PRODUCT_NAME = "$(TARGET_NAME)"; 420 | SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; 421 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 422 | SWIFT_VERSION = 5.0; 423 | VERSIONING_SYSTEM = "apple-generic"; 424 | }; 425 | name = Debug; 426 | }; 427 | 97C147071CF9000F007C117D /* Release */ = { 428 | isa = XCBuildConfiguration; 429 | baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; 430 | buildSettings = { 431 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 432 | CLANG_ENABLE_MODULES = YES; 433 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; 434 | ENABLE_BITCODE = NO; 435 | INFOPLIST_FILE = Runner/Info.plist; 436 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 437 | PRODUCT_BUNDLE_IDENTIFIER = luisma.cryptoTracker; 438 | PRODUCT_NAME = "$(TARGET_NAME)"; 439 | SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; 440 | SWIFT_VERSION = 5.0; 441 | VERSIONING_SYSTEM = "apple-generic"; 442 | }; 443 | name = Release; 444 | }; 445 | /* End XCBuildConfiguration section */ 446 | 447 | /* Begin XCConfigurationList section */ 448 | 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { 449 | isa = XCConfigurationList; 450 | buildConfigurations = ( 451 | 97C147031CF9000F007C117D /* Debug */, 452 | 97C147041CF9000F007C117D /* Release */, 453 | 249021D3217E4FDB00AE95B9 /* Profile */, 454 | ); 455 | defaultConfigurationIsVisible = 0; 456 | defaultConfigurationName = Release; 457 | }; 458 | 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { 459 | isa = XCConfigurationList; 460 | buildConfigurations = ( 461 | 97C147061CF9000F007C117D /* Debug */, 462 | 97C147071CF9000F007C117D /* Release */, 463 | 249021D4217E4FDB00AE95B9 /* Profile */, 464 | ); 465 | defaultConfigurationIsVisible = 0; 466 | defaultConfigurationName = Release; 467 | }; 468 | /* End XCConfigurationList section */ 469 | }; 470 | rootObject = 97C146E61CF9000F007C117D /* Project object */; 471 | } 472 | -------------------------------------------------------------------------------- /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 | 8 | -------------------------------------------------------------------------------- /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/LuisMaGit/flutter-crypto/4f098d6eb027a92393b8d15efd12615f8f795059/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/LuisMaGit/flutter-crypto/4f098d6eb027a92393b8d15efd12615f8f795059/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/LuisMaGit/flutter-crypto/4f098d6eb027a92393b8d15efd12615f8f795059/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/LuisMaGit/flutter-crypto/4f098d6eb027a92393b8d15efd12615f8f795059/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/LuisMaGit/flutter-crypto/4f098d6eb027a92393b8d15efd12615f8f795059/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/LuisMaGit/flutter-crypto/4f098d6eb027a92393b8d15efd12615f8f795059/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/LuisMaGit/flutter-crypto/4f098d6eb027a92393b8d15efd12615f8f795059/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/LuisMaGit/flutter-crypto/4f098d6eb027a92393b8d15efd12615f8f795059/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/LuisMaGit/flutter-crypto/4f098d6eb027a92393b8d15efd12615f8f795059/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/LuisMaGit/flutter-crypto/4f098d6eb027a92393b8d15efd12615f8f795059/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/LuisMaGit/flutter-crypto/4f098d6eb027a92393b8d15efd12615f8f795059/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/LuisMaGit/flutter-crypto/4f098d6eb027a92393b8d15efd12615f8f795059/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/LuisMaGit/flutter-crypto/4f098d6eb027a92393b8d15efd12615f8f795059/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/LuisMaGit/flutter-crypto/4f098d6eb027a92393b8d15efd12615f8f795059/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/LuisMaGit/flutter-crypto/4f098d6eb027a92393b8d15efd12615f8f795059/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "LaunchImage.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "LaunchImage@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "LaunchImage@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LuisMaGit/flutter-crypto/4f098d6eb027a92393b8d15efd12615f8f795059/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LuisMaGit/flutter-crypto/4f098d6eb027a92393b8d15efd12615f8f795059/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LuisMaGit/flutter-crypto/4f098d6eb027a92393b8d15efd12615f8f795059/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 | -------------------------------------------------------------------------------- /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 | CFBundleName 14 | crypto_tracker 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | $(FLUTTER_BUILD_NAME) 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | $(FLUTTER_BUILD_NUMBER) 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIMainStoryboardFile 28 | Main 29 | UISupportedInterfaceOrientations 30 | 31 | UIInterfaceOrientationPortrait 32 | UIInterfaceOrientationLandscapeLeft 33 | UIInterfaceOrientationLandscapeRight 34 | 35 | UISupportedInterfaceOrientations~ipad 36 | 37 | UIInterfaceOrientationPortrait 38 | UIInterfaceOrientationPortraitUpsideDown 39 | UIInterfaceOrientationLandscapeLeft 40 | UIInterfaceOrientationLandscapeRight 41 | 42 | UIViewControllerBasedStatusBarAppearance 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /ios/Runner/Runner-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | #import "GeneratedPluginRegistrant.h" 2 | -------------------------------------------------------------------------------- /lib/data/crypto_data_service/crypto_data_api_contracts.dart: -------------------------------------------------------------------------------- 1 | abstract class CryptoDataApiContracts { 2 | //coingecko API KEY 3 | static const apiKey = 'YOUR_API_KEY_HERE'; 4 | static const nameApiKey = 'x-cg-demo-api-key'; 5 | 6 | static const url = 'api.coingecko.com'; 7 | static String endpoint(String crypto) => 8 | '/api/v3/coins/$crypto/market_chart/range'; 9 | 10 | 11 | static Map queryParameters({ 12 | required String fiat, 13 | required String from, 14 | required String to, 15 | }) { 16 | return { 17 | 'vs_currency': fiat, 18 | 'from': from, 19 | 'to': to, 20 | }; 21 | } 22 | } 23 | 24 | 25 | -------------------------------------------------------------------------------- /lib/data/crypto_data_service/crypto_data_constants.dart: -------------------------------------------------------------------------------- 1 | enum TimeSpan { 2 | Day, 3 | Week, 4 | TwoWeeks, 5 | Month, 6 | Year, 7 | } 8 | 9 | enum CryptoCode { 10 | BTC(uiCode: 'BTC', apiCode: 'bitcoin'), 11 | ETH(uiCode: 'ETH', apiCode: 'ethereum'), 12 | XRP(uiCode: 'XRP', apiCode: 'ripple'), 13 | XMR(uiCode: 'XMR', apiCode: 'monero'), 14 | DASH(uiCode: 'DASH', apiCode: 'dash'), 15 | USDT(uiCode: 'USDT', apiCode: 'tether'), 16 | DOGE(uiCode: 'DOGE', apiCode: 'dogecoin'), 17 | LTC(uiCode: 'LTC', apiCode: 'litecoin'), 18 | ADA(uiCode: 'ADA', apiCode: 'cardano'); 19 | 20 | const CryptoCode({ 21 | required this.uiCode, 22 | required this.apiCode, 23 | }); 24 | 25 | final String uiCode; 26 | final String apiCode; 27 | } 28 | 29 | enum FiatCode { 30 | USD(uiCode: 'USD', apiCode: 'usd'), 31 | EUR(uiCode: 'EUR', apiCode: 'eur'), 32 | CAD(uiCode: 'CAD', apiCode: 'cad'); 33 | 34 | const FiatCode({ 35 | required this.uiCode, 36 | required this.apiCode, 37 | }); 38 | 39 | final String uiCode; 40 | final String apiCode; 41 | } 42 | -------------------------------------------------------------------------------- /lib/data/crypto_data_service/crypto_data_model.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | 3 | class CryptoResponseModel { 4 | List cryptoData = []; 5 | 6 | CryptoResponseModel({ 7 | required this.cryptoData, 8 | }); 9 | 10 | factory CryptoResponseModel.fromMap({ 11 | required Map map, 12 | }) { 13 | final prices = map['prices'] as List; 14 | List output = []; 15 | prices.forEach( 16 | (price) { 17 | output.add( 18 | CryptoModel( 19 | prettyTime: '', 20 | dateTimeMillisecondsSinceEpoch: price[0], 21 | dateTime: DateTime.fromMillisecondsSinceEpoch( 22 | price[0], 23 | ), 24 | price: price[1], 25 | ), 26 | ); 27 | }, 28 | ); 29 | 30 | return CryptoResponseModel( 31 | cryptoData: output, 32 | ); 33 | } 34 | 35 | factory CryptoResponseModel.fromJson({ 36 | required String source, 37 | }) { 38 | return CryptoResponseModel.fromMap( 39 | map: json.decode(source), 40 | ); 41 | } 42 | } 43 | 44 | class CryptoModel { 45 | String prettyTime; 46 | double price; 47 | int dateTimeMillisecondsSinceEpoch; 48 | DateTime dateTime; 49 | 50 | CryptoModel({ 51 | required this.prettyTime, 52 | required this.price, 53 | required this.dateTimeMillisecondsSinceEpoch, 54 | required this.dateTime, 55 | }); 56 | 57 | CryptoModel copyWith({ 58 | String? prettyTime, 59 | double? price, 60 | int? dateTimeMillisecondsSinceEpoch, 61 | DateTime? dateTime, 62 | }) { 63 | return CryptoModel( 64 | prettyTime: prettyTime ?? this.prettyTime, 65 | price: price ?? this.price, 66 | dateTimeMillisecondsSinceEpoch: 67 | dateTimeMillisecondsSinceEpoch ?? this.dateTimeMillisecondsSinceEpoch, 68 | dateTime: dateTime ?? this.dateTime, 69 | ); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /lib/data/crypto_data_service/crypto_data_service.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | import 'dart:io'; 3 | 4 | import 'package:crypto_tracker/data/crypto_data_service/crypto_data_api_contracts.dart'; 5 | import 'package:crypto_tracker/data/crypto_data_service/crypto_data_constants.dart'; 6 | import 'package:crypto_tracker/data/crypto_data_service/crypto_data_model.dart'; 7 | 8 | class CryptoDataService { 9 | (String from, String to) _dateRangeRequestBasedOnTimeSpan(TimeSpan span) { 10 | final now = DateTime.now().toLocal(); 11 | switch (span) { 12 | case TimeSpan.Day: 13 | final nowOnlyDateAndHour = DateTime( 14 | now.year, 15 | now.month, 16 | now.day, 17 | now.hour, 18 | ); 19 | final startDateTime = nowOnlyDateAndHour.subtract(Duration(hours: 24)); 20 | final from = startDateTime.millisecondsSinceEpoch ~/ 1000; 21 | final to = nowOnlyDateAndHour.millisecondsSinceEpoch ~/ 1000; 22 | return ( 23 | from.toString(), 24 | to.toString(), 25 | ); 26 | case TimeSpan.Week: 27 | final nowOnlyDate = DateTime(now.year, now.month, now.day); 28 | final start = nowOnlyDate.subtract(Duration(days: 7)); 29 | final from = start.millisecondsSinceEpoch ~/ 1000; 30 | final to = nowOnlyDate.millisecondsSinceEpoch ~/ 1000; 31 | return ( 32 | from.toString(), 33 | to.toString(), 34 | ); 35 | case TimeSpan.TwoWeeks: 36 | final nowOnlyDate = DateTime(now.year, now.month, now.day); 37 | final start = nowOnlyDate.subtract(Duration(days: 14)); 38 | final from = start.millisecondsSinceEpoch ~/ 1000; 39 | final to = nowOnlyDate.millisecondsSinceEpoch ~/ 1000; 40 | return ( 41 | from.toString(), 42 | to.toString(), 43 | ); 44 | case TimeSpan.Month: 45 | final nowOnlyDate = DateTime(now.year, now.month, now.day); 46 | final start = nowOnlyDate.subtract(Duration(days: 30)); 47 | final from = start.millisecondsSinceEpoch ~/ 1000; 48 | final to = nowOnlyDate.millisecondsSinceEpoch ~/ 1000; 49 | return ( 50 | from.toString(), 51 | to.toString(), 52 | ); 53 | case TimeSpan.Year: 54 | final nowOnlyDate = DateTime(now.year, now.month, now.day); 55 | final start = nowOnlyDate.subtract(Duration(days: 364)); 56 | final from = start.millisecondsSinceEpoch ~/ 1000; 57 | final to = nowOnlyDate.millisecondsSinceEpoch ~/ 1000; 58 | return ( 59 | from.toString(), 60 | to.toString(), 61 | ); 62 | } 63 | } 64 | 65 | static String _prettyTimePutCero(int time) { 66 | final timeStr = time.toString(); 67 | return timeStr.length == 1 ? '0$timeStr' : timeStr; 68 | } 69 | 70 | static String _prettyTime({ 71 | required int timeMillisecondsSinceEpoch, 72 | required TimeSpan timeSpan, 73 | }) { 74 | final date = DateTime.fromMillisecondsSinceEpoch( 75 | timeMillisecondsSinceEpoch, 76 | ); 77 | 78 | final base = 79 | '${_prettyTimePutCero(date.year)}-${_prettyTimePutCero(date.month)}-${_prettyTimePutCero(date.day)}'; 80 | if (timeSpan == TimeSpan.Day) { 81 | return '$base\n${_prettyTimePutCero(date.hour)}:${_prettyTimePutCero(date.minute)}'; 82 | } 83 | 84 | return base; 85 | } 86 | 87 | List _extractFromResponseNeedItDataBaseOnTimeSpan({ 88 | required List cryptos, 89 | required TimeSpan timeSpan, 90 | }) { 91 | var output = []; 92 | final crytosReversed = cryptos.reversed; 93 | switch (timeSpan) { 94 | case TimeSpan.Day: 95 | var hour = -1; 96 | for (var crypto in crytosReversed) { 97 | if (crypto.dateTime.hour != hour) { 98 | hour = crypto.dateTime.hour; 99 | output.add( 100 | crypto.copyWith( 101 | prettyTime: _prettyTime( 102 | timeMillisecondsSinceEpoch: 103 | crypto.dateTimeMillisecondsSinceEpoch, 104 | timeSpan: timeSpan, 105 | ), 106 | ), 107 | ); 108 | } 109 | } 110 | break; 111 | case TimeSpan.TwoWeeks: 112 | case TimeSpan.Week: 113 | case TimeSpan.Month: 114 | var day = -1; 115 | for (var crypto in crytosReversed) { 116 | if (crypto.dateTime.day != day) { 117 | day = crypto.dateTime.day; 118 | output.add( 119 | crypto.copyWith( 120 | prettyTime: _prettyTime( 121 | timeMillisecondsSinceEpoch: 122 | crypto.dateTimeMillisecondsSinceEpoch, 123 | timeSpan: timeSpan, 124 | ), 125 | ), 126 | ); 127 | } 128 | } 129 | case TimeSpan.Year: 130 | var month = -1; 131 | for (var crypto in crytosReversed) { 132 | if (crypto.dateTime.month != month) { 133 | month = crypto.dateTime.month; 134 | output.add( 135 | crypto.copyWith( 136 | prettyTime: _prettyTime( 137 | timeMillisecondsSinceEpoch: 138 | crypto.dateTimeMillisecondsSinceEpoch, 139 | timeSpan: timeSpan, 140 | ), 141 | ), 142 | ); 143 | } 144 | } 145 | } 146 | return output.reversed.toList(); 147 | } 148 | 149 | Future?> getCryptoData({ 150 | required TimeSpan timeSpan, 151 | required CryptoCode cryptoCode, 152 | required FiatCode fiatCode, 153 | }) async { 154 | final (start, end) = _dateRangeRequestBasedOnTimeSpan(timeSpan); 155 | final uri = Uri.https( 156 | CryptoDataApiContracts.url, 157 | CryptoDataApiContracts.endpoint(cryptoCode.apiCode), 158 | CryptoDataApiContracts.queryParameters( 159 | fiat: fiatCode.apiCode, 160 | from: start, 161 | to: end, 162 | ), 163 | ); 164 | try { 165 | final client = HttpClient(); 166 | final request = await client.getUrl(uri); 167 | request.headers.add( 168 | CryptoDataApiContracts.nameApiKey, 169 | CryptoDataApiContracts.apiKey, 170 | ); 171 | final response = await request.close(); 172 | if (response.statusCode == 200) { 173 | final responseBody = await response.transform(utf8.decoder).join(); 174 | final fullCryptoData = CryptoResponseModel.fromJson( 175 | source: responseBody, 176 | ).cryptoData; 177 | return _extractFromResponseNeedItDataBaseOnTimeSpan( 178 | cryptos: fullCryptoData, 179 | timeSpan: timeSpan, 180 | ); 181 | } 182 | return null; 183 | } catch (e) { 184 | return null; 185 | } 186 | } 187 | } 188 | -------------------------------------------------------------------------------- /lib/data/theme_service.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | class ThemeService { 4 | factory ThemeService() { 5 | return _instance; 6 | } 7 | ThemeService._privateConstructor(); 8 | static final ThemeService _instance = ThemeService._privateConstructor(); 9 | 10 | late StreamController themeController = 11 | StreamController.broadcast(); 12 | ThemeModeCrypto _themeMode = ThemeModeCrypto.Dark; 13 | ThemeModeCrypto get themeMode => _themeMode; 14 | 15 | void _setThemeMode() { 16 | _themeMode = _themeMode == ThemeModeCrypto.Light 17 | ? ThemeModeCrypto.Dark 18 | : ThemeModeCrypto.Light; 19 | } 20 | 21 | void switchTheme() { 22 | _setThemeMode(); 23 | themeController.add(_themeMode); 24 | } 25 | 26 | void dispose() { 27 | themeController.close(); 28 | } 29 | } 30 | 31 | enum ThemeModeCrypto { 32 | Dark, 33 | Light, 34 | } 35 | -------------------------------------------------------------------------------- /lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'package:crypto_tracker/ui/features/theme_builder/theme_builder.dart'; 2 | import 'package:crypto_tracker/ui/features/main_view/main_view.dart'; 3 | import 'package:flutter/material.dart'; 4 | 5 | void main() { 6 | runApp(CryptoTracker()); 7 | } 8 | 9 | class CryptoTracker extends StatelessWidget { 10 | @override 11 | Widget build(BuildContext context) { 12 | return ThemeBuilder( 13 | builder: (theme) { 14 | return MaterialApp( 15 | title: 'CryptoTracker', 16 | theme: theme, 17 | debugShowCheckedModeBanner: false, 18 | home: MainView(), 19 | ); 20 | }, 21 | ); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /lib/ui/features/base_view/base_view.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/foundation.dart'; 2 | import 'package:flutter/widgets.dart'; 3 | 4 | import 'package:crypto_tracker/ui/features/base_view/base_vm.dart'; 5 | 6 | class BaseViewBuilder extends StatefulWidget { 7 | final T viewModel; 8 | final void Function(T viewModel)? initViewModel; 9 | final Widget Function(BuildContext context) builder; 10 | 11 | const BaseViewBuilder({ 12 | Key? key, 13 | this.initViewModel, 14 | required this.viewModel, 15 | required this.builder, 16 | }) : super(key: key); 17 | 18 | @override 19 | _BaseViewBuilderState createState() => _BaseViewBuilderState(); 20 | } 21 | 22 | class _BaseViewBuilderState 23 | extends State> { 24 | @override 25 | void initState() { 26 | super.initState(); 27 | if (widget.initViewModel != null) { 28 | widget.initViewModel!(widget.viewModel); 29 | } 30 | } 31 | 32 | @override 33 | Widget build(BuildContext context) { 34 | return BaseViewInheretedWidget( 35 | viewModel: widget.viewModel, 36 | child: _ProxyBaseView( 37 | viewModel: widget.viewModel, 38 | builder: widget.builder, 39 | ), 40 | ); 41 | } 42 | } 43 | 44 | class BaseViewInheretedWidget extends InheritedWidget { 45 | final Widget child; 46 | final T viewModel; 47 | 48 | static BaseViewInheretedWidget of( 49 | BuildContext context) => 50 | context.dependOnInheritedWidgetOfExactType()!; 51 | 52 | BaseViewInheretedWidget({ 53 | required this.child, 54 | required this.viewModel, 55 | }) : super(child: child); 56 | 57 | @override 58 | bool updateShouldNotify(covariant InheritedWidget oldWidget) => true; 59 | } 60 | 61 | class _ProxyBaseView extends _BaseViewListener { 62 | final Listenable viewModel; 63 | final Widget Function(BuildContext context) builder; 64 | _ProxyBaseView({ 65 | Key? key, 66 | required this.viewModel, 67 | required this.builder, 68 | }) : super(key: key, listenable: viewModel); 69 | 70 | @override 71 | Widget build(BuildContext context) => builder(context); 72 | } 73 | 74 | abstract class _BaseViewListener extends StatefulWidget { 75 | final Listenable listenable; 76 | const _BaseViewListener({ 77 | Key? key, 78 | required this.listenable, 79 | }) : super(key: key); 80 | 81 | @override 82 | __BaseViewListenerState createState() => __BaseViewListenerState(); 83 | 84 | @protected 85 | Widget build(BuildContext context); 86 | 87 | @override 88 | void debugFillProperties(DiagnosticPropertiesBuilder properties) { 89 | super.debugFillProperties(properties); 90 | properties 91 | .add(DiagnosticsProperty('Base LuisMa View', listenable)); 92 | } 93 | } 94 | 95 | class __BaseViewListenerState extends State<_BaseViewListener> { 96 | @override 97 | void initState() { 98 | super.initState(); 99 | widget.listenable.addListener(_handleChange); 100 | } 101 | 102 | @override 103 | void didUpdateWidget(covariant _BaseViewListener oldWidget) { 104 | super.didUpdateWidget(oldWidget); 105 | if (widget.listenable != oldWidget.listenable) { 106 | oldWidget.listenable.removeListener(_handleChange); 107 | widget.listenable.addListener(_handleChange); 108 | } 109 | } 110 | 111 | @override 112 | void dispose() { 113 | widget.listenable.removeListener(_handleChange); 114 | super.dispose(); 115 | } 116 | 117 | void _handleChange() { 118 | setState(() {}); 119 | } 120 | 121 | @override 122 | Widget build(BuildContext context) => widget.build(context); 123 | } 124 | -------------------------------------------------------------------------------- /lib/ui/features/base_view/base_view_state.dart: -------------------------------------------------------------------------------- 1 | enum BaseViewState{ 2 | Bussy, 3 | Iddle, 4 | } -------------------------------------------------------------------------------- /lib/ui/features/base_view/base_vm.dart: -------------------------------------------------------------------------------- 1 | import 'package:crypto_tracker/ui/features/base_view/base_view_state.dart'; 2 | import 'package:flutter/foundation.dart'; 3 | 4 | abstract class BaseVM extends ChangeNotifier { 5 | BaseViewState state = BaseViewState.Bussy; 6 | 7 | set setState(BaseViewState value) { 8 | state = value; 9 | notifyListeners(); 10 | } 11 | } 12 | 13 | 14 | -------------------------------------------------------------------------------- /lib/ui/features/main_view/main_view.dart: -------------------------------------------------------------------------------- 1 | import 'package:crypto_tracker/data/crypto_data_service/crypto_data_constants.dart'; 2 | import 'package:crypto_tracker/ui/features/base_view/base_view.dart'; 3 | import 'package:crypto_tracker/ui/features/base_view/base_view_state.dart'; 4 | import 'package:crypto_tracker/ui/ui_constants/labels.dart'; 5 | import 'package:crypto_tracker/ui/widgets/crypto_card.dart'; 6 | import 'package:crypto_tracker/ui/widgets/graph.dart'; 7 | import 'package:crypto_tracker/ui/features/main_view/main_view_vm.dart'; 8 | import 'package:crypto_tracker/ui/widgets/modal_error.dart'; 9 | import 'package:crypto_tracker/ui/widgets/modal_fiat_selector.dart'; 10 | import 'package:crypto_tracker/ui/ui_helpers.dart'; 11 | import 'package:flutter/material.dart'; 12 | 13 | class MainView extends StatefulWidget { 14 | @override 15 | _MainViewState createState() => _MainViewState(); 16 | } 17 | 18 | class _MainViewState extends State 19 | with SingleTickerProviderStateMixin { 20 | final model = MainViewVM(); 21 | late AnimationController _controllerYFactor; 22 | late CurvedAnimation _curveYAnimation; 23 | late Animation _animationYFactor; 24 | late ScrollController _scrollController; 25 | 26 | @override 27 | void initState() { 28 | super.initState(); 29 | _controllerYFactor = 30 | AnimationController(vsync: this, duration: Duration(milliseconds: 500)); 31 | _animationYFactor = 32 | Tween(begin: 0, end: 1).animate(_controllerYFactor); 33 | _curveYAnimation = 34 | CurvedAnimation(curve: Curves.easeIn, parent: _animationYFactor); 35 | } 36 | 37 | Future _startAnimation() async { 38 | await _controllerYFactor.forward(); 39 | } 40 | 41 | Future _reverseAnimation() async { 42 | await _controllerYFactor.reverse(); 43 | } 44 | 45 | @override 46 | void dispose() { 47 | super.dispose(); 48 | _controllerYFactor.dispose(); 49 | } 50 | 51 | @override 52 | Widget build(BuildContext context) { 53 | final _w = MediaQuery.of(context).size.width; 54 | 55 | void _scrollMoveTo(CryptoCode c) { 56 | final offset = c.index * _w; 57 | _scrollController.animateTo( 58 | offset, 59 | duration: Duration(milliseconds: 200), 60 | curve: Curves.easeIn, 61 | ); 62 | } 63 | 64 | Future _showFiatDialog(FiatCode selected) async { 65 | return await showDialog( 66 | context: context, 67 | builder: (context) { 68 | return ModalFiatSelector(selected: selected); 69 | }); 70 | } 71 | 72 | Future _showErrorDialog() async { 73 | await showDialog( 74 | context: context, 75 | builder: (context) { 76 | return ModalError(); 77 | }); 78 | } 79 | 80 | return SafeArea( 81 | child: Scaffold( 82 | body: BaseViewBuilder( 83 | initViewModel: (model) { 84 | _scrollController = ScrollController( 85 | initialScrollOffset: _w * model.cryptoSelected.index) 86 | ..addListener(() { 87 | final position = _scrollController.position.pixels; 88 | final positionTruncated = position.toInt(); 89 | final wTruncated = _w.toInt(); 90 | if (positionTruncated % wTruncated == 0) { 91 | final idx = positionTruncated ~/ wTruncated; 92 | model.changeCrypto(CryptoCode.values[idx]); 93 | } 94 | }); 95 | 96 | model.initMainViewVM( 97 | startAnimation: _startAnimation, 98 | reverseAnimation: _reverseAnimation, 99 | scrollMoveTo: _scrollMoveTo, 100 | showFiatDialog: _showFiatDialog, 101 | showErrorDialog: _showErrorDialog, 102 | ); 103 | }, 104 | viewModel: model, 105 | builder: (context) { 106 | final graphModel = GraphModel( 107 | prefixBigText: UIHelper.fiat[model.fiatSelected] ?? '', 108 | plots: [], 109 | ); 110 | 111 | if (model.state == BaseViewState.Bussy) { 112 | return _MainBody( 113 | disable: true, 114 | scrollController: _scrollController, 115 | graphModel: graphModel.copyWith( 116 | bigText: Labels.mainViewloading( 117 | UIHelper.cryptoName[model.cryptoSelected] ?? '', 118 | ), 119 | ), 120 | ); 121 | } 122 | if (model.state == BaseViewState.Iddle) { 123 | final plots = UIHelper.getPlotsFromCryptoModel( 124 | model.detailsSelected.cryptoData, 125 | ); 126 | final baseText = '1 ${model.cryptoSelected.uiCode} ≈'; 127 | return _MainBody( 128 | curvedAnimation: _curveYAnimation, 129 | scrollController: _scrollController, 130 | graphModel: 131 | graphModel.copyWith(plots: plots, baseText: baseText)); 132 | } 133 | return SizedBox(); 134 | }), 135 | ), 136 | ); 137 | } 138 | } 139 | 140 | class _MainBody extends StatelessWidget { 141 | final GraphModel graphModel; 142 | final bool disable; 143 | final CurvedAnimation? curvedAnimation; 144 | final ScrollController scrollController; 145 | 146 | const _MainBody({ 147 | Key? key, 148 | required this.graphModel, 149 | required this.scrollController, 150 | this.curvedAnimation, 151 | this.disable = false, 152 | }) : super(key: key); 153 | 154 | @override 155 | Widget build(BuildContext context) { 156 | final size = MediaQuery.of(context).size; 157 | final _h = size.height; 158 | final _w = size.width; 159 | final _vm = BaseViewInheretedWidget.of(context).viewModel; 160 | final _spacer = SizedBox(height: 16); 161 | final theme = Theme.of(context); 162 | return Center( 163 | child: ListView( 164 | shrinkWrap: true, 165 | padding: const EdgeInsets.only(bottom: 20), 166 | physics: BouncingScrollPhysics(), 167 | children: [ 168 | //App Bar 169 | Row( 170 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 171 | children: [ 172 | Expanded( 173 | flex: 8, 174 | child: Padding( 175 | padding: const EdgeInsets.only(left: 12.0), 176 | child: Text(Labels.appName(), 177 | style: theme 178 | .textTheme 179 | .headlineLarge! 180 | .copyWith(fontSize: 34)), 181 | ), 182 | ), 183 | _ButtonAppBarWrapper( 184 | onTap: disable ? () {} : _vm.openFiatDialog, 185 | child: Text(UIHelper.fiat[_vm.fiatSelected] ?? '', 186 | style: theme.textTheme.headlineLarge)), 187 | _ButtonAppBarWrapper( 188 | onTap: _vm.changeTheme, 189 | child: Icon(Icons.lightbulb_outline_rounded)), 190 | ], 191 | ), 192 | SizedBox(height: 10), 193 | //Dates 194 | SizedBox( 195 | height: 40, 196 | child: Row( 197 | children: [ 198 | Expanded( 199 | child: ListView( 200 | scrollDirection: Axis.horizontal, 201 | padding: const EdgeInsets.only(left: 10), 202 | physics: BouncingScrollPhysics(), 203 | children: TimeSpan.values.map((t) { 204 | return Center( 205 | child: GestureDetector( 206 | onTap: disable ? () {} : () => _vm.changeDate(t), 207 | child: Container( 208 | margin: const EdgeInsets.only(right: 20), 209 | padding: const EdgeInsets.symmetric( 210 | horizontal: 10, vertical: 5), 211 | decoration: BoxDecoration( 212 | color: t == _vm.timeSpanSelected 213 | ? UIHelper 214 | .cyrptoColor[_vm.cryptoSelected]! 215 | .withOpacity(.5) 216 | : theme 217 | .colorScheme 218 | .onBackground, 219 | borderRadius: BorderRadius.circular(14)), 220 | child: Text(UIHelper.date[t] ?? '', 221 | style: theme 222 | .textTheme 223 | .bodyLarge! 224 | .copyWith( 225 | color: theme 226 | .colorScheme 227 | .secondary)), 228 | ), 229 | ), 230 | ); 231 | }).toList(), 232 | )), 233 | ], 234 | )), 235 | //Graph 236 | Container( 237 | height: _h * .3, 238 | constraints: BoxConstraints(minHeight: 200, maxHeight: 400), 239 | child: AnimatedBuilder( 240 | animation: curvedAnimation ?? AlwaysStoppedAnimation(0), 241 | builder: (context, child) { 242 | return Graph( 243 | yMoveFactor: curvedAnimation?.value ?? 0, 244 | graphData: graphModel, 245 | primaryColor: UIHelper.cyrptoColor[_vm.cryptoSelected] ?? 246 | theme.colorScheme.primary, 247 | backGroundColor: theme.canvasColor, 248 | bigTextColor: theme.colorScheme.primary, 249 | cardColor: theme.colorScheme.onBackground, 250 | primaryTextStyle: theme.textTheme.headlineLarge, 251 | secondaryTextStyle: theme.textTheme.bodySmall, 252 | ); 253 | }), 254 | ), 255 | _spacer, 256 | //Coins 257 | SizedBox( 258 | height: 45, 259 | child: Row( 260 | children: [ 261 | Expanded( 262 | child: ListView( 263 | physics: BouncingScrollPhysics(), 264 | scrollDirection: Axis.horizontal, 265 | padding: const EdgeInsets.symmetric(horizontal: 12), 266 | children: CryptoCode.values 267 | .map((c) => GestureDetector( 268 | onTap: () => _vm.changeCryptoByTap(c), 269 | child: Container( 270 | margin: const EdgeInsets.only(right: 16), 271 | padding: const EdgeInsets.only(right: 16), 272 | decoration: BoxDecoration( 273 | borderRadius: BorderRadius.circular(23), 274 | color: c == _vm.cryptoSelected 275 | ? UIHelper.cyrptoColor[c]! 276 | .withOpacity(.5) 277 | : theme 278 | .colorScheme 279 | .onBackground), 280 | child: Row( 281 | children: [ 282 | CircleAvatar( 283 | radius: 20, 284 | backgroundColor: theme 285 | .colorScheme 286 | .onBackground, 287 | child: Image.asset( 288 | UIHelper.cryptoPic[c]!)), 289 | SizedBox(width: 10), 290 | Text(UIHelper.cryptoName[c] ?? '', 291 | style: theme 292 | .textTheme 293 | .bodyLarge) 294 | ], 295 | ), 296 | ), 297 | )) 298 | .toList())) 299 | ], 300 | ), 301 | ), 302 | _spacer, 303 | //List Cards 304 | SizedBox( 305 | height: 260, 306 | child: ListView.builder( 307 | controller: scrollController, 308 | physics: disable 309 | ? const NeverScrollableScrollPhysics() 310 | : const PageScrollPhysics(parent: BouncingScrollPhysics()), 311 | scrollDirection: Axis.horizontal, 312 | itemCount: CryptoCode.values.length, 313 | itemBuilder: (context, index) { 314 | final cryptoCode = CryptoCode.values[index]; 315 | return SizedBox( 316 | width: _w, 317 | child: Center( 318 | child: CriptoCard( 319 | animation: _vm.cryptoSelected == cryptoCode 320 | ? curvedAnimation 321 | : null, 322 | cryptoCode: cryptoCode, 323 | colorCrypto: UIHelper.cyrptoColor[cryptoCode] ?? 324 | theme.colorScheme.secondary, 325 | decrease: _vm.detailsOf(cryptoCode).percentage == 0 326 | ? null 327 | : _vm.detailsOf(cryptoCode).percentage.isNegative, 328 | codeStr: cryptoCode.uiCode, 329 | percentage: _vm.detailsOf(cryptoCode).percentageStr, 330 | price: _vm.detailsOf(cryptoCode).lastStr, 331 | fiat: UIHelper.fiat[_vm.fiatSelected] ?? '', 332 | plots: UIHelper.getPlotsFromCryptoModel( 333 | _vm.detailsOf(cryptoCode).cryptoData), 334 | ), 335 | ), 336 | ); 337 | }, 338 | ), 339 | ), 340 | ], 341 | ), 342 | ); 343 | } 344 | } 345 | 346 | class _ButtonAppBarWrapper extends StatelessWidget { 347 | final Widget child; 348 | final VoidCallback onTap; 349 | 350 | const _ButtonAppBarWrapper( 351 | {Key? key, required this.child, required this.onTap}) 352 | : super(key: key); 353 | @override 354 | Widget build(BuildContext context) { 355 | return SizedBox( 356 | width: 50, 357 | child: TextButton( 358 | onPressed: onTap, 359 | style: ButtonStyle( 360 | padding: MaterialStateProperty.all( 361 | EdgeInsets.only(right: 12))), 362 | child: child)); 363 | } 364 | } 365 | -------------------------------------------------------------------------------- /lib/ui/features/main_view/main_view_data.dart: -------------------------------------------------------------------------------- 1 | import 'package:crypto_tracker/data/crypto_data_service/crypto_data_constants.dart'; 2 | import 'package:crypto_tracker/data/crypto_data_service/crypto_data_model.dart'; 3 | 4 | typedef AnimationCallback = Future Function(); 5 | typedef ScrollMoveCallback = void Function(CryptoCode c); 6 | typedef ShowFiatDialogCallback = Future Function(FiatCode c); 7 | typedef ShowErrorDialogCallback = Future Function(); 8 | 9 | 10 | class CryptoViewData { 11 | List cryptoData; 12 | double percentage; 13 | String percentageStr; 14 | double last; 15 | String lastStr; 16 | 17 | CryptoViewData({ 18 | required this.cryptoData, 19 | this.percentage = 0.0, 20 | this.percentageStr = '', 21 | this.last = 0.0, 22 | this.lastStr = '', 23 | }); 24 | } 25 | -------------------------------------------------------------------------------- /lib/ui/features/main_view/main_view_vm.dart: -------------------------------------------------------------------------------- 1 | import 'package:crypto_tracker/data/crypto_data_service/crypto_data_constants.dart'; 2 | import 'package:crypto_tracker/data/crypto_data_service/crypto_data_model.dart'; 3 | import 'package:crypto_tracker/data/crypto_data_service/crypto_data_service.dart'; 4 | import 'package:crypto_tracker/data/theme_service.dart'; 5 | import 'package:crypto_tracker/ui/features/base_view/base_view_state.dart'; 6 | import 'package:crypto_tracker/ui/features/base_view/base_vm.dart'; 7 | import 'package:crypto_tracker/ui/features/main_view/main_view_data.dart'; 8 | 9 | class MainViewVM extends BaseVM { 10 | final _themeService = ThemeService(); 11 | final _cryptoDataService = CryptoDataService(); 12 | 13 | CryptoViewData get detailsSelected => 14 | _fullCryptoDataModel[cryptoSelected]![timeSpanSelected]![fiatSelected]!; 15 | 16 | CryptoViewData detailsOf(CryptoCode cryptoCode) => 17 | _fullCryptoDataModel[cryptoCode]![timeSpanSelected]![fiatSelected]!; 18 | 19 | CryptoCode cryptoSelected = CryptoCode.BTC; 20 | TimeSpan timeSpanSelected = TimeSpan.Day; 21 | FiatCode fiatSelected = FiatCode.USD; 22 | 23 | late Map>> 24 | _fullCryptoDataModel; 25 | late AnimationCallback _startAnimation; 26 | late AnimationCallback _reverseAnimation; 27 | late ScrollMoveCallback _scrollMoveTo; 28 | late ShowFiatDialogCallback _showFiatDialog; 29 | late ShowErrorDialogCallback _showErrorDialog; 30 | 31 | void _setDataCryptoSelected(List data) { 32 | final last = data.last.price; 33 | final lastStr = last.toStringAsFixed(3); 34 | final first = data.first.price; 35 | final percentage = (last / first) * (first > last ? -1 : 1); 36 | final percentageStr = percentage.toStringAsFixed(3); 37 | 38 | _fullCryptoDataModel[cryptoSelected]![timeSpanSelected]![fiatSelected]! 39 | ..last = last 40 | ..lastStr = lastStr 41 | ..percentage = percentage 42 | ..percentageStr = percentageStr 43 | ..cryptoData.addAll(data); 44 | } 45 | 46 | Future _fetchData() async { 47 | final data = await _cryptoDataService.getCryptoData( 48 | timeSpan: timeSpanSelected, 49 | cryptoCode: cryptoSelected, 50 | fiatCode: fiatSelected, 51 | ); 52 | 53 | if (data != null) { 54 | _setDataCryptoSelected(data); 55 | setState = BaseViewState.Iddle; 56 | _startAnimation(); 57 | return; 58 | } 59 | 60 | await _showErrorDialog(); 61 | _fetchData(); 62 | } 63 | 64 | Future _handleChange() async { 65 | if (_fullCryptoDataModel[cryptoSelected]![timeSpanSelected]![fiatSelected]! 66 | .cryptoData 67 | .isEmpty) { 68 | setState = BaseViewState.Bussy; 69 | List> futures = [ 70 | _reverseAnimation(), 71 | _fetchData(), 72 | ]; 73 | await Future.wait(futures); 74 | return; 75 | } 76 | 77 | await _reverseAnimation(); 78 | _startAnimation(); 79 | setState = BaseViewState.Iddle; 80 | } 81 | 82 | Future initMainViewVM( 83 | {required AnimationCallback startAnimation, 84 | required AnimationCallback reverseAnimation, 85 | required ScrollMoveCallback scrollMoveTo, 86 | required ShowFiatDialogCallback showFiatDialog, 87 | required ShowErrorDialogCallback showErrorDialog}) async { 88 | _startAnimation = startAnimation; 89 | _reverseAnimation = reverseAnimation; 90 | _scrollMoveTo = scrollMoveTo; 91 | _showFiatDialog = showFiatDialog; 92 | _showErrorDialog = showErrorDialog; 93 | _fullCryptoDataModel = {}; 94 | for (final code in CryptoCode.values) { 95 | Map> timeMap = {}; 96 | for (var time in TimeSpan.values) { 97 | Map fiatMap = {}; 98 | for (var fiat in FiatCode.values) { 99 | fiatMap.addAll({fiat: CryptoViewData(cryptoData: [])}); 100 | } 101 | timeMap.addAll({time: fiatMap}); 102 | } 103 | _fullCryptoDataModel.addAll({code: timeMap}); 104 | } 105 | 106 | await _fetchData(); 107 | } 108 | 109 | void changeTheme() { 110 | _themeService.switchTheme(); 111 | } 112 | 113 | Future changeDate(TimeSpan t) async { 114 | if (t == timeSpanSelected) return; 115 | 116 | timeSpanSelected = t; 117 | 118 | await _handleChange(); 119 | } 120 | 121 | Future changeCrypto(CryptoCode c) async { 122 | if (c == cryptoSelected) return; 123 | cryptoSelected = c; 124 | await _handleChange(); 125 | } 126 | 127 | Future changeCryptoByTap(CryptoCode c) async { 128 | _scrollMoveTo(c); 129 | await changeCrypto(c); 130 | } 131 | 132 | Future openFiatDialog() async { 133 | final fiat = await _showFiatDialog(fiatSelected); 134 | 135 | if (fiat == null || fiat == fiatSelected) return; 136 | 137 | fiatSelected = fiat; 138 | await _handleChange(); 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /lib/ui/features/theme_builder/theme_builder.dart: -------------------------------------------------------------------------------- 1 | import 'package:crypto_tracker/data/theme_service.dart'; 2 | import 'package:crypto_tracker/ui/ui_constants/kcolors.dart'; 3 | import 'package:crypto_tracker/ui/features/base_view/base_view.dart'; 4 | import 'package:crypto_tracker/ui/features/theme_builder/theme_builder_vm.dart'; 5 | import 'package:flutter/material.dart'; 6 | 7 | class ThemeBuilder extends StatefulWidget { 8 | final Widget Function(ThemeData theme) builder; 9 | 10 | const ThemeBuilder({Key? key, required this.builder}) : super(key: key); 11 | 12 | @override 13 | _ThemeBuilderState createState() => _ThemeBuilderState(); 14 | } 15 | 16 | class _ThemeBuilderState extends State { 17 | final model = ThemeBuilderVM(); 18 | 19 | @override 20 | Widget build(BuildContext context) { 21 | ThemeData _getTheme(ThemeModeCrypto mode) { 22 | late Color primary; 23 | late Color canvasColor; 24 | late ColorScheme colorScheme; 25 | 26 | switch (mode) { 27 | case ThemeModeCrypto.Light: 28 | canvasColor = kWhiteDarker; 29 | primary = kBlackDarker; 30 | colorScheme = ColorScheme.light( 31 | primary: primary, 32 | background: kWhiteDarker, 33 | onBackground: kWhite, 34 | secondary: kBlack, 35 | onSecondary: kBlackDarker, 36 | tertiary: kGrayDarker, 37 | ); 38 | break; 39 | 40 | case ThemeModeCrypto.Dark: 41 | canvasColor = kBlackDarker; 42 | primary = kWhiteDarker; 43 | colorScheme = ColorScheme.dark( 44 | primary: primary, 45 | background: kBlackDarker, 46 | onBackground: kBlack, 47 | secondary: kWhite, 48 | onSecondary: kWhiteDarker, 49 | tertiary: kGray, 50 | ); 51 | break; 52 | } 53 | 54 | return ThemeData( 55 | primaryColor: primary, 56 | textTheme: TextTheme( 57 | headlineLarge: TextStyle( 58 | color: colorScheme.secondary, 59 | fontWeight: FontWeight.bold, 60 | fontSize: 24, 61 | ), 62 | headlineMedium: TextStyle( 63 | color: colorScheme.secondary, 64 | fontWeight: FontWeight.bold, 65 | fontSize: 18, 66 | ), 67 | bodyLarge: TextStyle( 68 | color: colorScheme.secondary, 69 | fontSize: 14, 70 | ), 71 | bodySmall: TextStyle( 72 | color: colorScheme.tertiary, 73 | fontSize: 12, 74 | )), 75 | canvasColor: canvasColor, 76 | colorScheme: colorScheme, 77 | ); 78 | } 79 | 80 | return BaseViewBuilder( 81 | viewModel: model, 82 | builder: (context) => widget.builder(_getTheme(model.themeMode))); 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /lib/ui/features/theme_builder/theme_builder_vm.dart: -------------------------------------------------------------------------------- 1 | import 'package:crypto_tracker/data/theme_service.dart'; 2 | import 'package:crypto_tracker/ui/features/base_view/base_vm.dart'; 3 | 4 | class ThemeBuilderVM extends BaseVM { 5 | final _themeService = ThemeService(); 6 | final bool listenable = false; 7 | late ThemeModeCrypto themeMode; 8 | 9 | ThemeBuilderVM() { 10 | initTheme(); 11 | } 12 | 13 | void initTheme() { 14 | themeMode = _themeService.themeMode; 15 | 16 | Stream stream = _themeService.themeController.stream; 17 | stream.listen((value) { 18 | _swichTheme(value); 19 | }); 20 | } 21 | 22 | void _swichTheme(ThemeModeCrypto value) { 23 | themeMode = value; 24 | notifyListeners(); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /lib/ui/ui_constants/assets.dart: -------------------------------------------------------------------------------- 1 | abstract class Assets{ 2 | static const _base = 'assets/crypto_logos/'; 3 | static const btc = '${_base}BTC.png'; 4 | static const doge = '${_base}DOGE.png'; 5 | static const eth = '${_base}ETH.png'; 6 | static const ltc = '${_base}LTC.png'; 7 | static const ada = '${_base}ADA.png'; 8 | static const xmr = '${_base}XMR.png'; 9 | static const dash = '${_base}DASH.png'; 10 | static const xrp = '${_base}XRP.png'; 11 | static const usdt = '${_base}USDT.png'; 12 | } -------------------------------------------------------------------------------- /lib/ui/ui_constants/kcolors.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | const kGreen = Color(0xFF00b894); 4 | const kBlack = Color(0xFF353b48); 5 | const kBlackDarker = Color(0xFF2f3640); 6 | const kWhite = Color(0xFFffffff); 7 | const kWhiteDarker = Color(0xFFf1f2f6); 8 | const kRedAccent = Colors.redAccent; 9 | const kGreenAccent = Colors.greenAccent; 10 | 11 | const kGray = Color(0XFFa4b0be); 12 | const kGrayDarker = Color(0x8a000000); 13 | 14 | const kColorBtc = Color(0XFFFF9400); 15 | const kColorEth = Color(0XFF8893B5); 16 | const kColorLtc = Color(0XFF005DA0); 17 | const kColorDoge = Color(0XFFC5A021); 18 | const kColorAda = Color(0XFF3468D1); 19 | const kColorXmr = Color(0XFFF26822); 20 | const kColorDash = Color(0XFF008DE4); 21 | const kColorXrp = Color(0XFF10A4DC); 22 | const kColorUsdt = Color(0XFF50AF95); 23 | -------------------------------------------------------------------------------- /lib/ui/ui_constants/labels.dart: -------------------------------------------------------------------------------- 1 | abstract class Labels { 2 | static String appName() => 'CryptoTracker'; 3 | static String errorTitle() => 'Something went wrong'; 4 | static String errorButton() => 'Try again'; 5 | static String mainViewloading(String crypto) => 'Loading $crypto data...'; 6 | static String modalFiatSelectorTitle() => 'Select fiat currency:'; 7 | } -------------------------------------------------------------------------------- /lib/ui/ui_helpers.dart: -------------------------------------------------------------------------------- 1 | import 'package:crypto_tracker/data/crypto_data_service/crypto_data_constants.dart'; 2 | import 'package:crypto_tracker/data/crypto_data_service/crypto_data_model.dart'; 3 | import 'package:crypto_tracker/ui/ui_constants/assets.dart'; 4 | import 'package:crypto_tracker/ui/ui_constants/kcolors.dart'; 5 | import 'package:crypto_tracker/ui/widgets/graph.dart'; 6 | 7 | abstract class UIHelper { 8 | static List getPlotsFromCryptoModel(List cryptoModel) { 9 | return cryptoModel.map((c) => Plot(c.prettyTime, c.price)).toList(); 10 | } 11 | 12 | static const cryptoName = { 13 | CryptoCode.BTC: 'Bitcoin', 14 | CryptoCode.ETH: 'Ethereum', 15 | CryptoCode.LTC: 'Litecoin', 16 | CryptoCode.DOGE: 'Doge', 17 | CryptoCode.ADA: 'Cardano', 18 | CryptoCode.XMR: 'Monero', 19 | CryptoCode.DASH: 'Dash', 20 | CryptoCode.XRP: 'Ripple', 21 | CryptoCode.USDT: 'Tether', 22 | }; 23 | 24 | static const cyrptoColor = { 25 | CryptoCode.BTC: kColorBtc, 26 | CryptoCode.ETH: kColorEth, 27 | CryptoCode.LTC: kColorLtc, 28 | CryptoCode.DOGE: kColorDoge, 29 | CryptoCode.ADA: kColorAda, 30 | CryptoCode.XMR: kColorXmr, 31 | CryptoCode.DASH: kColorDash, 32 | CryptoCode.XRP: kColorXrp, 33 | CryptoCode.USDT: kColorUsdt, 34 | }; 35 | 36 | static const cryptoPic = { 37 | CryptoCode.BTC: Assets.btc, 38 | CryptoCode.ETH: Assets.eth, 39 | CryptoCode.LTC: Assets.ltc, 40 | CryptoCode.DOGE: Assets.doge, 41 | CryptoCode.ADA: Assets.ada, 42 | CryptoCode.XMR: Assets.xmr, 43 | CryptoCode.DASH: Assets.dash, 44 | CryptoCode.XRP: Assets.xrp, 45 | CryptoCode.USDT: Assets.usdt, 46 | }; 47 | 48 | static const fiat = { 49 | FiatCode.USD: '\$', 50 | FiatCode.EUR: '€', 51 | FiatCode.CAD: 'C\$' 52 | }; 53 | 54 | static const fiatName = { 55 | FiatCode.USD: 'United States Dollar (USD)', 56 | FiatCode.EUR: 'Euro (EUR)', 57 | FiatCode.CAD: 'Canadian Dollar (CAD)' 58 | }; 59 | 60 | static const date = { 61 | TimeSpan.Day: '1D', 62 | TimeSpan.Week: '1W', 63 | TimeSpan.TwoWeeks: '2W', 64 | TimeSpan.Month: '1M', 65 | TimeSpan.Year: '1Y', 66 | }; 67 | } 68 | -------------------------------------------------------------------------------- /lib/ui/widgets/crypto_card.dart: -------------------------------------------------------------------------------- 1 | import 'package:crypto_tracker/data/crypto_data_service/crypto_data_constants.dart'; 2 | import 'package:crypto_tracker/ui/ui_constants/kcolors.dart'; 3 | import 'package:crypto_tracker/ui/widgets/graph.dart'; 4 | import 'package:crypto_tracker/ui/ui_helpers.dart'; 5 | import 'package:flutter/material.dart'; 6 | 7 | class CriptoCard extends StatelessWidget { 8 | final CryptoCode cryptoCode; 9 | final Color colorCrypto; 10 | final String codeStr; 11 | final Animation? animation; 12 | final String percentage; 13 | final bool? decrease; 14 | final String price; 15 | final String fiat; 16 | final List plots; 17 | 18 | CriptoCard({ 19 | required this.cryptoCode, 20 | required this.colorCrypto, 21 | required this.decrease, 22 | required this.codeStr, 23 | required this.price, 24 | required this.plots, 25 | required this.fiat, 26 | this.animation, 27 | this.percentage = '', 28 | }); 29 | 30 | @override 31 | Widget build(BuildContext context) { 32 | String arrow() => decrease! ? '↓ ' : '↑ '; 33 | 34 | Color colorPercentage() => decrease! ? kRedAccent : kGreenAccent; 35 | 36 | final theme = Theme.of(context); 37 | 38 | return Container( 39 | decoration: BoxDecoration( 40 | color: theme.colorScheme.onBackground, 41 | borderRadius: BorderRadius.circular(40)), 42 | padding: const EdgeInsets.only(top: 20, bottom: 10, left: 20, right: 20), 43 | child: Column( 44 | mainAxisAlignment: MainAxisAlignment.center, 45 | children: [ 46 | //LOGO 47 | Stack( 48 | alignment: Alignment.center, 49 | children: [ 50 | ConstrainedBox( 51 | constraints: BoxConstraints(maxWidth: 200, maxHeight: 100), 52 | child: AnimatedBuilder( 53 | animation: animation ?? AlwaysStoppedAnimation(0), 54 | builder: (context, _) => Graph( 55 | yMoveFactor: animation?.value ?? 0, 56 | graphData: 57 | GraphModel(typeCard: TypeGraph.preview, plots: plots), 58 | primaryColor: theme.colorScheme.secondary, 59 | backGroundColor: theme.canvasColor, 60 | bigTextColor: theme.colorScheme.primary, 61 | cardColor: theme.colorScheme.onBackground, 62 | primaryTextStyle: theme.textTheme.headlineLarge, 63 | secondaryTextStyle: theme.textTheme.bodySmall), 64 | ), 65 | ), 66 | ScaleTransition( 67 | scale: animation ?? AlwaysStoppedAnimation(0), 68 | child: SizedBox( 69 | height: 100, 70 | width: 100, 71 | child: PhysicalModel( 72 | color: theme.colorScheme.onBackground, 73 | elevation: 10, 74 | shadowColor: theme.colorScheme.background, 75 | borderRadius: BorderRadius.circular(100), 76 | child: Image.asset(UIHelper.cryptoPic[cryptoCode]!)), 77 | ), 78 | ), 79 | ], 80 | ), 81 | SizedBox(height: 20), 82 | //NAME 83 | RichText( 84 | text: TextSpan(children: [ 85 | TextSpan( 86 | text: '${UIHelper.cryptoName[cryptoCode]} ', 87 | style: theme.textTheme.headlineMedium! 88 | .copyWith(color: colorCrypto)), 89 | TextSpan(text: codeStr, style: theme.textTheme.bodySmall), 90 | ])), 91 | //PERCENTAGE 92 | decrease == null 93 | ? SizedBox() 94 | : RichText( 95 | maxLines: 1, 96 | text: TextSpan(children: [ 97 | TextSpan( 98 | text: arrow(), 99 | style: theme.textTheme.bodyLarge! 100 | .copyWith(color: colorPercentage())), 101 | TextSpan( 102 | text: '$percentage%', 103 | style: theme.textTheme.bodyLarge! 104 | .copyWith(color: colorPercentage())), 105 | ])), 106 | SizedBox(height: 10), 107 | //PRICE 108 | price.isEmpty 109 | ? SizedBox() 110 | : FadeTransition( 111 | opacity: animation ?? AlwaysStoppedAnimation(0), 112 | child: Text( 113 | '$fiat $price', 114 | maxLines: 1, 115 | style: theme.textTheme.headlineLarge, 116 | ), 117 | ), 118 | SizedBox(height: 10), 119 | ], 120 | ), 121 | ); 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /lib/ui/widgets/dialog_wrapper.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class DialogWrapper extends StatelessWidget { 4 | final Widget child; 5 | 6 | const DialogWrapper({Key? key, required this.child}) : super(key: key); 7 | 8 | @override 9 | Widget build(BuildContext context) { 10 | return Dialog( 11 | backgroundColor: Theme.of(context).colorScheme.onBackground, 12 | shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)), 13 | child: Padding( 14 | padding: const EdgeInsets.all(16), 15 | child: child, 16 | ), 17 | ); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /lib/ui/widgets/graph.dart: -------------------------------------------------------------------------------- 1 | import 'dart:math'; 2 | import 'dart:ui' as ui; 3 | 4 | import 'package:crypto_tracker/ui/ui_constants/kcolors.dart'; 5 | import 'package:flutter/gestures.dart'; 6 | import 'package:flutter/material.dart'; 7 | import 'package:flutter/rendering.dart'; 8 | 9 | enum TypeGraph { 10 | full, 11 | preview, 12 | } 13 | 14 | class GraphModel { 15 | String baseText; 16 | String prefixBigText; 17 | String bigText; 18 | List plots; 19 | TypeGraph typeCard; 20 | GraphModel({ 21 | required this.plots, 22 | this.baseText = '', 23 | this.prefixBigText = '', 24 | this.bigText = '', 25 | this.typeCard = TypeGraph.full, 26 | }); 27 | 28 | GraphModel copyWith({ 29 | String? baseText, 30 | String? prefixBigText, 31 | String? bigText, 32 | List? plots, 33 | TypeGraph? typeCard, 34 | }) { 35 | return GraphModel( 36 | baseText: baseText ?? this.baseText, 37 | prefixBigText: prefixBigText ?? this.prefixBigText, 38 | bigText: bigText ?? this.bigText, 39 | plots: plots ?? this.plots, 40 | typeCard: typeCard ?? this.typeCard, 41 | ); 42 | } 43 | } 44 | 45 | class Plot { 46 | final String x; 47 | final double y; 48 | Plot( 49 | this.x, 50 | this.y, 51 | ); 52 | } 53 | 54 | class _CardModel { 55 | final double xPosition; 56 | final double yPosition; 57 | final String textPrinc; 58 | final String textSec; 59 | final double percentageChange; 60 | 61 | _CardModel({ 62 | required this.xPosition, 63 | required this.yPosition, 64 | required this.textPrinc, 65 | required this.textSec, 66 | required this.percentageChange, 67 | }); 68 | } 69 | 70 | class Graph extends LeafRenderObjectWidget { 71 | final GraphModel graphData; 72 | final double yMoveFactor; 73 | final Color primaryColor; 74 | final Color backGroundColor; 75 | final Color cardColor; 76 | final Color bigTextColor; 77 | final TextStyle? primaryTextStyle; 78 | final TextStyle? secondaryTextStyle; 79 | 80 | Graph({ 81 | required this.graphData, 82 | this.yMoveFactor = 0, 83 | this.primaryColor = kBlack, 84 | this.backGroundColor = kWhite, 85 | this.cardColor = kWhite, 86 | this.bigTextColor = kBlack, 87 | this.primaryTextStyle, 88 | this.secondaryTextStyle, 89 | }); 90 | 91 | @override 92 | RenderObject createRenderObject(BuildContext context) { 93 | return RenderGraphBox( 94 | graphData: graphData, 95 | yMoveFactor: yMoveFactor, 96 | primaryColor: primaryColor, 97 | backGroundColor: backGroundColor, 98 | cardColor: cardColor, 99 | primaryTextStyle: primaryTextStyle, 100 | secondaryTextStyle: secondaryTextStyle, 101 | bigTextColor: bigTextColor, 102 | ); 103 | } 104 | 105 | @override 106 | void updateRenderObject( 107 | BuildContext context, covariant RenderGraphBox renderObject) { 108 | super.updateRenderObject(context, renderObject); 109 | renderObject 110 | ..setGraphData = graphData 111 | ..setYMoveFactor = yMoveFactor 112 | ..setPrimaryColor = primaryColor 113 | ..setBackGroundColor = backGroundColor 114 | ..setCardColor = cardColor 115 | ..setPrimaryTextStyle = primaryTextStyle 116 | ..setSecondaryTextStyle = secondaryTextStyle 117 | ..setBigTextColor = bigTextColor; 118 | } 119 | } 120 | 121 | class RenderGraphBox extends RenderBox { 122 | RenderGraphBox({ 123 | required GraphModel graphData, 124 | double yMoveFactor = 0, 125 | Color primaryColor = kBlack, 126 | Color backGroundColor = kWhite, 127 | Color cardColor = kWhite, 128 | Color bigTextColor = kBlack, 129 | TextStyle? primaryTextStyle, 130 | TextStyle? secondaryTextStyle, 131 | }) : _graphData = graphData, 132 | _yMoveFactor = yMoveFactor, 133 | _backGroundColor = backGroundColor, 134 | _primaryColor = primaryColor, 135 | _cardColor = cardColor, 136 | _primaryTextStyle = primaryTextStyle, 137 | _secondaryTextStyle = secondaryTextStyle, 138 | _bigTextColor = bigTextColor; 139 | 140 | //Limits 141 | late double _height; 142 | late double _width; 143 | late double _widthGraph; 144 | late double _middleY; 145 | //Props 146 | GraphModel _graphData; 147 | double _yMoveFactor; 148 | Color _primaryColor; 149 | Color _backGroundColor; 150 | Color _cardColor; 151 | Color _bigTextColor; 152 | TextStyle? _primaryTextStyle; 153 | TextStyle? _secondaryTextStyle; 154 | //Control var 155 | int _currentCardIdx = -1; 156 | //Paints 157 | late Paint _linePaint; 158 | late Paint _gradientPaint; 159 | //Points 160 | List _initialXPoints = []; 161 | List _finalYPoints = []; 162 | //LateralMarks (Points, yOffsets) 163 | List _lateralMarks = []; 164 | List _lateralMarksOffsets = []; 165 | //Cards 166 | List<_CardModel> _cards = []; 167 | //Const 168 | static const double _curveW = 3; 169 | static const double _paddingVerticalCard = 10; 170 | static const double _breakTextCard = 5; 171 | static const double _paddingHorizontalCard = 15; 172 | static const double _totalHeightPadding = 173 | 3 * _paddingVerticalCard + _breakTextCard; 174 | static const double _totalWidhtPadding = 175 | 2 * _paddingHorizontalCard; //2*(_paddingCard) 176 | static const double _radiusCard = 14; 177 | static const double _lateralMarksWidth = 50; 178 | //Gestures 179 | late HorizontalDragGestureRecognizer _drag; 180 | late TapGestureRecognizer _tap; 181 | 182 | @override 183 | void attach(PipelineOwner owner) { 184 | super.attach(owner); 185 | 186 | //Drag 187 | _drag = HorizontalDragGestureRecognizer(debugOwner: this) 188 | ..onUpdate = (DragUpdateDetails details) { 189 | _horizontalDragHandler(details.localPosition); 190 | }; 191 | _tap = TapGestureRecognizer(debugOwner: this) 192 | ..onTapDown = (TapDownDetails details) { 193 | _horizontalDragHandler(details.localPosition); 194 | }; 195 | } 196 | 197 | @override 198 | void detach() { 199 | super.detach(); 200 | _drag.dispose(); 201 | } 202 | 203 | @override 204 | void performLayout() { 205 | size = Size(constraints.maxWidth, constraints.maxHeight); 206 | _height = size.height; 207 | _width = size.width; 208 | _widthGraph = _width; 209 | if (_graphData.typeCard == TypeGraph.full) { 210 | _widthGraph -= _lateralMarksWidth; 211 | } 212 | _middleY = _height / 2; 213 | 214 | //Needs dimensions 215 | if (_graphData.typeCard == TypeGraph.full) _prepareGradientBrush(); 216 | _prepareLineBrush(); 217 | } 218 | 219 | @override 220 | bool hitTestSelf(Offset position) { 221 | return size.contains(position); 222 | } 223 | 224 | @override 225 | void handleEvent(PointerEvent event, BoxHitTestEntry entry) { 226 | assert(debugHandleEvent(event, entry)); 227 | 228 | if (event is PointerDownEvent && _graphData.typeCard == TypeGraph.full) { 229 | _drag.addPointer(event); 230 | _tap.addPointer(event); 231 | } 232 | } 233 | 234 | //Events 235 | set setGraphData(GraphModel value) { 236 | bool newPlots = false; 237 | bool newPrefixBigText = false; 238 | bool forceBigText = false; 239 | bool newbaseText = false; 240 | bool newTypeCard = false; 241 | 242 | if (value.plots.length != _graphData.plots.length) { 243 | newPlots = true; 244 | } 245 | 246 | if (!newPlots) { 247 | for (int x = 0; x < value.plots.length; x++) { 248 | if (value.plots[x].y != _graphData.plots[x].y) { 249 | newPlots = true; 250 | break; 251 | } 252 | } 253 | } 254 | 255 | if (newPlots == true) { 256 | _graphData.plots.clear(); 257 | _graphData.plots = value.plots; 258 | _initialXPoints = []; 259 | } 260 | 261 | if (_graphData.prefixBigText != value.prefixBigText) { 262 | _graphData.prefixBigText = value.prefixBigText; 263 | newPrefixBigText = true; 264 | } 265 | 266 | if (_graphData.baseText != value.baseText) { 267 | _graphData.baseText = value.baseText; 268 | newbaseText = true; 269 | } 270 | 271 | if (_graphData.typeCard != value.typeCard) { 272 | _graphData.typeCard = value.typeCard; 273 | newTypeCard = true; 274 | } 275 | 276 | if (newTypeCard) { 277 | _initialXPoints = []; 278 | } 279 | 280 | if (_graphData.bigText != value.bigText) { 281 | _graphData.bigText = value.bigText; 282 | forceBigText = true; 283 | } 284 | 285 | if (newPlots || 286 | newPrefixBigText || 287 | newbaseText || 288 | newTypeCard || 289 | forceBigText) { 290 | markNeedsPaint(); 291 | } 292 | } 293 | 294 | set setYMoveFactor(double value) { 295 | if (_yMoveFactor == value) return; 296 | _yMoveFactor = value; 297 | markNeedsPaint(); 298 | } 299 | 300 | set setPrimaryTextStyle(TextStyle? value) { 301 | if (value == _primaryTextStyle) return; 302 | 303 | _primaryTextStyle = value; 304 | markNeedsPaint(); 305 | } 306 | 307 | set setSecondaryTextStyle(TextStyle? value) { 308 | if (value == _secondaryTextStyle) return; 309 | 310 | _secondaryTextStyle = value; 311 | markNeedsPaint(); 312 | } 313 | 314 | set setBackGroundColor(Color value) { 315 | if (value == _backGroundColor) return; 316 | 317 | _backGroundColor = value; 318 | markNeedsPaint(); 319 | } 320 | 321 | set setBigTextColor(Color value) { 322 | if (value == _bigTextColor) return; 323 | 324 | _bigTextColor = value; 325 | markNeedsPaint(); 326 | } 327 | 328 | set setPrimaryColor(Color value) { 329 | if (_primaryColor == value) return; 330 | 331 | _primaryColor = value; 332 | markNeedsPaint(); 333 | } 334 | 335 | set setCardColor(Color value) { 336 | if (_cardColor == value) return; 337 | 338 | _cardColor = value; 339 | markNeedsPaint(); 340 | } 341 | 342 | void _horizontalDragHandler(Offset localPosition) { 343 | if (localPosition.dx > _widthGraph) return; 344 | 345 | if (_yMoveFactor == 1) { 346 | final rawPositon = 347 | (localPosition.dx / _widthGraph) * _graphData.plots.length; 348 | 349 | final currentCardIdx = int.parse(rawPositon.toStringAsFixed(0)); 350 | 351 | if (currentCardIdx != _currentCardIdx && 352 | currentCardIdx >= 0 && 353 | currentCardIdx < _graphData.plots.length) { 354 | _currentCardIdx = currentCardIdx; 355 | markNeedsPaint(); 356 | markNeedsSemanticsUpdate(); 357 | } 358 | } 359 | } 360 | 361 | //Methods 362 | void _setUpValues() { 363 | //Reset 364 | _initialXPoints = []; 365 | _finalYPoints = []; 366 | _cards = []; 367 | _currentCardIdx = -1; 368 | 369 | //Min, Max 370 | double minYPlot = _graphData.plots[0].y; 371 | double maxYPlot = _graphData.plots[0].y; 372 | _graphData.plots.forEach((p) { 373 | minYPlot = min(minYPlot, p.y); 374 | maxYPlot = max(maxYPlot, p.y); 375 | }); 376 | final dy = maxYPlot - minYPlot; 377 | //Plot Points 378 | for (int p = 0; p < _graphData.plots.length; p++) { 379 | final x = _widthGraph * p / (_graphData.plots.length - 1); 380 | final y = (_height / dy) * (maxYPlot - _graphData.plots[p].y); 381 | _initialXPoints.add(x); 382 | _finalYPoints.add(y); 383 | 384 | double percentageChange = 0; 385 | if (p != 0) { 386 | percentageChange = _getPercentageChange( 387 | _graphData.plots[p - 1].y, 388 | _graphData.plots[p].y, 389 | ); 390 | } 391 | 392 | _cards.add( 393 | _CardModel( 394 | xPosition: _initialXPoints[p], 395 | yPosition: _finalYPoints[p], 396 | textPrinc: _graphData.plots[p].y.toStringAsFixed(2), 397 | textSec: _graphData.plots[p].x, 398 | percentageChange: percentageChange, 399 | ), 400 | ); 401 | } 402 | 403 | //LateralMarsks Points 404 | final stepMark = dy / 4; 405 | final stepOffsetMark = _height / 4; 406 | double baseMark = maxYPlot; 407 | double baseOffsetMark = 0; 408 | _lateralMarks = []; 409 | _lateralMarksOffsets = []; 410 | for (int x = 0; x < 5; x++) { 411 | if (x == 0) { 412 | _lateralMarks.add(maxYPlot); 413 | _lateralMarksOffsets.add(0); 414 | } 415 | if (x == 4) { 416 | _lateralMarks.add(minYPlot); 417 | _lateralMarksOffsets.add(_height); 418 | } 419 | 420 | if (x > 0 && x < 4) { 421 | baseMark -= stepMark; 422 | baseOffsetMark += stepOffsetMark; 423 | _lateralMarks.add(baseMark); 424 | _lateralMarksOffsets.add(baseOffsetMark); 425 | } 426 | } 427 | } 428 | 429 | double _getPercentageChange(double first, double second) { 430 | final percentage = (second / first) * (first > second ? -1 : 1); 431 | return percentage; 432 | } 433 | 434 | void _prepareGradientBrush() { 435 | _gradientPaint = Paint() 436 | ..shader = ui.Gradient.linear( 437 | Offset(_widthGraph / 2, 0), 438 | Offset(_widthGraph / 2, _height), 439 | [ 440 | _primaryColor.withOpacity(.5), 441 | _backGroundColor.withOpacity(.1), 442 | _backGroundColor, 443 | ], 444 | [0, 1, 1], 445 | ); 446 | } 447 | 448 | void _prepareLineBrush() { 449 | _linePaint = Paint() 450 | ..color = _primaryColor 451 | ..strokeCap = StrokeCap.round 452 | ..strokeJoin = StrokeJoin.round 453 | ..strokeWidth = _curveW; 454 | } 455 | 456 | double _getdy(int x) { 457 | return (_finalYPoints[x] - _middleY) * _yMoveFactor + _middleY; 458 | } 459 | 460 | void _drawLine(Canvas canvas) { 461 | final endPoint = _height / 2; 462 | final path = Path() 463 | ..moveTo(0, endPoint) 464 | ..lineTo(_widthGraph, _height / 2) 465 | ..lineTo(_widthGraph, _height) 466 | ..lineTo(0, _height) 467 | ..close(); 468 | 469 | if (_graphData.typeCard == TypeGraph.full) 470 | canvas.drawPath(path, _gradientPaint); 471 | 472 | canvas.drawLine( 473 | Offset(0, endPoint), 474 | Offset(_widthGraph, endPoint), 475 | _linePaint, 476 | ); 477 | } 478 | 479 | void _drawGraph(Canvas canvas) { 480 | for (int x = 0; x < _finalYPoints.length - 1; x++) { 481 | double dy = _getdy(x); 482 | double dy1 = _getdy(x + 1); 483 | 484 | canvas.drawLine(Offset(_initialXPoints[x], dy), 485 | Offset(_initialXPoints[x + 1], dy1), _linePaint); 486 | } 487 | } 488 | 489 | void _drawCard(Canvas canvas, _CardModel model) { 490 | final textMaxW = _widthGraph / 3; 491 | double widhtCard = 0; 492 | //Text Principal 493 | final TextPainter textPrinc = TextPainter( 494 | text: TextSpan( 495 | text: '${_graphData.prefixBigText} ${model.textPrinc}', 496 | style: _primaryTextStyle?.copyWith(fontSize: 16)), 497 | maxLines: 1, 498 | textAlign: TextAlign.justify, 499 | textDirection: TextDirection.ltr) 500 | ..layout( 501 | maxWidth: textMaxW, 502 | ); 503 | widhtCard = max(widhtCard, textPrinc.width); 504 | //Second Text 505 | final TextPainter textSecondary = TextPainter( 506 | text: TextSpan( 507 | text: model.textSec, 508 | style: _secondaryTextStyle?.copyWith(fontSize: 14)), 509 | maxLines: 2, 510 | textAlign: TextAlign.left, 511 | textDirection: TextDirection.ltr) 512 | ..layout(maxWidth: textMaxW); 513 | widhtCard = max(widhtCard, textSecondary.width); 514 | //Third Text 515 | bool grow = model.percentageChange > 0; 516 | String arrow = grow ? '↑' : '↓'; 517 | final TextPainter textThird = TextPainter( 518 | text: TextSpan( 519 | text: model.percentageChange != 0 520 | ? '$arrow${model.percentageChange.toStringAsFixed(3)}%' 521 | : '', 522 | style: _secondaryTextStyle?.copyWith( 523 | fontSize: 14, 524 | color: grow ? kGreenAccent : kRedAccent, 525 | )), 526 | textAlign: TextAlign.left, 527 | maxLines: 1, 528 | textDirection: TextDirection.ltr) 529 | ..layout( 530 | maxWidth: textMaxW, 531 | ); 532 | 533 | //Width Card (Max Text) 534 | widhtCard = max(widhtCard, textThird.width); 535 | widhtCard += _totalWidhtPadding; 536 | //Height Text + padding 537 | final heightCard = textPrinc.height + 538 | textSecondary.height + 539 | textThird.height + 540 | _totalHeightPadding; 541 | 542 | final drawToRight = model.xPosition + widhtCard < _widthGraph; 543 | final drawDown = model.yPosition + heightCard < _height; 544 | 545 | double startX = model.xPosition; 546 | double startY = model.yPosition; 547 | double finalX = startX, finalY = startY; 548 | 549 | //Mark 550 | final paintOut = Paint()..color = _cardColor; 551 | final paintIn = Paint()..color = _primaryColor; 552 | final rectShadowMark = 553 | Rect.fromCenter(center: Offset(startX, startY), width: 14, height: 14); 554 | final rRectShadowMark = RRect.fromRectXY(rectShadowMark, 12, 12); 555 | final pathShadowMark = Path()..addRRect(rRectShadowMark); 556 | canvas.drawShadow(pathShadowMark, _primaryColor.withOpacity(.5), 2, true); 557 | canvas.drawCircle(Offset(startX, startY), 6, paintOut); 558 | canvas.drawCircle(Offset(startX, startY), 3, paintIn); 559 | 560 | if (drawToRight) { 561 | finalX += widhtCard; 562 | } else { 563 | startX -= widhtCard; 564 | finalX = startX + widhtCard; 565 | } 566 | 567 | if (drawDown) { 568 | finalY += heightCard; 569 | } else { 570 | startY -= heightCard; 571 | finalY = startY + heightCard; 572 | } 573 | 574 | // Container 575 | final container = Paint()..color = _cardColor; 576 | final rect = Rect.fromPoints( 577 | Offset(startX, startY), 578 | Offset(finalX, finalY), 579 | ); 580 | final rRect = RRect.fromRectAndCorners( 581 | rect, 582 | topLeft: Radius.circular(_radiusCard), 583 | topRight: Radius.circular(_radiusCard), 584 | bottomLeft: Radius.circular(_radiusCard), 585 | bottomRight: Radius.circular(_radiusCard), 586 | ); 587 | 588 | final shadowPath = Path()..addRRect(rRect); 589 | canvas.drawShadow(shadowPath, _cardColor.withOpacity(.5), 2, true); 590 | canvas.drawRRect(rRect, container); 591 | 592 | //Text 593 | textPrinc.paint(canvas, 594 | Offset(startX + _paddingHorizontalCard, startY + _paddingVerticalCard)); 595 | textSecondary.paint( 596 | canvas, 597 | Offset( 598 | startX + _paddingHorizontalCard, 599 | startY + textPrinc.height + 2 * _paddingVerticalCard, 600 | )); 601 | textThird.paint( 602 | canvas, 603 | Offset( 604 | startX + _paddingHorizontalCard, 605 | startY + 606 | textPrinc.height + 607 | textSecondary.height + 608 | 2 * _paddingVerticalCard + 609 | _breakTextCard, 610 | )); 611 | } 612 | 613 | void _drawGradinte(Canvas canvas) { 614 | final path = Path()..moveTo(_initialXPoints[0], _middleY); 615 | 616 | for (int x = 0; x < _finalYPoints.length - 1; x++) { 617 | double dy = _getdy(x); 618 | double dy1 = _getdy(x + 1); 619 | if (x == 0) { 620 | path.moveTo(_initialXPoints[x], dy); 621 | path.lineTo(_initialXPoints[x + 1], dy1); 622 | } else { 623 | path.lineTo(_initialXPoints[x + 1], dy1); 624 | } 625 | if (x == _finalYPoints.length - 2) { 626 | path.lineTo(_widthGraph, _height); 627 | path.lineTo(0, _height); 628 | canvas.drawPath(path, _gradientPaint); 629 | } 630 | } 631 | } 632 | 633 | void _drawBackSign(Canvas canvas) { 634 | final textBackSecondary = TextPainter( 635 | text: TextSpan( 636 | text: _graphData.plots.isEmpty ? '' : _graphData.baseText, 637 | style: _secondaryTextStyle?.copyWith(fontSize: 16), 638 | ), 639 | textAlign: TextAlign.left, 640 | textDirection: TextDirection.ltr) 641 | ..layout( 642 | maxWidth: _widthGraph - 2 * _paddingHorizontalCard, 643 | ); 644 | 645 | final textBackPrimary = TextPainter( 646 | text: TextSpan( 647 | text: _graphData.bigText.isEmpty 648 | ? '${_graphData.prefixBigText} ${_graphData.plots.last.y.toStringAsFixed(2)}' 649 | : '${_graphData.bigText}', 650 | style: _primaryTextStyle?.copyWith(color: _bigTextColor), 651 | ), 652 | textAlign: TextAlign.left, 653 | textDirection: TextDirection.ltr) 654 | ..layout( 655 | maxWidth: _widthGraph - 2 * _paddingHorizontalCard, 656 | ); 657 | 658 | textBackPrimary.paint( 659 | canvas, Offset(_paddingHorizontalCard, textBackSecondary.height + 5)); 660 | textBackSecondary.paint(canvas, Offset(_paddingHorizontalCard, 0)); 661 | } 662 | 663 | void _drawLateralMarks(Canvas canvas) { 664 | final offsetText = _widthGraph + 5; 665 | 666 | for (int x = 0; x < 5; x++) { 667 | final text = TextPainter( 668 | text: TextSpan( 669 | text: _lateralMarks[x].toStringAsFixed(2), 670 | style: _secondaryTextStyle?.copyWith(fontSize: 10), 671 | ), 672 | maxLines: 2, 673 | textAlign: TextAlign.left, 674 | textDirection: TextDirection.ltr) 675 | ..layout( 676 | maxWidth: 45, 677 | ); 678 | 679 | text.paint( 680 | canvas, 681 | Offset( 682 | offsetText, 683 | x != 0 684 | ? _lateralMarksOffsets[x] - text.height 685 | : _lateralMarksOffsets[x])); 686 | } 687 | } 688 | 689 | void paint(PaintingContext context, Offset offset) { 690 | final canvas = context.canvas; 691 | canvas.save(); 692 | canvas.translate(offset.dx, offset.dy); 693 | 694 | //Brushes 695 | _prepareLineBrush(); 696 | if (_graphData.typeCard == TypeGraph.full) _prepareGradientBrush(); 697 | 698 | //Line with back sign 699 | if (_graphData.plots.isEmpty) { 700 | _drawLine(canvas); 701 | //Back Sign 702 | if (_graphData.typeCard == TypeGraph.full) _drawBackSign(canvas); 703 | canvas.restore(); 704 | return; 705 | } 706 | //SetUp 707 | if (_initialXPoints.isEmpty) { 708 | _setUpValues(); 709 | } 710 | //Back Sign 711 | if (_graphData.typeCard == TypeGraph.full) _drawBackSign(canvas); 712 | //Gradient 713 | if (_graphData.typeCard == TypeGraph.full) _drawGradinte(canvas); 714 | //Graph 715 | _drawGraph(canvas); 716 | //Draw Lateral Marks 717 | if (_graphData.typeCard == TypeGraph.full && _yMoveFactor > 0) { 718 | _drawLateralMarks(canvas); 719 | } 720 | //Card 721 | if (_currentCardIdx != -1 && 722 | _yMoveFactor == 1 && 723 | _graphData.typeCard != TypeGraph.preview) { 724 | _drawCard(canvas, _cards[_currentCardIdx]); 725 | } 726 | 727 | canvas.restore(); 728 | } 729 | } 730 | -------------------------------------------------------------------------------- /lib/ui/widgets/modal_error.dart: -------------------------------------------------------------------------------- 1 | import 'package:crypto_tracker/ui/ui_constants/kcolors.dart'; 2 | import 'package:crypto_tracker/ui/ui_constants/labels.dart'; 3 | import 'package:crypto_tracker/ui/widgets/dialog_wrapper.dart'; 4 | import 'package:flutter/material.dart'; 5 | 6 | class ModalError extends StatelessWidget { 7 | const ModalError({ 8 | Key? key, 9 | }) : super(key: key); 10 | @override 11 | Widget build(BuildContext context) { 12 | final theme = Theme.of(context); 13 | return DialogWrapper( 14 | child: Column( 15 | mainAxisSize: MainAxisSize.min, 16 | children: [ 17 | Text(Labels.errorTitle(), 18 | style: theme.textTheme.headlineMedium! 19 | .copyWith(color: kRedAccent)), 20 | SizedBox(height: 10), 21 | FractionallySizedBox( 22 | widthFactor: 1, 23 | child: TextButton( 24 | onPressed: () => Navigator.of(context).pop(), 25 | child: Text(Labels.errorButton(), 26 | style: theme.textTheme.bodyLarge)), 27 | ) 28 | ], 29 | ), 30 | ); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /lib/ui/widgets/modal_fiat_selector.dart: -------------------------------------------------------------------------------- 1 | import 'package:crypto_tracker/data/crypto_data_service/crypto_data_constants.dart'; 2 | import 'package:crypto_tracker/ui/ui_constants/labels.dart'; 3 | import 'package:crypto_tracker/ui/widgets/dialog_wrapper.dart'; 4 | import 'package:crypto_tracker/ui/ui_helpers.dart'; 5 | import 'package:flutter/material.dart'; 6 | 7 | class ModalFiatSelector extends StatelessWidget { 8 | final FiatCode selected; 9 | 10 | const ModalFiatSelector({ 11 | Key? key, 12 | required this.selected, 13 | }) : super(key: key); 14 | @override 15 | Widget build(BuildContext context) { 16 | final theme = Theme.of(context); 17 | return DialogWrapper( 18 | child: ListView( 19 | shrinkWrap: true, 20 | physics: const BouncingScrollPhysics(), 21 | children: [ 22 | Text(Labels.modalFiatSelectorTitle(), 23 | style: theme.textTheme.headlineMedium), 24 | SizedBox(height: 10), 25 | ...FiatCode.values.map((f) { 26 | return GestureDetector( 27 | onTap: () { 28 | Navigator.of(context).pop(f); 29 | }, 30 | child: Padding( 31 | padding: const EdgeInsets.symmetric(vertical: 8.0), 32 | child: Text( 33 | UIHelper.fiatName[f] ?? '', 34 | style: selected == f 35 | ? theme.textTheme.bodyLarge 36 | : theme.textTheme.bodyLarge!.copyWith( 37 | color: theme.colorScheme.tertiary, 38 | ), 39 | ), 40 | ), 41 | ); 42 | }).toList() 43 | ], 44 | ), 45 | ); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /pubspec.lock: -------------------------------------------------------------------------------- 1 | # Generated by pub 2 | # See https://dart.dev/tools/pub/glossary#lockfile 3 | packages: 4 | async: 5 | dependency: transitive 6 | description: 7 | name: async 8 | sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c" 9 | url: "https://pub.dev" 10 | source: hosted 11 | version: "2.11.0" 12 | boolean_selector: 13 | dependency: transitive 14 | description: 15 | name: boolean_selector 16 | sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66" 17 | url: "https://pub.dev" 18 | source: hosted 19 | version: "2.1.1" 20 | characters: 21 | dependency: transitive 22 | description: 23 | name: characters 24 | sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605" 25 | url: "https://pub.dev" 26 | source: hosted 27 | version: "1.3.0" 28 | clock: 29 | dependency: transitive 30 | description: 31 | name: clock 32 | sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf 33 | url: "https://pub.dev" 34 | source: hosted 35 | version: "1.1.1" 36 | collection: 37 | dependency: transitive 38 | description: 39 | name: collection 40 | sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a 41 | url: "https://pub.dev" 42 | source: hosted 43 | version: "1.18.0" 44 | fake_async: 45 | dependency: transitive 46 | description: 47 | name: fake_async 48 | sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78" 49 | url: "https://pub.dev" 50 | source: hosted 51 | version: "1.3.1" 52 | flutter: 53 | dependency: "direct main" 54 | description: flutter 55 | source: sdk 56 | version: "0.0.0" 57 | flutter_test: 58 | dependency: "direct dev" 59 | description: flutter 60 | source: sdk 61 | version: "0.0.0" 62 | leak_tracker: 63 | dependency: transitive 64 | description: 65 | name: leak_tracker 66 | sha256: "78eb209deea09858f5269f5a5b02be4049535f568c07b275096836f01ea323fa" 67 | url: "https://pub.dev" 68 | source: hosted 69 | version: "10.0.0" 70 | leak_tracker_flutter_testing: 71 | dependency: transitive 72 | description: 73 | name: leak_tracker_flutter_testing 74 | sha256: b46c5e37c19120a8a01918cfaf293547f47269f7cb4b0058f21531c2465d6ef0 75 | url: "https://pub.dev" 76 | source: hosted 77 | version: "2.0.1" 78 | leak_tracker_testing: 79 | dependency: transitive 80 | description: 81 | name: leak_tracker_testing 82 | sha256: a597f72a664dbd293f3bfc51f9ba69816f84dcd403cdac7066cb3f6003f3ab47 83 | url: "https://pub.dev" 84 | source: hosted 85 | version: "2.0.1" 86 | matcher: 87 | dependency: transitive 88 | description: 89 | name: matcher 90 | sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb 91 | url: "https://pub.dev" 92 | source: hosted 93 | version: "0.12.16+1" 94 | material_color_utilities: 95 | dependency: transitive 96 | description: 97 | name: material_color_utilities 98 | sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a" 99 | url: "https://pub.dev" 100 | source: hosted 101 | version: "0.8.0" 102 | meta: 103 | dependency: transitive 104 | description: 105 | name: meta 106 | sha256: d584fa6707a52763a52446f02cc621b077888fb63b93bbcb1143a7be5a0c0c04 107 | url: "https://pub.dev" 108 | source: hosted 109 | version: "1.11.0" 110 | path: 111 | dependency: transitive 112 | description: 113 | name: path 114 | sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af" 115 | url: "https://pub.dev" 116 | source: hosted 117 | version: "1.9.0" 118 | sky_engine: 119 | dependency: transitive 120 | description: flutter 121 | source: sdk 122 | version: "0.0.99" 123 | source_span: 124 | dependency: transitive 125 | description: 126 | name: source_span 127 | sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c" 128 | url: "https://pub.dev" 129 | source: hosted 130 | version: "1.10.0" 131 | stack_trace: 132 | dependency: transitive 133 | description: 134 | name: stack_trace 135 | sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b" 136 | url: "https://pub.dev" 137 | source: hosted 138 | version: "1.11.1" 139 | stream_channel: 140 | dependency: transitive 141 | description: 142 | name: stream_channel 143 | sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7 144 | url: "https://pub.dev" 145 | source: hosted 146 | version: "2.1.2" 147 | string_scanner: 148 | dependency: transitive 149 | description: 150 | name: string_scanner 151 | sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde" 152 | url: "https://pub.dev" 153 | source: hosted 154 | version: "1.2.0" 155 | term_glyph: 156 | dependency: transitive 157 | description: 158 | name: term_glyph 159 | sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84 160 | url: "https://pub.dev" 161 | source: hosted 162 | version: "1.2.1" 163 | test_api: 164 | dependency: transitive 165 | description: 166 | name: test_api 167 | sha256: "5c2f730018264d276c20e4f1503fd1308dfbbae39ec8ee63c5236311ac06954b" 168 | url: "https://pub.dev" 169 | source: hosted 170 | version: "0.6.1" 171 | vector_math: 172 | dependency: transitive 173 | description: 174 | name: vector_math 175 | sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" 176 | url: "https://pub.dev" 177 | source: hosted 178 | version: "2.1.4" 179 | vm_service: 180 | dependency: transitive 181 | description: 182 | name: vm_service 183 | sha256: b3d56ff4341b8f182b96aceb2fa20e3dcb336b9f867bc0eafc0de10f1048e957 184 | url: "https://pub.dev" 185 | source: hosted 186 | version: "13.0.0" 187 | sdks: 188 | dart: ">=3.3.4 <4.0.0" 189 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: crypto_tracker 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: '>=3.3.4 <4.0.0' 22 | 23 | dependencies: 24 | flutter: 25 | sdk: flutter 26 | 27 | # The following adds the Cupertino Icons font to your application. 28 | # Use with the CupertinoIcons class for iOS style icons. 29 | # cupertino_icons: ^1.0.2 30 | 31 | dev_dependencies: 32 | flutter_test: 33 | sdk: flutter 34 | 35 | # For information on the generic Dart part of this file, see the 36 | # following page: https://dart.dev/tools/pub/pubspec 37 | 38 | # The following section is specific to Flutter. 39 | flutter: 40 | # The following line ensures that the Material Icons font is 41 | # included with your application, so that you can use the icons in 42 | # the material Icons class. 43 | uses-material-design: true 44 | 45 | # To add assets to your application, add an assets section, like this: 46 | assets: 47 | - assets/crypto_logos/ 48 | # - images/a_dot_ham.jpeg 49 | 50 | # An image asset can refer to one or more resolution-specific "variants", see 51 | # https://flutter.dev/assets-and-images/#resolution-aware. 52 | 53 | # For details regarding adding assets from package dependencies, see 54 | # https://flutter.dev/assets-and-images/#from-packages 55 | 56 | # To add custom fonts to your application, add a fonts section here, 57 | # in this "flutter" section. Each entry in this list should have a 58 | # "family" key with the font family name, and a "fonts" key with a 59 | # list giving the asset and other descriptors for the font. For 60 | # example: 61 | # fonts: 62 | # - family: Schyler 63 | # fonts: 64 | # - asset: fonts/Schyler-Regular.ttf 65 | # - asset: fonts/Schyler-Italic.ttf 66 | # style: italic 67 | # - family: Trajan Pro 68 | # fonts: 69 | # - asset: fonts/TrajanPro.ttf 70 | # - asset: fonts/TrajanPro_Bold.ttf 71 | # weight: 700 72 | # 73 | # For details regarding fonts from package dependencies, 74 | # see https://flutter.dev/custom-fonts/#from-packages 75 | --------------------------------------------------------------------------------