├── .gitignore ├── LICENSE ├── README.md ├── chart-project ├── .gitignore ├── .vscode │ └── settings.json ├── package-lock.json ├── package.json └── src │ ├── tvchart.css │ ├── tvchart.html │ └── tvchart.ts ├── docs └── images │ ├── chart-src.png │ ├── flutter-assets.png │ ├── screenshot-android.png │ └── screenshot-ios.PNG └── flutter_app ├── .gitignore ├── .metadata ├── .vscode ├── launch.json ├── settings.json └── tasks.json ├── README.md ├── analysis_options.yaml ├── android ├── .gitignore ├── app │ ├── build.gradle │ └── src │ │ ├── debug │ │ └── AndroidManifest.xml │ │ ├── main │ │ ├── AndroidManifest.xml │ │ ├── kotlin │ │ │ └── com │ │ │ │ └── andree │ │ │ │ └── chart │ │ │ │ └── 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 ├── ios ├── .gitignore ├── Flutter │ ├── AppFrameworkInfo.plist │ ├── Debug.xcconfig │ └── Release.xcconfig ├── Podfile ├── Podfile.lock ├── Runner.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ ├── IDEWorkspaceChecks.plist │ │ │ └── WorkspaceSettings.xcsettings │ └── xcshareddata │ │ └── xcschemes │ │ └── Runner.xcscheme ├── Runner.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ ├── IDEWorkspaceChecks.plist │ │ └── WorkspaceSettings.xcsettings └── Runner │ ├── AppDelegate.swift │ ├── Assets.xcassets │ ├── AppIcon.appiconset │ │ ├── Contents.json │ │ ├── Icon-App-1024x1024@1x.png │ │ ├── Icon-App-20x20@1x.png │ │ ├── Icon-App-20x20@2x.png │ │ ├── Icon-App-20x20@3x.png │ │ ├── Icon-App-29x29@1x.png │ │ ├── Icon-App-29x29@2x.png │ │ ├── Icon-App-29x29@3x.png │ │ ├── Icon-App-40x40@1x.png │ │ ├── Icon-App-40x40@2x.png │ │ ├── Icon-App-40x40@3x.png │ │ ├── Icon-App-60x60@2x.png │ │ ├── Icon-App-60x60@3x.png │ │ ├── Icon-App-76x76@1x.png │ │ ├── Icon-App-76x76@2x.png │ │ └── Icon-App-83.5x83.5@2x.png │ └── 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 ├── commons │ ├── localhost.dart │ ├── random.dart │ └── tzdt.dart ├── components │ └── tvchart │ │ ├── tvchart.dart │ │ ├── tvchart_types.dart │ │ └── tvchart_types.g.dart ├── data │ └── historical.dart ├── main.dart └── pages │ └── home.dart ├── pubspec.lock ├── pubspec.yaml ├── test └── widget_test.dart └── web ├── favicon.png ├── icons ├── Icon-192.png └── Icon-512.png ├── index.html └── manifest.json /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT No Attribution 2 | 3 | Copyright (c) 2021 Andree Yosua 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this 6 | software and associated documentation files (the "Software"), to deal in the Software 7 | without restriction, including without limitation the rights to use, copy, modify, 8 | merge, publish, distribute, sublicense, and/or sell copies of the Software, and to 9 | permit persons to whom the Software is furnished to do so. 10 | 11 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 12 | INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 13 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 14 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 15 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 16 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Flutter Charting Library Example 2 | 3 | This is an example on how to add charting_library to your flutter application with JS api. 4 | The repo contains 2 projects, flutter project and js project. 5 | Before starting, read prerequisite. 6 | 7 | # Prerequisite 8 | 9 | - Flutter 2.0 or newer 10 | - Access to charting_library v19.x (or newer) 11 | - (IMPORTANT) Knowledge on how to use charting_library on web 12 | 13 | # Step to build 14 | 15 | ## Bundling charting_library 16 | 17 | First you need to bundle charting_library as a single page web. 18 | You can use your own bundler/way. But if you need quick you can use method this example use: 19 | 20 | 1. Grab these files and put it on `src/charting_library` 21 | 22 | ![chart-src](./docs/images/chart-src.png) 23 | 24 | 2. Run build script `npm run build` 25 | 3. Bundled files should be in dist folder 26 | 27 | ## Add charting_library to flutter 28 | 29 | This example is using `flutter_inappwebview` for WebView and localhost, to serve webpage directly from assets folder. You can use other WebView packages/serve via server, if you wish. 30 | For this example follow this method: 31 | 32 | 1. Grab the dist files from step before 33 | 2. Create folder `assets/tvchart`, and paste those files. 34 | 3. Create folder `assets/tvchart/public`, and paste all files charting_library needed (the one needed from library_path). This is assets folder should like right now. 35 | 36 | ![flutter-assets](./docs/images/flutter-assets.png) 37 | 38 | 4. Add folder to your `pubspec.yaml` assets list, you need to specify all subfolder as well check example `pubspec.yaml`. 39 | 5. Run flutter app on your devices. 40 | 41 | 42 | ## (Optional) Adding new types/class 43 | 44 | If you needed types/class that doesn't exist yet on `tvchart_types.dart` you can check the typedef (.d.ts files) on charting_library and try to add an equivalent class to `tvchart_types.dart`, and then add `toJson` and `fromJson` function (see other class as example). 45 | After that run build_runner to generate new/updated `JsonSerializable` annotated class. You can run it using vscode tasks `Run build_runner` or with terminal command 46 | ``` 47 | flutter pub run build_runner build --delete-conflicting-outputs 48 | ``` 49 | 50 | The only limitation here is any properties with `function` values is not supported, as `function` is not a JSON valid value. 51 | 52 | ## Some points that you should know 53 | 54 | - Since this example is using localhost http to serve files, you need to allow localhost on `ios/Runner/Info.plist` with `NSAllowsLocalNetworking` (iOS 10+). If you serve the webpage from server with https this won't be needed. By default iOS block all http pages. 55 | 56 | ``` 57 | NSAppTransportSecurity 58 | 59 | NSAllowsLocalNetworking 60 | 61 | 62 | ``` 63 | 64 | # FAQ 65 | 66 | **Q:** I don't have access to charting_library, help! 67 | 68 | **A:** Not my problem. 69 | ## 70 | 71 | **Q:** charting_library files is missing here. 72 | 73 | **A:** It's not included, you need to add yours to this project. 74 | ## 75 | 76 | **Q:** Do you have any apk build to test/check? 77 | 78 | **A:** I will not provide any kind of build for this example. 79 | ## 80 | 81 | **Q:** I want to ask question. 82 | 83 | **A:** Open new issue. 84 | ## 85 | 86 | **Q:** I have error in charting_library side, symbol not exist, etc. 87 | 88 | **A:** I won't answer any question that is not related to integration chart to flutter, if you don't know how to use charting_library ask on their discord/github, not here. 89 | ## 90 | 91 | **Q:** It doesn't work because this example use old version of charting_library. 92 | 93 | **A:** Open new issue. I'll try to update the example with latest available lib version. 94 | ## 95 | 96 | **Q:** Does this work on Android, iOS, and Web? 97 | 98 | **A:** Yes, Yes, No atm. 99 | ## 100 | 101 | **Q:** Does this work on other WebView packages? 102 | 103 | **A:** As long the packages support 2 way communication between dart and JS, Yes. Otherwise, No. 104 | ## 105 | 106 | **Q:** Does the chart react to theme changes? 107 | 108 | **A:** Yes if used under `MaterialApp`, I haven't test it under `CupertinoApp` yet. 109 | ## 110 | 111 | **Q:** There are some types/class I needed that not exist on `tvchart_types.dart` yet. How do I add them? 112 | 113 | **A:** You can check the charting_library typedef, and add stuff you needed. Check `(Optional) Adding new types/class`. 114 | ## 115 | 116 | **Q:** How do I add properties with `function` value? 117 | 118 | **A:** The communication between dart and JS relies on JSON parse/decode, since function is not valid JSON value, you need to add any function value on JS side, maybe by passing message on json you could add predefined function value on JS side. If you know a better way to do this, let me know. 119 | ## 120 | 121 | # Screenshot 122 | 123 | ## Android 124 | 125 | Android 11 126 | 127 | screenshot-android 128 | 129 | ## iOS 130 | 131 | iOS 14.4.2 with `MaterialApp` 132 | 133 | screenshot-ios 134 | -------------------------------------------------------------------------------- /chart-project/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | .pnpm-debug.log* 9 | 10 | # Diagnostic reports (https://nodejs.org/api/report.html) 11 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 12 | 13 | # Runtime data 14 | pids 15 | *.pid 16 | *.seed 17 | *.pid.lock 18 | 19 | # Directory for instrumented libs generated by jscoverage/JSCover 20 | lib-cov 21 | 22 | # Coverage directory used by tools like istanbul 23 | coverage 24 | *.lcov 25 | 26 | # nyc test coverage 27 | .nyc_output 28 | 29 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 30 | .grunt 31 | 32 | # Bower dependency directory (https://bower.io/) 33 | bower_components 34 | 35 | # node-waf configuration 36 | .lock-wscript 37 | 38 | # Compiled binary addons (https://nodejs.org/api/addons.html) 39 | build/Release 40 | 41 | # Dependency directories 42 | node_modules/ 43 | jspm_packages/ 44 | 45 | # Snowpack dependency directory (https://snowpack.dev/) 46 | web_modules/ 47 | 48 | # TypeScript cache 49 | *.tsbuildinfo 50 | 51 | # Optional npm cache directory 52 | .npm 53 | 54 | # Optional eslint cache 55 | .eslintcache 56 | 57 | # Microbundle cache 58 | .rpt2_cache/ 59 | .rts2_cache_cjs/ 60 | .rts2_cache_es/ 61 | .rts2_cache_umd/ 62 | 63 | # Optional REPL history 64 | .node_repl_history 65 | 66 | # Output of 'npm pack' 67 | *.tgz 68 | 69 | # Yarn Integrity file 70 | .yarn-integrity 71 | 72 | # dotenv environment variables file 73 | .env 74 | .env.test 75 | .env.production 76 | 77 | # parcel-bundler cache (https://parceljs.org/) 78 | .cache 79 | .parcel-cache 80 | 81 | # Next.js build output 82 | .next 83 | out 84 | 85 | # Nuxt.js build / generate output 86 | .nuxt 87 | dist 88 | 89 | # Gatsby files 90 | .cache/ 91 | # Comment in the public line in if your project uses Gatsby and not Next.js 92 | # https://nextjs.org/blog/next-9-1#public-directory-support 93 | # public 94 | 95 | # vuepress build output 96 | .vuepress/dist 97 | 98 | # Serverless directories 99 | .serverless/ 100 | 101 | # FuseBox cache 102 | .fusebox/ 103 | 104 | # DynamoDB Local files 105 | .dynamodb/ 106 | 107 | # TernJS port file 108 | .tern-port 109 | 110 | # Stores VSCode versions used for testing VSCode extensions 111 | .vscode-test 112 | 113 | # yarn v2 114 | .yarn/cache 115 | .yarn/unplugged 116 | .yarn/build-state.yml 117 | .yarn/install-state.gz 118 | .pnp.* 119 | 120 | /dist 121 | /src/charting_library -------------------------------------------------------------------------------- /chart-project/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.rulers": [80], 3 | "editor.tabSize": 2, 4 | "editor.defaultFormatter": "esbenp.prettier-vscode", 5 | "prettier.printWidth": 80, 6 | "prettier.tabWidth": 2 7 | } -------------------------------------------------------------------------------- /chart-project/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "chart-project", 3 | "version": "1.0.0", 4 | "description": "", 5 | "scripts": { 6 | "build": "parcel build src/tvchart.html --public-url ./ --no-content-hash --no-source-maps" 7 | }, 8 | "author": "Andree Yosua", 9 | "license": "MIT-0", 10 | "devDependencies": { 11 | "@types/jquery": "^3.5.6", 12 | "parcel": "^2.0.0-rc.0", 13 | "typescript": "^4.4.3" 14 | }, 15 | "browserslist": [ 16 | "last 10 Chrome versions", 17 | "last 2 iOS major versions" 18 | ] 19 | } 20 | -------------------------------------------------------------------------------- /chart-project/src/tvchart.css: -------------------------------------------------------------------------------- 1 | body { 2 | padding: 0; 3 | margin: 0; 4 | width: 100vw; 5 | height: 100vh; 6 | overflow: hidden; 7 | } 8 | 9 | div#tvchart { 10 | width: 100vw; 11 | height: 100vh; 12 | min-width: 0; 13 | min-height: 0; 14 | } 15 | -------------------------------------------------------------------------------- /chart-project/src/tvchart.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Document 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /chart-project/src/tvchart.ts: -------------------------------------------------------------------------------- 1 | import * as TradingView from "./charting_library/charting_library"; 2 | 3 | declare global { 4 | interface Window { 5 | flutter_inappwebview: { 6 | callHandler: (handlerName: string, ...args: any) => Promise; 7 | }; 8 | toggleDarkTheme: () => void; 9 | toggleLightTheme: () => void; 10 | callOnTick: (payload: string) => void; 11 | } 12 | } 13 | 14 | const onTickMap: Map = new Map(); 15 | 16 | const datafeed: TradingView.IBasicDataFeed = { 17 | onReady: (callback: TradingView.OnReadyCallback) => { 18 | window.flutter_inappwebview.callHandler("onReady").then((result) => { 19 | callback(result); 20 | }); 21 | }, 22 | searchSymbols: ( 23 | userInput: string, 24 | exchange: string, 25 | symbolType: string, 26 | onResult: TradingView.SearchSymbolsCallback 27 | ) => { 28 | window.flutter_inappwebview 29 | .callHandler("searchSymbols", userInput, exchange, symbolType) 30 | .then((value) => { 31 | if (Array.isArray(value)) { 32 | onResult(value); 33 | } else { 34 | onResult([]); 35 | } 36 | }) 37 | .catch((reason) => { 38 | onResult([]); 39 | }); 40 | }, 41 | resolveSymbol: ( 42 | symbolName: string, 43 | onResolve: TradingView.ResolveCallback, 44 | onError: TradingView.ErrorCallback, 45 | extension: TradingView.SymbolResolveExtension 46 | ) => { 47 | window.flutter_inappwebview 48 | .callHandler("resolveSymbol", symbolName) 49 | .then((value) => { 50 | if (value !== null && typeof value === "object") { 51 | onResolve(value as TradingView.LibrarySymbolInfo); 52 | } else if (typeof value === "string") { 53 | onError(value); 54 | } else { 55 | onError("Unexpected resolveSymbol return type"); 56 | } 57 | }) 58 | .catch((reason) => { 59 | onError("Unexpected error on resolveSymbol"); 60 | }); 61 | }, 62 | getBars: ( 63 | symbolInfo: TradingView.LibrarySymbolInfo, 64 | resolution: TradingView.ResolutionString, 65 | periodParams: TradingView.PeriodParams, 66 | onResult: TradingView.HistoryCallback, 67 | onError: TradingView.ErrorCallback 68 | ) => { 69 | window.flutter_inappwebview 70 | .callHandler("getBars", symbolInfo, resolution, periodParams) 71 | .then((value) => { 72 | if (value !== null && typeof value === "object") { 73 | onResult(value.bars, value.meta); 74 | } else if (typeof value === "string") { 75 | onError(value); 76 | } else { 77 | onError("Unexpected getBars return type"); 78 | } 79 | }) 80 | .catch((reason) => { 81 | onError("Unexpected error on getBars"); 82 | }); 83 | }, 84 | subscribeBars: ( 85 | symbolInfo: TradingView.LibrarySymbolInfo, 86 | resolution: TradingView.ResolutionString, 87 | onTick: TradingView.SubscribeBarsCallback, 88 | listenerGuid: string, 89 | onResetCacheNeededCallback: () => void 90 | ) => { 91 | onTickMap.set(listenerGuid, onTick); 92 | 93 | window.flutter_inappwebview.callHandler( 94 | "subscribeBars", 95 | symbolInfo, 96 | resolution, 97 | listenerGuid 98 | ); 99 | }, 100 | unsubscribeBars: (listenerGuid: string) => { 101 | onTickMap.delete(listenerGuid); 102 | 103 | window.flutter_inappwebview.callHandler("unsubscribeBars", listenerGuid); 104 | } 105 | }; 106 | 107 | let chart: TradingView.IChartingLibraryWidget | undefined; 108 | 109 | function toggleLightTheme() { 110 | if (chart != undefined) { 111 | chart.changeTheme("Light"); 112 | } 113 | } 114 | window.toggleLightTheme = toggleLightTheme; 115 | 116 | function toggleDarkTheme() { 117 | if (chart != undefined) { 118 | chart.changeTheme("Dark"); 119 | } 120 | } 121 | window.toggleDarkTheme = toggleDarkTheme; 122 | 123 | function callOnTick(payload: string) { 124 | const payloadObject: Record = JSON.parse(payload); 125 | const listenerGuid: string | undefined = payloadObject["listenerGuid"]; 126 | const bar: TradingView.Bar | undefined = payloadObject["bar"]; 127 | 128 | if (listenerGuid == undefined || bar == undefined) return; 129 | 130 | if (onTickMap.has(listenerGuid)) { 131 | const onTick = onTickMap.get(listenerGuid); 132 | onTick(bar); 133 | } 134 | } 135 | window.callOnTick = callOnTick; 136 | 137 | window.addEventListener("flutterInAppWebViewPlatformReady", (event) => { 138 | window.flutter_inappwebview 139 | .callHandler("start") 140 | .then((options: TradingView.ChartingLibraryWidgetOptions) => { 141 | options.container = "tvchart"; 142 | options.library_path = "public/"; 143 | options.datafeed = datafeed; 144 | 145 | if (chart == undefined) { 146 | console.log(TradingView.version()); 147 | chart = new TradingView.widget(options); 148 | chart.onChartReady(() => { 149 | chart.subscribe("onAutoSaveNeeded", () => { 150 | chart.save((state) => { 151 | window.flutter_inappwebview.callHandler("saveData", state); 152 | }); 153 | }); 154 | }); 155 | } 156 | }); 157 | }); 158 | -------------------------------------------------------------------------------- /docs/images/chart-src.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SuperPenguin/charting-library-flutter/d780e5b3682e3d4bb3e5f8ee832f1f5f7e63832f/docs/images/chart-src.png -------------------------------------------------------------------------------- /docs/images/flutter-assets.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SuperPenguin/charting-library-flutter/d780e5b3682e3d4bb3e5f8ee832f1f5f7e63832f/docs/images/flutter-assets.png -------------------------------------------------------------------------------- /docs/images/screenshot-android.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SuperPenguin/charting-library-flutter/d780e5b3682e3d4bb3e5f8ee832f1f5f7e63832f/docs/images/screenshot-android.png -------------------------------------------------------------------------------- /docs/images/screenshot-ios.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SuperPenguin/charting-library-flutter/d780e5b3682e3d4bb3e5f8ee832f1f5f7e63832f/docs/images/screenshot-ios.PNG -------------------------------------------------------------------------------- /flutter_app/.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 | 48 | # Bundled chart files 49 | /assets/tvchart/ -------------------------------------------------------------------------------- /flutter_app/.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: 1d9032c7e1d867f071f2277eb1673e8f9b0274e3 8 | channel: unknown 9 | 10 | project_type: app 11 | -------------------------------------------------------------------------------- /flutter_app/.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "name": "Launch Debug", 6 | "request": "launch", 7 | "type": "dart", 8 | "program": "lib/main.dart", 9 | "flutterMode": "debug" 10 | } 11 | ] 12 | } -------------------------------------------------------------------------------- /flutter_app/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.tabSize": 2 3 | } -------------------------------------------------------------------------------- /flutter_app/.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "tasks": [ 4 | { 5 | "label": "Run build_runner", 6 | "group": "build", 7 | "type": "flutter", 8 | "detail": "Regenerate all autogenerated files", 9 | "command": "flutter", 10 | "args": [ 11 | "pub", 12 | "run", 13 | "build_runner", 14 | "build", 15 | "--delete-conflicting-outputs" 16 | ], 17 | "problemMatcher": [] 18 | } 19 | ] 20 | } -------------------------------------------------------------------------------- /flutter_app/README.md: -------------------------------------------------------------------------------- 1 | # chart 2 | 3 | A new Flutter project. 4 | 5 | ## Getting Started 6 | 7 | This project is a starting point for a Flutter application. 8 | 9 | A few resources to get you started if this is your first Flutter project: 10 | 11 | - [Lab: Write your first Flutter app](https://flutter.dev/docs/get-started/codelab) 12 | - [Cookbook: Useful Flutter samples](https://flutter.dev/docs/cookbook) 13 | 14 | For help getting started with Flutter, view our 15 | [online documentation](https://flutter.dev/docs), which offers tutorials, 16 | samples, guidance on mobile development, and a full API reference. 17 | -------------------------------------------------------------------------------- /flutter_app/analysis_options.yaml: -------------------------------------------------------------------------------- 1 | include: package:flutter_lints/flutter.yaml -------------------------------------------------------------------------------- /flutter_app/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 | -------------------------------------------------------------------------------- /flutter_app/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 30 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 "com.andree.chart" 38 | minSdkVersion 17 39 | targetSdkVersion 30 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 | -------------------------------------------------------------------------------- /flutter_app/android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /flutter_app/android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 7 | 14 | 18 | 22 | 27 | 31 | 32 | 33 | 34 | 35 | 36 | 38 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /flutter_app/android/app/src/main/kotlin/com/andree/chart/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.andree.chart 2 | 3 | import io.flutter.embedding.android.FlutterActivity 4 | 5 | class MainActivity: FlutterActivity() { 6 | } 7 | -------------------------------------------------------------------------------- /flutter_app/android/app/src/main/res/drawable-v21/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /flutter_app/android/app/src/main/res/drawable/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /flutter_app/android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SuperPenguin/charting-library-flutter/d780e5b3682e3d4bb3e5f8ee832f1f5f7e63832f/flutter_app/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /flutter_app/android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SuperPenguin/charting-library-flutter/d780e5b3682e3d4bb3e5f8ee832f1f5f7e63832f/flutter_app/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /flutter_app/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SuperPenguin/charting-library-flutter/d780e5b3682e3d4bb3e5f8ee832f1f5f7e63832f/flutter_app/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /flutter_app/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SuperPenguin/charting-library-flutter/d780e5b3682e3d4bb3e5f8ee832f1f5f7e63832f/flutter_app/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /flutter_app/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SuperPenguin/charting-library-flutter/d780e5b3682e3d4bb3e5f8ee832f1f5f7e63832f/flutter_app/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /flutter_app/android/app/src/main/res/values-night/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /flutter_app/android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /flutter_app/android/app/src/profile/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /flutter_app/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 | task clean(type: Delete) { 30 | delete rootProject.buildDir 31 | } 32 | -------------------------------------------------------------------------------- /flutter_app/android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx1536M 2 | android.useAndroidX=true 3 | android.enableJetifier=true 4 | -------------------------------------------------------------------------------- /flutter_app/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-6.7-all.zip 7 | -------------------------------------------------------------------------------- /flutter_app/android/settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | 3 | def localPropertiesFile = new File(rootProject.projectDir, "local.properties") 4 | def properties = new Properties() 5 | 6 | assert localPropertiesFile.exists() 7 | localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) } 8 | 9 | def flutterSdkPath = properties.getProperty("flutter.sdk") 10 | assert flutterSdkPath != null, "flutter.sdk not set in local.properties" 11 | apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle" 12 | -------------------------------------------------------------------------------- /flutter_app/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 | -------------------------------------------------------------------------------- /flutter_app/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 | -------------------------------------------------------------------------------- /flutter_app/ios/Flutter/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /flutter_app/ios/Flutter/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /flutter_app/ios/Podfile: -------------------------------------------------------------------------------- 1 | # Uncomment this line to define a global platform for your project 2 | # platform :ios, '9.0' 3 | 4 | # CocoaPods analytics sends network stats synchronously affecting flutter build latency. 5 | ENV['COCOAPODS_DISABLE_STATS'] = 'true' 6 | 7 | project 'Runner', { 8 | 'Debug' => :debug, 9 | 'Profile' => :release, 10 | 'Release' => :release, 11 | } 12 | 13 | def flutter_root 14 | generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__) 15 | unless File.exist?(generated_xcode_build_settings_path) 16 | raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first" 17 | end 18 | 19 | File.foreach(generated_xcode_build_settings_path) do |line| 20 | matches = line.match(/FLUTTER_ROOT\=(.*)/) 21 | return matches[1].strip if matches 22 | end 23 | raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get" 24 | end 25 | 26 | require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) 27 | 28 | flutter_ios_podfile_setup 29 | 30 | target 'Runner' do 31 | use_frameworks! 32 | use_modular_headers! 33 | 34 | flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) 35 | end 36 | 37 | post_install do |installer| 38 | installer.pods_project.targets.each do |target| 39 | flutter_additional_ios_build_settings(target) 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /flutter_app/ios/Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - Flutter (1.0.0) 3 | - flutter_inappwebview (0.0.1): 4 | - Flutter 5 | - flutter_inappwebview/Core (= 0.0.1) 6 | - OrderedSet (~> 5.0) 7 | - flutter_inappwebview/Core (0.0.1): 8 | - Flutter 9 | - OrderedSet (~> 5.0) 10 | - OrderedSet (5.0.0) 11 | 12 | DEPENDENCIES: 13 | - Flutter (from `Flutter`) 14 | - flutter_inappwebview (from `.symlinks/plugins/flutter_inappwebview/ios`) 15 | 16 | SPEC REPOS: 17 | trunk: 18 | - OrderedSet 19 | 20 | EXTERNAL SOURCES: 21 | Flutter: 22 | :path: Flutter 23 | flutter_inappwebview: 24 | :path: ".symlinks/plugins/flutter_inappwebview/ios" 25 | 26 | SPEC CHECKSUMS: 27 | Flutter: 434fef37c0980e73bb6479ef766c45957d4b510c 28 | flutter_inappwebview: bfd58618f49dc62f2676de690fc6dcda1d6c3721 29 | OrderedSet: aaeb196f7fef5a9edf55d89760da9176ad40b93c 30 | 31 | PODFILE CHECKSUM: aafe91acc616949ddb318b77800a7f51bffa2a4c 32 | 33 | COCOAPODS: 1.10.1 34 | -------------------------------------------------------------------------------- /flutter_app/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 | FB5A1854262146F038E9C300 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B5C447AA834112AA9792FB36 /* Pods_Runner.framework */; }; 17 | /* End PBXBuildFile section */ 18 | 19 | /* Begin PBXCopyFilesBuildPhase section */ 20 | 9705A1C41CF9048500538489 /* Embed Frameworks */ = { 21 | isa = PBXCopyFilesBuildPhase; 22 | buildActionMask = 2147483647; 23 | dstPath = ""; 24 | dstSubfolderSpec = 10; 25 | files = ( 26 | ); 27 | name = "Embed Frameworks"; 28 | runOnlyForDeploymentPostprocessing = 0; 29 | }; 30 | /* End PBXCopyFilesBuildPhase section */ 31 | 32 | /* Begin PBXFileReference section */ 33 | 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; 34 | 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; 35 | 1A05C32F3385B99993D53CCF /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; 36 | 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; 37 | 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; 38 | 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 39 | 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; 40 | 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; 41 | 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; 42 | 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; 43 | 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 44 | 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 45 | 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 46 | 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 47 | B5C447AA834112AA9792FB36 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 48 | D9906CCB91D4599A4E3D1884 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; 49 | F9040B2FB3364DE7DB998169 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; 50 | /* End PBXFileReference section */ 51 | 52 | /* Begin PBXFrameworksBuildPhase section */ 53 | 97C146EB1CF9000F007C117D /* Frameworks */ = { 54 | isa = PBXFrameworksBuildPhase; 55 | buildActionMask = 2147483647; 56 | files = ( 57 | FB5A1854262146F038E9C300 /* Pods_Runner.framework in Frameworks */, 58 | ); 59 | runOnlyForDeploymentPostprocessing = 0; 60 | }; 61 | /* End PBXFrameworksBuildPhase section */ 62 | 63 | /* Begin PBXGroup section */ 64 | 1BD99A60DFB2BFE53A02205B /* Frameworks */ = { 65 | isa = PBXGroup; 66 | children = ( 67 | B5C447AA834112AA9792FB36 /* Pods_Runner.framework */, 68 | ); 69 | name = Frameworks; 70 | sourceTree = ""; 71 | }; 72 | 9740EEB11CF90186004384FC /* Flutter */ = { 73 | isa = PBXGroup; 74 | children = ( 75 | 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, 76 | 9740EEB21CF90195004384FC /* Debug.xcconfig */, 77 | 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, 78 | 9740EEB31CF90195004384FC /* Generated.xcconfig */, 79 | ); 80 | name = Flutter; 81 | sourceTree = ""; 82 | }; 83 | 97C146E51CF9000F007C117D = { 84 | isa = PBXGroup; 85 | children = ( 86 | 9740EEB11CF90186004384FC /* Flutter */, 87 | 97C146F01CF9000F007C117D /* Runner */, 88 | 97C146EF1CF9000F007C117D /* Products */, 89 | 9DDA067605A04E93033AAC14 /* Pods */, 90 | 1BD99A60DFB2BFE53A02205B /* Frameworks */, 91 | ); 92 | sourceTree = ""; 93 | }; 94 | 97C146EF1CF9000F007C117D /* Products */ = { 95 | isa = PBXGroup; 96 | children = ( 97 | 97C146EE1CF9000F007C117D /* Runner.app */, 98 | ); 99 | name = Products; 100 | sourceTree = ""; 101 | }; 102 | 97C146F01CF9000F007C117D /* Runner */ = { 103 | isa = PBXGroup; 104 | children = ( 105 | 97C146FA1CF9000F007C117D /* Main.storyboard */, 106 | 97C146FD1CF9000F007C117D /* Assets.xcassets */, 107 | 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, 108 | 97C147021CF9000F007C117D /* Info.plist */, 109 | 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, 110 | 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, 111 | 74858FAE1ED2DC5600515810 /* AppDelegate.swift */, 112 | 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */, 113 | ); 114 | path = Runner; 115 | sourceTree = ""; 116 | }; 117 | 9DDA067605A04E93033AAC14 /* Pods */ = { 118 | isa = PBXGroup; 119 | children = ( 120 | 1A05C32F3385B99993D53CCF /* Pods-Runner.debug.xcconfig */, 121 | F9040B2FB3364DE7DB998169 /* Pods-Runner.release.xcconfig */, 122 | D9906CCB91D4599A4E3D1884 /* Pods-Runner.profile.xcconfig */, 123 | ); 124 | name = Pods; 125 | path = Pods; 126 | sourceTree = ""; 127 | }; 128 | /* End PBXGroup section */ 129 | 130 | /* Begin PBXNativeTarget section */ 131 | 97C146ED1CF9000F007C117D /* Runner */ = { 132 | isa = PBXNativeTarget; 133 | buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; 134 | buildPhases = ( 135 | 95FD5A11B5C6AD430069C8E4 /* [CP] Check Pods Manifest.lock */, 136 | 9740EEB61CF901F6004384FC /* Run Script */, 137 | 97C146EA1CF9000F007C117D /* Sources */, 138 | 97C146EB1CF9000F007C117D /* Frameworks */, 139 | 97C146EC1CF9000F007C117D /* Resources */, 140 | 9705A1C41CF9048500538489 /* Embed Frameworks */, 141 | 3B06AD1E1E4923F5004D2608 /* Thin Binary */, 142 | 70D1AA20BD9FAC4790386D28 /* [CP] Embed Pods Frameworks */, 143 | ); 144 | buildRules = ( 145 | ); 146 | dependencies = ( 147 | ); 148 | name = Runner; 149 | productName = Runner; 150 | productReference = 97C146EE1CF9000F007C117D /* Runner.app */; 151 | productType = "com.apple.product-type.application"; 152 | }; 153 | /* End PBXNativeTarget section */ 154 | 155 | /* Begin PBXProject section */ 156 | 97C146E61CF9000F007C117D /* Project object */ = { 157 | isa = PBXProject; 158 | attributes = { 159 | LastUpgradeCheck = 1020; 160 | ORGANIZATIONNAME = ""; 161 | TargetAttributes = { 162 | 97C146ED1CF9000F007C117D = { 163 | CreatedOnToolsVersion = 7.3.1; 164 | LastSwiftMigration = 1100; 165 | }; 166 | }; 167 | }; 168 | buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; 169 | compatibilityVersion = "Xcode 9.3"; 170 | developmentRegion = en; 171 | hasScannedForEncodings = 0; 172 | knownRegions = ( 173 | en, 174 | Base, 175 | ); 176 | mainGroup = 97C146E51CF9000F007C117D; 177 | productRefGroup = 97C146EF1CF9000F007C117D /* Products */; 178 | projectDirPath = ""; 179 | projectRoot = ""; 180 | targets = ( 181 | 97C146ED1CF9000F007C117D /* Runner */, 182 | ); 183 | }; 184 | /* End PBXProject section */ 185 | 186 | /* Begin PBXResourcesBuildPhase section */ 187 | 97C146EC1CF9000F007C117D /* Resources */ = { 188 | isa = PBXResourcesBuildPhase; 189 | buildActionMask = 2147483647; 190 | files = ( 191 | 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, 192 | 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, 193 | 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, 194 | 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, 195 | ); 196 | runOnlyForDeploymentPostprocessing = 0; 197 | }; 198 | /* End PBXResourcesBuildPhase section */ 199 | 200 | /* Begin PBXShellScriptBuildPhase section */ 201 | 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { 202 | isa = PBXShellScriptBuildPhase; 203 | buildActionMask = 2147483647; 204 | files = ( 205 | ); 206 | inputPaths = ( 207 | ); 208 | name = "Thin Binary"; 209 | outputPaths = ( 210 | ); 211 | runOnlyForDeploymentPostprocessing = 0; 212 | shellPath = /bin/sh; 213 | shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; 214 | }; 215 | 70D1AA20BD9FAC4790386D28 /* [CP] Embed Pods Frameworks */ = { 216 | isa = PBXShellScriptBuildPhase; 217 | buildActionMask = 2147483647; 218 | files = ( 219 | ); 220 | inputFileListPaths = ( 221 | "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", 222 | ); 223 | name = "[CP] Embed Pods Frameworks"; 224 | outputFileListPaths = ( 225 | "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", 226 | ); 227 | runOnlyForDeploymentPostprocessing = 0; 228 | shellPath = /bin/sh; 229 | shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; 230 | showEnvVarsInLog = 0; 231 | }; 232 | 95FD5A11B5C6AD430069C8E4 /* [CP] Check Pods Manifest.lock */ = { 233 | isa = PBXShellScriptBuildPhase; 234 | buildActionMask = 2147483647; 235 | files = ( 236 | ); 237 | inputFileListPaths = ( 238 | ); 239 | inputPaths = ( 240 | "${PODS_PODFILE_DIR_PATH}/Podfile.lock", 241 | "${PODS_ROOT}/Manifest.lock", 242 | ); 243 | name = "[CP] Check Pods Manifest.lock"; 244 | outputFileListPaths = ( 245 | ); 246 | outputPaths = ( 247 | "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", 248 | ); 249 | runOnlyForDeploymentPostprocessing = 0; 250 | shellPath = /bin/sh; 251 | shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; 252 | showEnvVarsInLog = 0; 253 | }; 254 | 9740EEB61CF901F6004384FC /* Run Script */ = { 255 | isa = PBXShellScriptBuildPhase; 256 | buildActionMask = 2147483647; 257 | files = ( 258 | ); 259 | inputPaths = ( 260 | ); 261 | name = "Run Script"; 262 | outputPaths = ( 263 | ); 264 | runOnlyForDeploymentPostprocessing = 0; 265 | shellPath = /bin/sh; 266 | shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; 267 | }; 268 | /* End PBXShellScriptBuildPhase section */ 269 | 270 | /* Begin PBXSourcesBuildPhase section */ 271 | 97C146EA1CF9000F007C117D /* Sources */ = { 272 | isa = PBXSourcesBuildPhase; 273 | buildActionMask = 2147483647; 274 | files = ( 275 | 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, 276 | 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, 277 | ); 278 | runOnlyForDeploymentPostprocessing = 0; 279 | }; 280 | /* End PBXSourcesBuildPhase section */ 281 | 282 | /* Begin PBXVariantGroup section */ 283 | 97C146FA1CF9000F007C117D /* Main.storyboard */ = { 284 | isa = PBXVariantGroup; 285 | children = ( 286 | 97C146FB1CF9000F007C117D /* Base */, 287 | ); 288 | name = Main.storyboard; 289 | sourceTree = ""; 290 | }; 291 | 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { 292 | isa = PBXVariantGroup; 293 | children = ( 294 | 97C147001CF9000F007C117D /* Base */, 295 | ); 296 | name = LaunchScreen.storyboard; 297 | sourceTree = ""; 298 | }; 299 | /* End PBXVariantGroup section */ 300 | 301 | /* Begin XCBuildConfiguration section */ 302 | 249021D3217E4FDB00AE95B9 /* Profile */ = { 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-with-dsym"; 333 | ENABLE_NS_ASSERTIONS = NO; 334 | ENABLE_STRICT_OBJC_MSGSEND = YES; 335 | GCC_C_LANGUAGE_STANDARD = gnu99; 336 | GCC_NO_COMMON_BLOCKS = YES; 337 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 338 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 339 | GCC_WARN_UNDECLARED_SELECTOR = YES; 340 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 341 | GCC_WARN_UNUSED_FUNCTION = YES; 342 | GCC_WARN_UNUSED_VARIABLE = YES; 343 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 344 | MTL_ENABLE_DEBUG_INFO = NO; 345 | SDKROOT = iphoneos; 346 | SUPPORTED_PLATFORMS = iphoneos; 347 | TARGETED_DEVICE_FAMILY = "1,2"; 348 | VALIDATE_PRODUCT = YES; 349 | }; 350 | name = Profile; 351 | }; 352 | 249021D4217E4FDB00AE95B9 /* Profile */ = { 353 | isa = XCBuildConfiguration; 354 | baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; 355 | buildSettings = { 356 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 357 | CLANG_ENABLE_MODULES = YES; 358 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; 359 | ENABLE_BITCODE = NO; 360 | INFOPLIST_FILE = Runner/Info.plist; 361 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 362 | PRODUCT_BUNDLE_IDENTIFIER = com.andree.chart; 363 | PRODUCT_NAME = "$(TARGET_NAME)"; 364 | SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; 365 | SWIFT_VERSION = 5.0; 366 | VERSIONING_SYSTEM = "apple-generic"; 367 | }; 368 | name = Profile; 369 | }; 370 | 97C147031CF9000F007C117D /* Debug */ = { 371 | isa = XCBuildConfiguration; 372 | buildSettings = { 373 | ALWAYS_SEARCH_USER_PATHS = NO; 374 | CLANG_ANALYZER_NONNULL = YES; 375 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 376 | CLANG_CXX_LIBRARY = "libc++"; 377 | CLANG_ENABLE_MODULES = YES; 378 | CLANG_ENABLE_OBJC_ARC = YES; 379 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 380 | CLANG_WARN_BOOL_CONVERSION = YES; 381 | CLANG_WARN_COMMA = YES; 382 | CLANG_WARN_CONSTANT_CONVERSION = YES; 383 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 384 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 385 | CLANG_WARN_EMPTY_BODY = YES; 386 | CLANG_WARN_ENUM_CONVERSION = YES; 387 | CLANG_WARN_INFINITE_RECURSION = YES; 388 | CLANG_WARN_INT_CONVERSION = YES; 389 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 390 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 391 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 392 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 393 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 394 | CLANG_WARN_STRICT_PROTOTYPES = YES; 395 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 396 | CLANG_WARN_UNREACHABLE_CODE = YES; 397 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 398 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 399 | COPY_PHASE_STRIP = NO; 400 | DEBUG_INFORMATION_FORMAT = dwarf; 401 | ENABLE_STRICT_OBJC_MSGSEND = YES; 402 | ENABLE_TESTABILITY = YES; 403 | GCC_C_LANGUAGE_STANDARD = gnu99; 404 | GCC_DYNAMIC_NO_PIC = NO; 405 | GCC_NO_COMMON_BLOCKS = YES; 406 | GCC_OPTIMIZATION_LEVEL = 0; 407 | GCC_PREPROCESSOR_DEFINITIONS = ( 408 | "DEBUG=1", 409 | "$(inherited)", 410 | ); 411 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 412 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 413 | GCC_WARN_UNDECLARED_SELECTOR = YES; 414 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 415 | GCC_WARN_UNUSED_FUNCTION = YES; 416 | GCC_WARN_UNUSED_VARIABLE = YES; 417 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 418 | MTL_ENABLE_DEBUG_INFO = YES; 419 | ONLY_ACTIVE_ARCH = YES; 420 | SDKROOT = iphoneos; 421 | TARGETED_DEVICE_FAMILY = "1,2"; 422 | }; 423 | name = Debug; 424 | }; 425 | 97C147041CF9000F007C117D /* Release */ = { 426 | isa = XCBuildConfiguration; 427 | buildSettings = { 428 | ALWAYS_SEARCH_USER_PATHS = NO; 429 | CLANG_ANALYZER_NONNULL = YES; 430 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 431 | CLANG_CXX_LIBRARY = "libc++"; 432 | CLANG_ENABLE_MODULES = YES; 433 | CLANG_ENABLE_OBJC_ARC = YES; 434 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 435 | CLANG_WARN_BOOL_CONVERSION = YES; 436 | CLANG_WARN_COMMA = YES; 437 | CLANG_WARN_CONSTANT_CONVERSION = YES; 438 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 439 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 440 | CLANG_WARN_EMPTY_BODY = YES; 441 | CLANG_WARN_ENUM_CONVERSION = YES; 442 | CLANG_WARN_INFINITE_RECURSION = YES; 443 | CLANG_WARN_INT_CONVERSION = YES; 444 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 445 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 446 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 447 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 448 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 449 | CLANG_WARN_STRICT_PROTOTYPES = YES; 450 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 451 | CLANG_WARN_UNREACHABLE_CODE = YES; 452 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 453 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 454 | COPY_PHASE_STRIP = NO; 455 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 456 | ENABLE_NS_ASSERTIONS = NO; 457 | ENABLE_STRICT_OBJC_MSGSEND = YES; 458 | GCC_C_LANGUAGE_STANDARD = gnu99; 459 | GCC_NO_COMMON_BLOCKS = YES; 460 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 461 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 462 | GCC_WARN_UNDECLARED_SELECTOR = YES; 463 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 464 | GCC_WARN_UNUSED_FUNCTION = YES; 465 | GCC_WARN_UNUSED_VARIABLE = YES; 466 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 467 | MTL_ENABLE_DEBUG_INFO = NO; 468 | SDKROOT = iphoneos; 469 | SUPPORTED_PLATFORMS = iphoneos; 470 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 471 | TARGETED_DEVICE_FAMILY = "1,2"; 472 | VALIDATE_PRODUCT = YES; 473 | }; 474 | name = Release; 475 | }; 476 | 97C147061CF9000F007C117D /* Debug */ = { 477 | isa = XCBuildConfiguration; 478 | baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; 479 | buildSettings = { 480 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 481 | CLANG_ENABLE_MODULES = YES; 482 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; 483 | ENABLE_BITCODE = NO; 484 | INFOPLIST_FILE = Runner/Info.plist; 485 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 486 | PRODUCT_BUNDLE_IDENTIFIER = com.andree.chart; 487 | PRODUCT_NAME = "$(TARGET_NAME)"; 488 | SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; 489 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 490 | SWIFT_VERSION = 5.0; 491 | VERSIONING_SYSTEM = "apple-generic"; 492 | }; 493 | name = Debug; 494 | }; 495 | 97C147071CF9000F007C117D /* Release */ = { 496 | isa = XCBuildConfiguration; 497 | baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; 498 | buildSettings = { 499 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 500 | CLANG_ENABLE_MODULES = YES; 501 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; 502 | ENABLE_BITCODE = NO; 503 | INFOPLIST_FILE = Runner/Info.plist; 504 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 505 | PRODUCT_BUNDLE_IDENTIFIER = com.andree.chart; 506 | PRODUCT_NAME = "$(TARGET_NAME)"; 507 | SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; 508 | SWIFT_VERSION = 5.0; 509 | VERSIONING_SYSTEM = "apple-generic"; 510 | }; 511 | name = Release; 512 | }; 513 | /* End XCBuildConfiguration section */ 514 | 515 | /* Begin XCConfigurationList section */ 516 | 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { 517 | isa = XCConfigurationList; 518 | buildConfigurations = ( 519 | 97C147031CF9000F007C117D /* Debug */, 520 | 97C147041CF9000F007C117D /* Release */, 521 | 249021D3217E4FDB00AE95B9 /* Profile */, 522 | ); 523 | defaultConfigurationIsVisible = 0; 524 | defaultConfigurationName = Release; 525 | }; 526 | 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { 527 | isa = XCConfigurationList; 528 | buildConfigurations = ( 529 | 97C147061CF9000F007C117D /* Debug */, 530 | 97C147071CF9000F007C117D /* Release */, 531 | 249021D4217E4FDB00AE95B9 /* Profile */, 532 | ); 533 | defaultConfigurationIsVisible = 0; 534 | defaultConfigurationName = Release; 535 | }; 536 | /* End XCConfigurationList section */ 537 | }; 538 | rootObject = 97C146E61CF9000F007C117D /* Project object */; 539 | } 540 | -------------------------------------------------------------------------------- /flutter_app/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /flutter_app/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /flutter_app/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /flutter_app/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 | -------------------------------------------------------------------------------- /flutter_app/ios/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /flutter_app/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /flutter_app/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /flutter_app/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 | -------------------------------------------------------------------------------- /flutter_app/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 | -------------------------------------------------------------------------------- /flutter_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SuperPenguin/charting-library-flutter/d780e5b3682e3d4bb3e5f8ee832f1f5f7e63832f/flutter_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png -------------------------------------------------------------------------------- /flutter_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SuperPenguin/charting-library-flutter/d780e5b3682e3d4bb3e5f8ee832f1f5f7e63832f/flutter_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png -------------------------------------------------------------------------------- /flutter_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SuperPenguin/charting-library-flutter/d780e5b3682e3d4bb3e5f8ee832f1f5f7e63832f/flutter_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png -------------------------------------------------------------------------------- /flutter_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SuperPenguin/charting-library-flutter/d780e5b3682e3d4bb3e5f8ee832f1f5f7e63832f/flutter_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png -------------------------------------------------------------------------------- /flutter_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SuperPenguin/charting-library-flutter/d780e5b3682e3d4bb3e5f8ee832f1f5f7e63832f/flutter_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png -------------------------------------------------------------------------------- /flutter_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SuperPenguin/charting-library-flutter/d780e5b3682e3d4bb3e5f8ee832f1f5f7e63832f/flutter_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png -------------------------------------------------------------------------------- /flutter_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SuperPenguin/charting-library-flutter/d780e5b3682e3d4bb3e5f8ee832f1f5f7e63832f/flutter_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png -------------------------------------------------------------------------------- /flutter_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SuperPenguin/charting-library-flutter/d780e5b3682e3d4bb3e5f8ee832f1f5f7e63832f/flutter_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png -------------------------------------------------------------------------------- /flutter_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SuperPenguin/charting-library-flutter/d780e5b3682e3d4bb3e5f8ee832f1f5f7e63832f/flutter_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png -------------------------------------------------------------------------------- /flutter_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SuperPenguin/charting-library-flutter/d780e5b3682e3d4bb3e5f8ee832f1f5f7e63832f/flutter_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png -------------------------------------------------------------------------------- /flutter_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SuperPenguin/charting-library-flutter/d780e5b3682e3d4bb3e5f8ee832f1f5f7e63832f/flutter_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png -------------------------------------------------------------------------------- /flutter_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SuperPenguin/charting-library-flutter/d780e5b3682e3d4bb3e5f8ee832f1f5f7e63832f/flutter_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png -------------------------------------------------------------------------------- /flutter_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SuperPenguin/charting-library-flutter/d780e5b3682e3d4bb3e5f8ee832f1f5f7e63832f/flutter_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png -------------------------------------------------------------------------------- /flutter_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SuperPenguin/charting-library-flutter/d780e5b3682e3d4bb3e5f8ee832f1f5f7e63832f/flutter_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png -------------------------------------------------------------------------------- /flutter_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SuperPenguin/charting-library-flutter/d780e5b3682e3d4bb3e5f8ee832f1f5f7e63832f/flutter_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png -------------------------------------------------------------------------------- /flutter_app/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 | -------------------------------------------------------------------------------- /flutter_app/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SuperPenguin/charting-library-flutter/d780e5b3682e3d4bb3e5f8ee832f1f5f7e63832f/flutter_app/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png -------------------------------------------------------------------------------- /flutter_app/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SuperPenguin/charting-library-flutter/d780e5b3682e3d4bb3e5f8ee832f1f5f7e63832f/flutter_app/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png -------------------------------------------------------------------------------- /flutter_app/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SuperPenguin/charting-library-flutter/d780e5b3682e3d4bb3e5f8ee832f1f5f7e63832f/flutter_app/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png -------------------------------------------------------------------------------- /flutter_app/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. -------------------------------------------------------------------------------- /flutter_app/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 | -------------------------------------------------------------------------------- /flutter_app/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 | -------------------------------------------------------------------------------- /flutter_app/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 | chart 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 | NSAppTransportSecurity 45 | 46 | NSAllowsLocalNetworking 47 | 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /flutter_app/ios/Runner/Runner-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | #import "GeneratedPluginRegistrant.h" 2 | -------------------------------------------------------------------------------- /flutter_app/lib/commons/localhost.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:flutter_inappwebview/flutter_inappwebview.dart'; 4 | 5 | LocalhostManager get localhostManager { 6 | var instance = LocalhostManager._instance; 7 | if (instance == null) { 8 | instance = LocalhostManager._internal(); 9 | LocalhostManager._instance = instance; 10 | } 11 | 12 | return instance; 13 | } 14 | 15 | class LocalhostManager { 16 | static const int port = 8080; 17 | static LocalhostManager? _instance; 18 | LocalhostManager._internal(); 19 | 20 | InAppLocalhostServer? _localhostServer; 21 | 22 | bool get isRunning => _localhostServer?.isRunning() ?? false; 23 | 24 | final _uri = Uri( 25 | scheme: 'http', 26 | host: 'localhost', 27 | port: port, 28 | ); 29 | 30 | Uri get uri => _uri.replace(); 31 | Uri getUriWith({String? path}) => _uri.replace(path: path); 32 | 33 | Future startServer() async { 34 | if (!isRunning) { 35 | final server = InAppLocalhostServer(port: port); 36 | _localhostServer = server; 37 | await server.start(); 38 | } 39 | } 40 | 41 | Future stopServer() async { 42 | if (isRunning) { 43 | final server = _localhostServer!; 44 | await server.close(); 45 | _localhostServer = null; 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /flutter_app/lib/commons/random.dart: -------------------------------------------------------------------------------- 1 | import 'dart:math'; 2 | 3 | Random source = Random(); 4 | 5 | /// Generate random double in range of min (inclusive) to max (exclusive) 6 | double doubleRandomRange(double min, double max) { 7 | return source.nextDouble() * (max - min) + min; 8 | } 9 | 10 | /// Generate random int in range of min (inclusive) to max (exclusive) 11 | int intRandomRange(int min, int max) { 12 | return source.nextInt(max - min) + min; 13 | } 14 | -------------------------------------------------------------------------------- /flutter_app/lib/commons/tzdt.dart: -------------------------------------------------------------------------------- 1 | import 'package:intl/intl.dart'; 2 | import 'package:timezone/timezone.dart'; 3 | 4 | late Location tzLocation; 5 | 6 | void setupTZLocation() { 7 | tzLocation = getLocation('Asia/Jakarta'); 8 | setLocalLocation(tzLocation); 9 | } 10 | 11 | TZDateTime tzNow() { 12 | return TZDateTime.now(tzLocation); 13 | } 14 | 15 | TZDateTime tzSecond(int second) { 16 | return tzMillisecond(second * 1000); 17 | } 18 | 19 | TZDateTime tzMillisecond(int millisecond) { 20 | return TZDateTime.fromMillisecondsSinceEpoch(tzLocation, millisecond); 21 | } 22 | 23 | TZDateTime tzToday([ 24 | int hour = 0, 25 | int minute = 0, 26 | int second = 0, 27 | int millisecond = 0, 28 | int microsecond = 0, 29 | ]) { 30 | var now = tzNow(); 31 | 32 | return TZDateTime( 33 | tzLocation, 34 | now.year, 35 | now.month, 36 | now.day, 37 | hour, 38 | minute, 39 | second, 40 | millisecond, 41 | microsecond, 42 | ); 43 | } 44 | 45 | TZDateTime tzDate( 46 | int year, [ 47 | int month = 1, 48 | int day = 1, 49 | int hour = 0, 50 | int minute = 0, 51 | int second = 0, 52 | int millisecond = 0, 53 | int microsecond = 0, 54 | ]) { 55 | return TZDateTime( 56 | tzLocation, 57 | year, 58 | month, 59 | day, 60 | hour, 61 | minute, 62 | second, 63 | millisecond, 64 | microsecond, 65 | ); 66 | } 67 | 68 | // Reuse formatter 69 | Map> _fmts = {}; 70 | 71 | String tzFormat( 72 | TZDateTime tzdt, 73 | String format, { 74 | String locale = 'en_US', 75 | }) { 76 | final locFmts = _fmts.putIfAbsent(locale, () => {}); 77 | final fmt = locFmts.putIfAbsent(format, () => DateFormat(format, locale)); 78 | return fmt.format(tzdt); 79 | } 80 | -------------------------------------------------------------------------------- /flutter_app/lib/components/tvchart/tvchart.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | 3 | import 'package:chart/commons/localhost.dart'; 4 | import 'package:chart/commons/tzdt.dart'; 5 | import 'package:chart/components/tvchart/tvchart_types.dart'; 6 | import 'package:chart/data/historical.dart'; 7 | import 'package:flutter/foundation.dart'; 8 | import 'package:flutter/gestures.dart'; 9 | import 'package:flutter/material.dart'; 10 | import 'package:flutter_inappwebview/flutter_inappwebview.dart'; 11 | 12 | const _assetsPath = '/assets/tvchart/tvchart.html'; 13 | 14 | class TVChart extends StatefulWidget { 15 | const TVChart({Key? key}) : super(key: key); 16 | 17 | @override 18 | _TVChartState createState() => _TVChartState(); 19 | } 20 | 21 | class _TVChartState extends State with WidgetsBindingObserver { 22 | InAppWebViewController? _controller; 23 | bool _isServerRunning = false; 24 | bool _isLoading = false; 25 | bool _isError = false; 26 | String _isErrorMessage = ''; 27 | bool _showBack = false; 28 | final Map _onTickMap = {}; 29 | 30 | bool get _chartLoaded { 31 | return _controller != null && !_isLoading && !_isError; 32 | } 33 | 34 | // ignore: unused_element 35 | void _callOnTick(String listenerGuid, Bar bar) { 36 | if (_chartLoaded) { 37 | final controller = _controller!; 38 | final Map payload = { 39 | 'listenerGuid': listenerGuid, 40 | 'bar': bar, 41 | }; 42 | 43 | controller.evaluateJavascript( 44 | source: 'window.callOnTick(`${jsonEncode(payload)}`);', 45 | ); 46 | } 47 | } 48 | 49 | void _attachHandler() { 50 | final controller = _controller; 51 | if (controller == null) return; 52 | 53 | controller.addJavaScriptHandler( 54 | handlerName: 'start', 55 | callback: (arguments) { 56 | return ChartingLibraryWidgetOptions( 57 | debug: false, 58 | locale: 'en', 59 | symbol: 'IDX:COMPOSITE', 60 | fullscreen: false, 61 | interval: '1D', 62 | timezone: Timezone.asiaJakarta, 63 | autosize: true, 64 | autoSaveDelay: 1, 65 | theme: Theme.of(context).brightness == Brightness.light 66 | ? ChartTheme.light 67 | : ChartTheme.dark, 68 | savedData: _savedData, 69 | disabledFeatures: const [ 70 | 'header_fullscreen_button', 71 | 'header_screenshot', 72 | 'use_localstorage_for_settings', 73 | 'timezone_menu', 74 | 'go_to_date', 75 | 'timeframes_toolbar', 76 | ], 77 | enabledFeatures: const ['hide_left_toolbar_by_default'], 78 | ); 79 | }, 80 | ); 81 | 82 | controller.addJavaScriptHandler( 83 | handlerName: 'onReady', 84 | callback: (arguments) { 85 | return const DatafeedConfiguration( 86 | exchanges: [ 87 | // Exchange( 88 | // name: 'IDX', 89 | // value: 'IDX', 90 | // desc: 'Indonesia Stock Exchange', 91 | // ), 92 | ], 93 | supportedResolutions: ['1D'], 94 | currencyCodes: ['IDR'], 95 | supportsMarks: false, 96 | supportsTime: true, 97 | supportsTimescaleMarks: true, 98 | symbolsTypes: [ 99 | DatafeedSymbolType( 100 | name: 'Index', 101 | value: 'index', 102 | ), 103 | DatafeedSymbolType( 104 | name: 'Stock', 105 | value: 'stock', 106 | ), 107 | ], 108 | ); 109 | }, 110 | ); 111 | 112 | controller.addJavaScriptHandler( 113 | handlerName: 'searchSymbols', 114 | callback: (arguments) { 115 | final String userInput = arguments[0]; 116 | // Only 1 exchange on example, not needed 117 | // final String exchange = arguments[1]; 118 | final String symbolType = arguments[2]; 119 | 120 | final List result = 121 | historical.searchSymbol(userInput, symbolType); 122 | return result; 123 | }, 124 | ); 125 | 126 | controller.addJavaScriptHandler( 127 | handlerName: 'resolveSymbol', 128 | callback: (arguments) { 129 | final String symbolName = arguments[0]; 130 | final SymbolData? result = historical.getSymbol(symbolName); 131 | 132 | if (result != null) { 133 | return result.getLibrarySymbolInfo(); 134 | } else { 135 | return 'Symbol not found!'; 136 | } 137 | }, 138 | ); 139 | 140 | controller.addJavaScriptHandler( 141 | handlerName: 'getBars', 142 | callback: (arguments) async { 143 | final LibrarySymbolInfo symbolInfo = 144 | LibrarySymbolInfo.fromJson(arguments[0]); 145 | // Only 1 resolution on example, not needed 146 | // final String resolution = arguments[1]; 147 | final PeriodParams periodParams = PeriodParams.fromJson(arguments[2]); 148 | 149 | var symbol = historical.getSymbol(symbolInfo.name); 150 | if (symbol == null) { 151 | return 'Symbol not found'; 152 | } else { 153 | var result = await symbol.getDataRange( 154 | tzMillisecond(periodParams.from * 1000), 155 | tzMillisecond(periodParams.to * 1000), 156 | ); 157 | 158 | return { 159 | 'bars': result 160 | .map( 161 | (e) => Bar( 162 | time: e.dt.millisecondsSinceEpoch, 163 | open: e.open, 164 | high: e.high, 165 | low: e.low, 166 | close: e.close, 167 | volume: e.volume, 168 | ), 169 | ) 170 | .toList(), 171 | 'meta': { 172 | 'noData': result.isEmpty, 173 | }, 174 | }; 175 | } 176 | }, 177 | ); 178 | 179 | controller.addJavaScriptHandler( 180 | handlerName: 'subscribeBars', 181 | callback: (arguments) { 182 | final LibrarySymbolInfo symbolInfo = 183 | LibrarySymbolInfo.fromJson(arguments[0]); 184 | final String resolution = arguments[1]; 185 | final String listenerGuid = arguments[2]; 186 | 187 | if (_onTickMap.containsKey(listenerGuid)) { 188 | // Dispose existing onTick with same ID 189 | } 190 | 191 | _onTickMap[listenerGuid] = _OnTickInfo( 192 | symbolInfo: symbolInfo, 193 | resolution: resolution, 194 | ); 195 | 196 | // Do request for realtime data 197 | // Use _callOnTick for returning realtime bar data 198 | }, 199 | ); 200 | 201 | controller.addJavaScriptHandler( 202 | handlerName: 'unsubscribeBars', 203 | callback: (arguments) { 204 | final String listenerGuid = arguments[0]; 205 | 206 | if (_onTickMap.containsKey(listenerGuid)) { 207 | // Dispose existing onTick 208 | } 209 | _onTickMap.remove(listenerGuid); 210 | }, 211 | ); 212 | 213 | controller.addJavaScriptHandler( 214 | handlerName: 'saveData', 215 | callback: (arguments) { 216 | if (arguments[0] is Map) { 217 | _savedData = arguments[0]; 218 | } 219 | }, 220 | ); 221 | } 222 | 223 | @override 224 | void initState() { 225 | super.initState(); 226 | 227 | _isLoading = true; 228 | _isError = false; 229 | _isServerRunning = localhostManager.isRunning; 230 | if (!_isServerRunning) { 231 | localhostManager.startServer().then((_) { 232 | WidgetsBinding.instance!.addPostFrameCallback((timeStamp) { 233 | if (mounted) { 234 | setState(() { 235 | _isServerRunning = true; 236 | }); 237 | } 238 | }); 239 | }); 240 | } 241 | 242 | WidgetsBinding.instance!.addObserver(this); 243 | } 244 | 245 | @override 246 | void dispose() { 247 | _controller = null; 248 | _onTickMap.forEach((key, value) { 249 | // Dispose all onTick request/stream 250 | }); 251 | 252 | WidgetsBinding.instance!.removeObserver(this); 253 | super.dispose(); 254 | } 255 | 256 | @override 257 | void didChangePlatformBrightness() { 258 | if (_chartLoaded) { 259 | _controller!.evaluateJavascript( 260 | source: WidgetsBinding.instance!.window.platformBrightness == 261 | Brightness.dark 262 | ? 'window.toggleDarkTheme();' 263 | : 'window.toggleLightTheme();', 264 | ); 265 | } 266 | 267 | super.didChangePlatformBrightness(); 268 | } 269 | 270 | @override 271 | Widget build(BuildContext context) { 272 | List stackContent = []; 273 | 274 | if (_isServerRunning) { 275 | stackContent.add( 276 | Column( 277 | children: [ 278 | _showBack 279 | ? Row( 280 | crossAxisAlignment: CrossAxisAlignment.center, 281 | children: [ 282 | IconButton( 283 | icon: Icon(Icons.adaptive.arrow_back), 284 | onPressed: () async { 285 | var controller = _controller; 286 | if (controller != null) { 287 | // Somehow the chart is gone when you press back quick enough ? 288 | // See Webview Console output 289 | if (await controller.canGoBack()) { 290 | await controller.goBack(); 291 | } 292 | 293 | setState(() { 294 | _showBack = false; 295 | }); 296 | } 297 | }, 298 | ), 299 | Text( 300 | 'Return to Chart', 301 | style: Theme.of(context).textTheme.headline6, 302 | ), 303 | ], 304 | ) 305 | : const SizedBox(), 306 | Expanded( 307 | child: InAppWebView( 308 | initialUrlRequest: URLRequest( 309 | url: localhostManager.getUriWith( 310 | path: _assetsPath, 311 | ), 312 | ), 313 | initialOptions: InAppWebViewGroupOptions( 314 | crossPlatform: InAppWebViewOptions( 315 | clearCache: true, 316 | supportZoom: false, 317 | transparentBackground: true, 318 | ), 319 | ), 320 | // Pass all gesture to Webview 321 | gestureRecognizers: { 322 | Factory(() => EagerGestureRecognizer()), 323 | }, 324 | onWebViewCreated: (controller) { 325 | _controller = controller; 326 | _attachHandler(); 327 | }, 328 | onLoadStart: (controller, url) { 329 | setState(() { 330 | _isLoading = true; 331 | _isError = false; 332 | _isErrorMessage = ''; 333 | }); 334 | }, 335 | onLoadStop: (controller, url) { 336 | setState(() { 337 | _isLoading = false; 338 | _isError = false; 339 | // In case host not localhost (ex: got redirected to www.tradingview.com) 340 | // Give user UI to call back on Webview 341 | _showBack = 342 | url != null && url.host != localhostManager.uri.host; 343 | }); 344 | }, 345 | onLoadError: (controller, url, code, message) { 346 | setState(() { 347 | _isLoading = false; 348 | _isError = false; 349 | _isErrorMessage = '$code - $message'; 350 | _showBack = 351 | url != null && url.host != localhostManager.uri.host; 352 | }); 353 | }, 354 | onLoadHttpError: (controller, url, statusCode, description) { 355 | setState(() { 356 | _isLoading = false; 357 | _isError = false; 358 | _isErrorMessage = 'HTTP $statusCode - $description'; 359 | _showBack = 360 | url != null && url.host != localhostManager.uri.host; 361 | }); 362 | }, 363 | onConsoleMessage: (controller, consoleMessage) { 364 | if (kDebugMode) { 365 | final level = consoleMessage.messageLevel.toString(); 366 | final message = consoleMessage.message; 367 | // ignore: avoid_print 368 | print('Webview Console $level: $message'); 369 | } 370 | }, 371 | ), 372 | ), 373 | ], 374 | ), 375 | ); 376 | } 377 | 378 | if (_isLoading) { 379 | stackContent.add(const CircularProgressIndicator.adaptive()); 380 | } 381 | 382 | if (_isError) { 383 | stackContent.add( 384 | Column( 385 | mainAxisSize: MainAxisSize.min, 386 | children: [ 387 | const Icon(Icons.error), 388 | Text(_isErrorMessage), 389 | ], 390 | ), 391 | ); 392 | } 393 | 394 | // ignore: avoid_unnecessary_containers 395 | return Container( 396 | child: Stack( 397 | alignment: Alignment.center, 398 | children: stackContent, 399 | ), 400 | ); 401 | } 402 | } 403 | 404 | Map? _savedData; 405 | 406 | @immutable 407 | class _OnTickInfo { 408 | final LibrarySymbolInfo symbolInfo; 409 | final String resolution; 410 | 411 | const _OnTickInfo({ 412 | required this.symbolInfo, 413 | required this.resolution, 414 | }); 415 | } 416 | -------------------------------------------------------------------------------- /flutter_app/lib/components/tvchart/tvchart_types.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/foundation.dart'; 2 | import 'package:json_annotation/json_annotation.dart'; 3 | 4 | part 'tvchart_types.g.dart'; 5 | 6 | enum ChartTheme { 7 | @JsonValue('Light') 8 | light, 9 | 10 | @JsonValue('Dark') 11 | dark, 12 | } 13 | 14 | enum AccessListType { 15 | @JsonValue('black') 16 | black, 17 | 18 | @JsonValue('white') 19 | white, 20 | } 21 | 22 | enum Timezone { 23 | @JsonValue('Etc/UTC') 24 | utc, 25 | 26 | @JsonValue('Africa/Cairo') 27 | africaCairo, 28 | 29 | @JsonValue('Africa/Johannesburg') 30 | africaJohannesburg, 31 | 32 | @JsonValue('Africa/Lagos') 33 | africaLagos, 34 | 35 | @JsonValue('America/Argentina/Buenos_Aires') 36 | americaArgentinaBuenosAires, 37 | 38 | @JsonValue('America/Bogota') 39 | americaBogota, 40 | 41 | @JsonValue('America/Caracas') 42 | americaCaracas, 43 | 44 | @JsonValue('America/Chicago') 45 | americaChicago, 46 | 47 | @JsonValue('America/El_Salvador') 48 | americaElSalvador, 49 | 50 | @JsonValue('America/Juneau') 51 | americaJuneau, 52 | 53 | @JsonValue('America/Lima') 54 | americaLima, 55 | 56 | @JsonValue('America/Los_Angeles') 57 | americaLosAngeles, 58 | 59 | @JsonValue('America/Mexico_City') 60 | americaMexicoCity, 61 | 62 | @JsonValue('America/New_York') 63 | americaNewYork, 64 | 65 | @JsonValue('America/Phoenix') 66 | americaPhoenix, 67 | 68 | @JsonValue('America/Santiago') 69 | americaSantiago, 70 | 71 | @JsonValue('America/Sao_Paulo') 72 | americaSaoPaulo, 73 | 74 | @JsonValue('America/Toronto') 75 | americaToronto, 76 | 77 | @JsonValue('America/Vancouver') 78 | americaVancouver, 79 | 80 | @JsonValue('Asia/Almaty') 81 | asiaAlmaty, 82 | 83 | @JsonValue('Asia/Ashkhabad') 84 | asiaAshkhabad, 85 | 86 | @JsonValue('Asia/Bahrain') 87 | asiaBahrain, 88 | 89 | @JsonValue('Asia/Bangkok') 90 | asiaBangkok, 91 | 92 | @JsonValue('Asia/Chongqing') 93 | asiaChongqing, 94 | 95 | @JsonValue('Asia/Dubai') 96 | asiaDubai, 97 | 98 | @JsonValue('Asia/Ho_Chi_Minh') 99 | asiaHoChiMinh, 100 | 101 | @JsonValue('Asia/Hong_Kong') 102 | asiaHongKong, 103 | 104 | @JsonValue('Asia/Jakarta') 105 | asiaJakarta, 106 | 107 | @JsonValue('Asia/Jerusalem') 108 | asiaJerusalem, 109 | 110 | @JsonValue('Asia/Kathmandu') 111 | asiaKathmandu, 112 | 113 | @JsonValue('Asia/Kolkata') 114 | asiaKolkata, 115 | 116 | @JsonValue('Asia/Kuwait') 117 | asiaKuwait, 118 | 119 | @JsonValue('Asia/Muscat') 120 | asiaMuscat, 121 | 122 | @JsonValue('Asia/Qatar') 123 | asiaQatar, 124 | 125 | @JsonValue('Asia/Riyadh') 126 | asiaRiyadh, 127 | 128 | @JsonValue('Asia/Seoul') 129 | asiaSeoul, 130 | 131 | @JsonValue('Asia/Shanghai') 132 | asiaShanghai, 133 | 134 | @JsonValue('Asia/Singapore') 135 | asiaSingapore, 136 | 137 | @JsonValue('Asia/Taipei') 138 | asiaTaipei, 139 | 140 | @JsonValue('Asia/Tehran') 141 | asiaTehran, 142 | 143 | @JsonValue('Asia/Tokyo') 144 | asiaTokyo, 145 | 146 | @JsonValue('Atlantic/Reykjavik') 147 | atlanticReykjavik, 148 | 149 | @JsonValue('Australia/ACT') 150 | australiaACT, 151 | 152 | @JsonValue('Australia/Adelaide') 153 | australiaAdelaide, 154 | 155 | @JsonValue('Australia/Brisbane') 156 | australiaBrisbane, 157 | 158 | @JsonValue('Australia/Perth') 159 | australiaPerth, 160 | 161 | @JsonValue('Australia/Sydney') 162 | australiaSydney, 163 | 164 | @JsonValue('Europe/Amsterdam') 165 | europeAmsterdam, 166 | 167 | @JsonValue('Europe/Athens') 168 | europeAthens, 169 | 170 | @JsonValue('Europe/Belgrade') 171 | europeBelgrade, 172 | 173 | @JsonValue('Europe/Berlin') 174 | europeBerlin, 175 | 176 | @JsonValue('Europe/Brussels') 177 | europeBrussels, 178 | 179 | @JsonValue('Europe/Copenhagen') 180 | europeCopenhagen, 181 | 182 | @JsonValue('Europe/Dublin') 183 | europeDublin, 184 | 185 | @JsonValue('Europe/Helsinki') 186 | europeHelsinki, 187 | 188 | @JsonValue('Europe/Istanbul') 189 | europeIstanbul, 190 | 191 | @JsonValue('Europe/Lisbon') 192 | europeLisbon, 193 | 194 | @JsonValue('Europe/London') 195 | europeLondon, 196 | 197 | @JsonValue('Europe/Luxembourg') 198 | europeLuxembourg, 199 | 200 | @JsonValue('Europe/Madrid') 201 | europeMadrid, 202 | 203 | @JsonValue('Europe/Malta') 204 | europeMalta, 205 | 206 | @JsonValue('Europe/Moscow') 207 | europeMoscow, 208 | 209 | @JsonValue('Europe/Oslo') 210 | europeOslo, 211 | 212 | @JsonValue('Europe/Paris') 213 | europeParis, 214 | 215 | @JsonValue('Europe/Riga') 216 | europeRiga, 217 | 218 | @JsonValue('Europe/Rome') 219 | europeRome, 220 | 221 | @JsonValue('Europe/Stockholm') 222 | europeStockholm, 223 | 224 | @JsonValue('Europe/Tallinn') 225 | europeTallinn, 226 | 227 | @JsonValue('Europe/Vilnius') 228 | europeVilnius, 229 | 230 | @JsonValue('Europe/Warsaw') 231 | europeWarsaw, 232 | 233 | @JsonValue('Europe/Zurich') 234 | europeZurich, 235 | 236 | @JsonValue('Pacific/Auckland') 237 | pacificAuckland, 238 | 239 | @JsonValue('Pacific/Chatham') 240 | pacificChatham, 241 | 242 | @JsonValue('Pacific/Fakaofo') 243 | pacificFakaofo, 244 | 245 | @JsonValue('Pacific/Honolulu') 246 | pacificHonolulu, 247 | 248 | @JsonValue('Pacific/Norfolk') 249 | pacificNorfolk, 250 | 251 | @JsonValue('US/Mountain') 252 | usMountain, 253 | 254 | /// Only use this when the parameters/function actually could accept timezone `exchange` 255 | @JsonValue('exchange') 256 | exchange, 257 | } 258 | 259 | enum SeriesFormat { 260 | @JsonValue('price') 261 | price, 262 | 263 | @JsonValue('volume') 264 | volume, 265 | } 266 | 267 | enum DataStatus { 268 | @JsonValue('streaming') 269 | streaming, 270 | 271 | @JsonValue('endofday') 272 | endOfDay, 273 | 274 | @JsonValue('pulsed') 275 | pulsed, 276 | 277 | @JsonValue('delayed_streaming') 278 | delayedStreaming, 279 | } 280 | 281 | @immutable 282 | @JsonSerializable(includeIfNull: false) 283 | class PeriodParams { 284 | final int from; 285 | 286 | final int to; 287 | 288 | final int countBack; 289 | 290 | final bool firstDataRequest; 291 | 292 | const PeriodParams({ 293 | required this.from, 294 | required this.to, 295 | required this.countBack, 296 | required this.firstDataRequest, 297 | }); 298 | 299 | factory PeriodParams.fromJson(Map json) => 300 | _$PeriodParamsFromJson(json); 301 | 302 | Map toJson() => _$PeriodParamsToJson(this); 303 | } 304 | 305 | @immutable 306 | @JsonSerializable(includeIfNull: false) 307 | class Bar { 308 | final int time; 309 | 310 | final double open; 311 | 312 | final double high; 313 | 314 | final double low; 315 | 316 | final double close; 317 | 318 | final int? volume; 319 | 320 | const Bar({ 321 | required this.time, 322 | required this.open, 323 | required this.high, 324 | required this.low, 325 | required this.close, 326 | this.volume, 327 | }); 328 | 329 | factory Bar.fromJson(Map json) => _$BarFromJson(json); 330 | 331 | Map toJson() => _$BarToJson(this); 332 | } 333 | 334 | @immutable 335 | @JsonSerializable(includeIfNull: false) 336 | class Exchange { 337 | final String value; 338 | 339 | final String name; 340 | 341 | final String desc; 342 | 343 | const Exchange({ 344 | required this.value, 345 | required this.name, 346 | required this.desc, 347 | }); 348 | 349 | factory Exchange.fromJson(Map json) => 350 | _$ExchangeFromJson(json); 351 | 352 | Map toJson() => _$ExchangeToJson(this); 353 | } 354 | 355 | @immutable 356 | @JsonSerializable(includeIfNull: false) 357 | class DatafeedConfiguration { 358 | final List? exchanges; 359 | 360 | @JsonKey(name: 'supported_resolutions') 361 | final List? supportedResolutions; 362 | 363 | @JsonKey(name: 'currency_codes') 364 | final List? currencyCodes; 365 | 366 | @JsonKey(name: 'supports_marks') 367 | final bool? supportsMarks; 368 | 369 | @JsonKey(name: 'supports_time') 370 | final bool? supportsTime; 371 | 372 | @JsonKey(name: 'supports_timescale_marks') 373 | final bool? supportsTimescaleMarks; 374 | 375 | @JsonKey(name: 'symbols_types') 376 | final List? symbolsTypes; 377 | 378 | const DatafeedConfiguration({ 379 | this.exchanges, 380 | this.supportedResolutions, 381 | this.currencyCodes, 382 | this.supportsMarks, 383 | this.supportsTime, 384 | this.supportsTimescaleMarks, 385 | this.symbolsTypes, 386 | }); 387 | 388 | factory DatafeedConfiguration.fromJson(Map json) => 389 | _$DatafeedConfigurationFromJson(json); 390 | 391 | Map toJson() => _$DatafeedConfigurationToJson(this); 392 | } 393 | 394 | @immutable 395 | @JsonSerializable(includeIfNull: false) 396 | class DatafeedSymbolType { 397 | final String name; 398 | 399 | final String value; 400 | 401 | const DatafeedSymbolType({ 402 | required this.name, 403 | required this.value, 404 | }); 405 | 406 | factory DatafeedSymbolType.fromJson(Map json) => 407 | _$DatafeedSymbolTypeFromJson(json); 408 | 409 | Map toJson() => _$DatafeedSymbolTypeToJson(this); 410 | } 411 | 412 | @immutable 413 | @JsonSerializable(includeIfNull: false) 414 | class SearchSymbolResultItem { 415 | final String symbol; 416 | 417 | @JsonKey(name: 'full_name') 418 | final String fullName; 419 | 420 | final String description; 421 | 422 | final String exchange; 423 | 424 | final String ticker; 425 | 426 | final String type; 427 | 428 | const SearchSymbolResultItem({ 429 | required this.symbol, 430 | required this.fullName, 431 | required this.description, 432 | required this.exchange, 433 | required this.ticker, 434 | required this.type, 435 | }); 436 | 437 | factory SearchSymbolResultItem.fromJson(Map json) => 438 | _$SearchSymbolResultItemFromJson(json); 439 | 440 | Map toJson() => _$SearchSymbolResultItemToJson(this); 441 | } 442 | 443 | @immutable 444 | @JsonSerializable(includeIfNull: false) 445 | class LibrarySymbolInfo { 446 | final String name; 447 | 448 | @JsonKey(name: 'full_name') 449 | final String fullName; 450 | 451 | @JsonKey(name: 'base_name') 452 | final List? baseName; 453 | 454 | final String? ticker; 455 | 456 | final String description; 457 | 458 | final String type; 459 | 460 | final String session; 461 | 462 | @JsonKey(name: 'session_display') 463 | final String? sessionDisplay; 464 | 465 | final String? holidays; 466 | 467 | final String? corrections; 468 | 469 | final String exchange; 470 | 471 | @JsonKey(name: 'listed_exchange') 472 | final String listedExchange; 473 | 474 | final Timezone timezone; 475 | 476 | final SeriesFormat format; 477 | 478 | final double pricescale; 479 | 480 | final double minmov; 481 | 482 | final bool? fractional; 483 | 484 | final double? minmove2; 485 | 486 | @JsonKey(name: 'has_intraday') 487 | final bool? hasIntraday; 488 | 489 | @JsonKey(name: 'supported_resolutions') 490 | final List supportedResolutions; 491 | 492 | @JsonKey(name: 'intraday_multipliers') 493 | final List? intradayMultipliers; 494 | 495 | @JsonKey(name: 'has_seconds') 496 | final bool? hasSeconds; 497 | 498 | @JsonKey(name: 'has_ticks') 499 | final bool? hasTicks; 500 | 501 | @JsonKey(name: 'seconds_multipliers') 502 | final List? secondsMultipliers; 503 | 504 | @JsonKey(name: 'has_daily') 505 | final bool? hasDaily; 506 | 507 | @JsonKey(name: 'has_weekly_and_monthly') 508 | final bool? hasWeeklyAndMonthly; 509 | 510 | @JsonKey(name: 'has_empty_bars') 511 | final bool? hasEmptyBars; 512 | 513 | @JsonKey(name: 'has_no_volume') 514 | final bool? hasNoVolume; 515 | 516 | @JsonKey(name: 'volume_precision') 517 | final double? volumePrecision; 518 | 519 | @JsonKey(name: 'data_status') 520 | final String? dataStatus; 521 | 522 | final bool? expired; 523 | 524 | @JsonKey(name: 'expiration_date') 525 | final int? expirationDate; 526 | 527 | final String? sector; 528 | 529 | final String? industry; 530 | 531 | @JsonKey(name: 'currency_code') 532 | final String? currencyCode; 533 | 534 | @JsonKey(name: 'original_currency_code') 535 | final String? originalCurrencyCode; 536 | 537 | const LibrarySymbolInfo({ 538 | required this.name, 539 | required this.fullName, 540 | this.baseName, 541 | this.ticker, 542 | required this.description, 543 | required this.type, 544 | required this.session, 545 | this.sessionDisplay, 546 | this.holidays, 547 | this.corrections, 548 | required this.exchange, 549 | required this.listedExchange, 550 | required this.timezone, 551 | required this.format, 552 | required this.pricescale, 553 | required this.minmov, 554 | this.fractional, 555 | this.minmove2, 556 | this.hasIntraday, 557 | required this.supportedResolutions, 558 | this.intradayMultipliers, 559 | this.hasSeconds, 560 | this.hasTicks, 561 | this.secondsMultipliers, 562 | this.hasDaily, 563 | this.hasWeeklyAndMonthly, 564 | this.hasEmptyBars, 565 | this.hasNoVolume, 566 | this.volumePrecision, 567 | this.dataStatus, 568 | this.expired, 569 | this.expirationDate, 570 | this.sector, 571 | this.industry, 572 | this.currencyCode, 573 | this.originalCurrencyCode, 574 | }); 575 | 576 | factory LibrarySymbolInfo.fromJson(Map json) => 577 | _$LibrarySymbolInfoFromJson(json); 578 | 579 | Map toJson() => _$LibrarySymbolInfoToJson(this); 580 | } 581 | 582 | /// Some property is not here and you should add it on JS side if needed: 583 | /// ```md 584 | /// required on JS side 585 | /// - container 586 | /// - datafeed 587 | /// ``` 588 | @immutable 589 | @JsonSerializable(includeIfNull: false) 590 | class ChartingLibraryWidgetOptions { 591 | final String interval; 592 | 593 | final String? symbol; 594 | 595 | @JsonKey(name: 'auto_save_delay') 596 | final int? autoSaveDelay; 597 | 598 | final bool? autosize; 599 | 600 | final bool? debug; 601 | 602 | @JsonKey(name: 'disabled_features') 603 | final List? disabledFeatures; 604 | 605 | @JsonKey(name: 'drawings_access') 606 | final AccessList? drawingsAccess; 607 | 608 | @JsonKey(name: 'enabled_features') 609 | final List? enabledFeatures; 610 | 611 | final bool? fullscreen; 612 | 613 | final int? height; 614 | 615 | final String locale; 616 | 617 | @JsonKey(name: 'numeric_formatting') 618 | final NumericFormattingParams? numericFormatting; 619 | 620 | @JsonKey(name: 'saved_data') 621 | final Map? savedData; 622 | 623 | @JsonKey(name: 'saved_data_meta_info') 624 | final SavedStateMetaInfo? savedDataMetaInfo; 625 | 626 | @JsonKey(name: 'studies_access') 627 | final AccessList? studiesAccess; 628 | 629 | @JsonKey(name: 'study_count_limit') 630 | final int? studyCountLimit; 631 | 632 | @JsonKey(name: 'symbol_search_request_delay') 633 | final int? symbolSearchRequestDelay; 634 | 635 | final String? timeframe; 636 | 637 | final Timezone? timezone; 638 | 639 | @JsonKey(name: 'toolbar_bg') 640 | final String? toolbarBg; 641 | 642 | final int? width; 643 | 644 | @JsonKey(name: 'charts_storage_url') 645 | final String? chartsStorageUrl; 646 | 647 | @JsonKey(name: 'charts_storage_api_version') 648 | final String? chartsStorageApiVersion; 649 | 650 | @JsonKey(name: 'client_id') 651 | final String? clientId; 652 | 653 | @JsonKey(name: 'user_id') 654 | final String? userId; 655 | 656 | @JsonKey(name: 'load_last_chart') 657 | final bool? loadLastChart; 658 | 659 | // TODO: Use custom class that handle Map 660 | @JsonKey(name: 'studies_overrides') 661 | final Map? studiesOverrides; 662 | 663 | // TODO: Use custom class that handle Map 664 | final Map? overrides; 665 | 666 | @JsonKey(name: 'snapshot_url') 667 | final String? snapshotUrl; 668 | 669 | final String? preset; 670 | 671 | @JsonKey(name: 'time_frames') 672 | final TimeFrameItem? timeFrames; 673 | 674 | @JsonKey(name: 'custom_css_url') 675 | final String? customCssUrl; 676 | 677 | final Favorites? favorites; 678 | 679 | @JsonKey(name: 'loading_screen') 680 | final LoadingScreenOptions? loadingScreen; 681 | 682 | final ChartTheme? theme; 683 | 684 | @JsonKey(name: 'compare_symbols') 685 | final List? compareSymbols; 686 | 687 | const ChartingLibraryWidgetOptions({ 688 | required this.interval, 689 | this.symbol, 690 | this.autoSaveDelay, 691 | this.autosize, 692 | this.debug, 693 | this.disabledFeatures, 694 | this.drawingsAccess, 695 | this.enabledFeatures, 696 | this.fullscreen, 697 | this.height, 698 | required this.locale, 699 | this.numericFormatting, 700 | this.savedData, 701 | this.savedDataMetaInfo, 702 | this.studiesAccess, 703 | this.studyCountLimit, 704 | this.symbolSearchRequestDelay, 705 | this.timeframe, 706 | this.timezone, 707 | this.toolbarBg, 708 | this.width, 709 | this.chartsStorageUrl, 710 | this.chartsStorageApiVersion, 711 | this.clientId, 712 | this.userId, 713 | this.loadLastChart, 714 | this.studiesOverrides, 715 | this.overrides, 716 | this.snapshotUrl, 717 | this.preset, 718 | this.timeFrames, 719 | this.customCssUrl, 720 | this.favorites, 721 | this.loadingScreen, 722 | this.theme, 723 | this.compareSymbols, 724 | }); 725 | 726 | factory ChartingLibraryWidgetOptions.fromJson(Map json) => 727 | _$ChartingLibraryWidgetOptionsFromJson(json); 728 | 729 | Map toJson() => _$ChartingLibraryWidgetOptionsToJson(this); 730 | } 731 | 732 | @immutable 733 | @JsonSerializable(includeIfNull: false) 734 | class SavedStateMetaInfo { 735 | final int uid; 736 | final String name; 737 | final String description; 738 | 739 | const SavedStateMetaInfo({ 740 | required this.uid, 741 | required this.name, 742 | required this.description, 743 | }); 744 | 745 | factory SavedStateMetaInfo.fromJson(Map json) => 746 | _$SavedStateMetaInfoFromJson(json); 747 | 748 | Map toJson() => _$SavedStateMetaInfoToJson(this); 749 | } 750 | 751 | @immutable 752 | @JsonSerializable(includeIfNull: false) 753 | class CompareSymbol { 754 | final String symbol; 755 | final String title; 756 | 757 | const CompareSymbol({ 758 | required this.symbol, 759 | required this.title, 760 | }); 761 | 762 | factory CompareSymbol.fromJson(Map json) => 763 | _$CompareSymbolFromJson(json); 764 | 765 | Map toJson() => _$CompareSymbolToJson(this); 766 | } 767 | 768 | @immutable 769 | @JsonSerializable(includeIfNull: false) 770 | class LoadingScreenOptions { 771 | final String? foregroundColor; 772 | final String? backgroundColor; 773 | 774 | const LoadingScreenOptions({ 775 | this.foregroundColor, 776 | this.backgroundColor, 777 | }); 778 | 779 | factory LoadingScreenOptions.fromJson(Map json) => 780 | _$LoadingScreenOptionsFromJson(json); 781 | 782 | Map toJson() => _$LoadingScreenOptionsToJson(this); 783 | } 784 | 785 | @immutable 786 | @JsonSerializable(includeIfNull: false) 787 | class Favorites { 788 | final List intervals; 789 | final List chartTypes; 790 | 791 | const Favorites({ 792 | required this.intervals, 793 | required this.chartTypes, 794 | }); 795 | 796 | factory Favorites.fromJson(Map json) => 797 | _$FavoritesFromJson(json); 798 | 799 | Map toJson() => _$FavoritesToJson(this); 800 | } 801 | 802 | @immutable 803 | @JsonSerializable(includeIfNull: false) 804 | class TimeFrameItem { 805 | final String text; 806 | final String resolution; 807 | final String? description; 808 | final String? title; 809 | 810 | const TimeFrameItem({ 811 | required this.text, 812 | required this.resolution, 813 | this.description, 814 | this.title, 815 | }); 816 | 817 | factory TimeFrameItem.fromJson(Map json) => 818 | _$TimeFrameItemFromJson(json); 819 | 820 | Map toJson() => _$TimeFrameItemToJson(this); 821 | } 822 | 823 | @immutable 824 | @JsonSerializable(includeIfNull: false) 825 | class NumericFormattingParams { 826 | @JsonKey(name: 'decimal_sign') 827 | final String decimalSign; 828 | 829 | const NumericFormattingParams({ 830 | required this.decimalSign, 831 | }); 832 | 833 | factory NumericFormattingParams.fromJson(Map json) => 834 | _$NumericFormattingParamsFromJson(json); 835 | 836 | Map toJson() => _$NumericFormattingParamsToJson(this); 837 | } 838 | 839 | @immutable 840 | @JsonSerializable(includeIfNull: false) 841 | class AccessList { 842 | final AccessListType type; 843 | final List tools; 844 | 845 | const AccessList({ 846 | required this.type, 847 | required this.tools, 848 | }); 849 | 850 | factory AccessList.fromJson(Map json) => 851 | _$AccessListFromJson(json); 852 | 853 | Map toJson() => _$AccessListToJson(this); 854 | } 855 | 856 | @immutable 857 | @JsonSerializable(includeIfNull: false) 858 | class AccessListItem { 859 | final String name; 860 | final bool? grayed; 861 | 862 | const AccessListItem({ 863 | required this.name, 864 | this.grayed, 865 | }); 866 | 867 | factory AccessListItem.fromJson(Map json) => 868 | _$AccessListItemFromJson(json); 869 | 870 | Map toJson() => _$AccessListItemToJson(this); 871 | } 872 | -------------------------------------------------------------------------------- /flutter_app/lib/components/tvchart/tvchart_types.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'tvchart_types.dart'; 4 | 5 | // ************************************************************************** 6 | // JsonSerializableGenerator 7 | // ************************************************************************** 8 | 9 | PeriodParams _$PeriodParamsFromJson(Map json) => PeriodParams( 10 | from: json['from'] as int, 11 | to: json['to'] as int, 12 | countBack: json['countBack'] as int, 13 | firstDataRequest: json['firstDataRequest'] as bool, 14 | ); 15 | 16 | Map _$PeriodParamsToJson(PeriodParams instance) => 17 | { 18 | 'from': instance.from, 19 | 'to': instance.to, 20 | 'countBack': instance.countBack, 21 | 'firstDataRequest': instance.firstDataRequest, 22 | }; 23 | 24 | Bar _$BarFromJson(Map json) => Bar( 25 | time: json['time'] as int, 26 | open: (json['open'] as num).toDouble(), 27 | high: (json['high'] as num).toDouble(), 28 | low: (json['low'] as num).toDouble(), 29 | close: (json['close'] as num).toDouble(), 30 | volume: json['volume'] as int?, 31 | ); 32 | 33 | Map _$BarToJson(Bar instance) { 34 | final val = { 35 | 'time': instance.time, 36 | 'open': instance.open, 37 | 'high': instance.high, 38 | 'low': instance.low, 39 | 'close': instance.close, 40 | }; 41 | 42 | void writeNotNull(String key, dynamic value) { 43 | if (value != null) { 44 | val[key] = value; 45 | } 46 | } 47 | 48 | writeNotNull('volume', instance.volume); 49 | return val; 50 | } 51 | 52 | Exchange _$ExchangeFromJson(Map json) => Exchange( 53 | value: json['value'] as String, 54 | name: json['name'] as String, 55 | desc: json['desc'] as String, 56 | ); 57 | 58 | Map _$ExchangeToJson(Exchange instance) => { 59 | 'value': instance.value, 60 | 'name': instance.name, 61 | 'desc': instance.desc, 62 | }; 63 | 64 | DatafeedConfiguration _$DatafeedConfigurationFromJson( 65 | Map json) => 66 | DatafeedConfiguration( 67 | exchanges: (json['exchanges'] as List?) 68 | ?.map((e) => Exchange.fromJson(e as Map)) 69 | .toList(), 70 | supportedResolutions: (json['supported_resolutions'] as List?) 71 | ?.map((e) => e as String) 72 | .toList(), 73 | currencyCodes: (json['currency_codes'] as List?) 74 | ?.map((e) => e as String) 75 | .toList(), 76 | supportsMarks: json['supports_marks'] as bool?, 77 | supportsTime: json['supports_time'] as bool?, 78 | supportsTimescaleMarks: json['supports_timescale_marks'] as bool?, 79 | symbolsTypes: (json['symbols_types'] as List?) 80 | ?.map((e) => DatafeedSymbolType.fromJson(e as Map)) 81 | .toList(), 82 | ); 83 | 84 | Map _$DatafeedConfigurationToJson( 85 | DatafeedConfiguration instance) { 86 | final val = {}; 87 | 88 | void writeNotNull(String key, dynamic value) { 89 | if (value != null) { 90 | val[key] = value; 91 | } 92 | } 93 | 94 | writeNotNull('exchanges', instance.exchanges); 95 | writeNotNull('supported_resolutions', instance.supportedResolutions); 96 | writeNotNull('currency_codes', instance.currencyCodes); 97 | writeNotNull('supports_marks', instance.supportsMarks); 98 | writeNotNull('supports_time', instance.supportsTime); 99 | writeNotNull('supports_timescale_marks', instance.supportsTimescaleMarks); 100 | writeNotNull('symbols_types', instance.symbolsTypes); 101 | return val; 102 | } 103 | 104 | DatafeedSymbolType _$DatafeedSymbolTypeFromJson(Map json) => 105 | DatafeedSymbolType( 106 | name: json['name'] as String, 107 | value: json['value'] as String, 108 | ); 109 | 110 | Map _$DatafeedSymbolTypeToJson(DatafeedSymbolType instance) => 111 | { 112 | 'name': instance.name, 113 | 'value': instance.value, 114 | }; 115 | 116 | SearchSymbolResultItem _$SearchSymbolResultItemFromJson( 117 | Map json) => 118 | SearchSymbolResultItem( 119 | symbol: json['symbol'] as String, 120 | fullName: json['full_name'] as String, 121 | description: json['description'] as String, 122 | exchange: json['exchange'] as String, 123 | ticker: json['ticker'] as String, 124 | type: json['type'] as String, 125 | ); 126 | 127 | Map _$SearchSymbolResultItemToJson( 128 | SearchSymbolResultItem instance) => 129 | { 130 | 'symbol': instance.symbol, 131 | 'full_name': instance.fullName, 132 | 'description': instance.description, 133 | 'exchange': instance.exchange, 134 | 'ticker': instance.ticker, 135 | 'type': instance.type, 136 | }; 137 | 138 | LibrarySymbolInfo _$LibrarySymbolInfoFromJson(Map json) => 139 | LibrarySymbolInfo( 140 | name: json['name'] as String, 141 | fullName: json['full_name'] as String, 142 | baseName: (json['base_name'] as List?) 143 | ?.map((e) => e as String) 144 | .toList(), 145 | ticker: json['ticker'] as String?, 146 | description: json['description'] as String, 147 | type: json['type'] as String, 148 | session: json['session'] as String, 149 | sessionDisplay: json['session_display'] as String?, 150 | holidays: json['holidays'] as String?, 151 | corrections: json['corrections'] as String?, 152 | exchange: json['exchange'] as String, 153 | listedExchange: json['listed_exchange'] as String, 154 | timezone: _$enumDecode(_$TimezoneEnumMap, json['timezone']), 155 | format: _$enumDecode(_$SeriesFormatEnumMap, json['format']), 156 | pricescale: (json['pricescale'] as num).toDouble(), 157 | minmov: (json['minmov'] as num).toDouble(), 158 | fractional: json['fractional'] as bool?, 159 | minmove2: (json['minmove2'] as num?)?.toDouble(), 160 | hasIntraday: json['has_intraday'] as bool?, 161 | supportedResolutions: (json['supported_resolutions'] as List) 162 | .map((e) => e as String) 163 | .toList(), 164 | intradayMultipliers: (json['intraday_multipliers'] as List?) 165 | ?.map((e) => e as String) 166 | .toList(), 167 | hasSeconds: json['has_seconds'] as bool?, 168 | hasTicks: json['has_ticks'] as bool?, 169 | secondsMultipliers: (json['seconds_multipliers'] as List?) 170 | ?.map((e) => e as String) 171 | .toList(), 172 | hasDaily: json['has_daily'] as bool?, 173 | hasWeeklyAndMonthly: json['has_weekly_and_monthly'] as bool?, 174 | hasEmptyBars: json['has_empty_bars'] as bool?, 175 | hasNoVolume: json['has_no_volume'] as bool?, 176 | volumePrecision: (json['volume_precision'] as num?)?.toDouble(), 177 | dataStatus: json['data_status'] as String?, 178 | expired: json['expired'] as bool?, 179 | expirationDate: json['expiration_date'] as int?, 180 | sector: json['sector'] as String?, 181 | industry: json['industry'] as String?, 182 | currencyCode: json['currency_code'] as String?, 183 | originalCurrencyCode: json['original_currency_code'] as String?, 184 | ); 185 | 186 | Map _$LibrarySymbolInfoToJson(LibrarySymbolInfo instance) { 187 | final val = { 188 | 'name': instance.name, 189 | 'full_name': instance.fullName, 190 | }; 191 | 192 | void writeNotNull(String key, dynamic value) { 193 | if (value != null) { 194 | val[key] = value; 195 | } 196 | } 197 | 198 | writeNotNull('base_name', instance.baseName); 199 | writeNotNull('ticker', instance.ticker); 200 | val['description'] = instance.description; 201 | val['type'] = instance.type; 202 | val['session'] = instance.session; 203 | writeNotNull('session_display', instance.sessionDisplay); 204 | writeNotNull('holidays', instance.holidays); 205 | writeNotNull('corrections', instance.corrections); 206 | val['exchange'] = instance.exchange; 207 | val['listed_exchange'] = instance.listedExchange; 208 | val['timezone'] = _$TimezoneEnumMap[instance.timezone]; 209 | val['format'] = _$SeriesFormatEnumMap[instance.format]; 210 | val['pricescale'] = instance.pricescale; 211 | val['minmov'] = instance.minmov; 212 | writeNotNull('fractional', instance.fractional); 213 | writeNotNull('minmove2', instance.minmove2); 214 | writeNotNull('has_intraday', instance.hasIntraday); 215 | val['supported_resolutions'] = instance.supportedResolutions; 216 | writeNotNull('intraday_multipliers', instance.intradayMultipliers); 217 | writeNotNull('has_seconds', instance.hasSeconds); 218 | writeNotNull('has_ticks', instance.hasTicks); 219 | writeNotNull('seconds_multipliers', instance.secondsMultipliers); 220 | writeNotNull('has_daily', instance.hasDaily); 221 | writeNotNull('has_weekly_and_monthly', instance.hasWeeklyAndMonthly); 222 | writeNotNull('has_empty_bars', instance.hasEmptyBars); 223 | writeNotNull('has_no_volume', instance.hasNoVolume); 224 | writeNotNull('volume_precision', instance.volumePrecision); 225 | writeNotNull('data_status', instance.dataStatus); 226 | writeNotNull('expired', instance.expired); 227 | writeNotNull('expiration_date', instance.expirationDate); 228 | writeNotNull('sector', instance.sector); 229 | writeNotNull('industry', instance.industry); 230 | writeNotNull('currency_code', instance.currencyCode); 231 | writeNotNull('original_currency_code', instance.originalCurrencyCode); 232 | return val; 233 | } 234 | 235 | K _$enumDecode( 236 | Map enumValues, 237 | Object? source, { 238 | K? unknownValue, 239 | }) { 240 | if (source == null) { 241 | throw ArgumentError( 242 | 'A value must be provided. Supported values: ' 243 | '${enumValues.values.join(', ')}', 244 | ); 245 | } 246 | 247 | return enumValues.entries.singleWhere( 248 | (e) => e.value == source, 249 | orElse: () { 250 | if (unknownValue == null) { 251 | throw ArgumentError( 252 | '`$source` is not one of the supported values: ' 253 | '${enumValues.values.join(', ')}', 254 | ); 255 | } 256 | return MapEntry(unknownValue, enumValues.values.first); 257 | }, 258 | ).key; 259 | } 260 | 261 | const _$TimezoneEnumMap = { 262 | Timezone.utc: 'Etc/UTC', 263 | Timezone.africaCairo: 'Africa/Cairo', 264 | Timezone.africaJohannesburg: 'Africa/Johannesburg', 265 | Timezone.africaLagos: 'Africa/Lagos', 266 | Timezone.americaArgentinaBuenosAires: 'America/Argentina/Buenos_Aires', 267 | Timezone.americaBogota: 'America/Bogota', 268 | Timezone.americaCaracas: 'America/Caracas', 269 | Timezone.americaChicago: 'America/Chicago', 270 | Timezone.americaElSalvador: 'America/El_Salvador', 271 | Timezone.americaJuneau: 'America/Juneau', 272 | Timezone.americaLima: 'America/Lima', 273 | Timezone.americaLosAngeles: 'America/Los_Angeles', 274 | Timezone.americaMexicoCity: 'America/Mexico_City', 275 | Timezone.americaNewYork: 'America/New_York', 276 | Timezone.americaPhoenix: 'America/Phoenix', 277 | Timezone.americaSantiago: 'America/Santiago', 278 | Timezone.americaSaoPaulo: 'America/Sao_Paulo', 279 | Timezone.americaToronto: 'America/Toronto', 280 | Timezone.americaVancouver: 'America/Vancouver', 281 | Timezone.asiaAlmaty: 'Asia/Almaty', 282 | Timezone.asiaAshkhabad: 'Asia/Ashkhabad', 283 | Timezone.asiaBahrain: 'Asia/Bahrain', 284 | Timezone.asiaBangkok: 'Asia/Bangkok', 285 | Timezone.asiaChongqing: 'Asia/Chongqing', 286 | Timezone.asiaDubai: 'Asia/Dubai', 287 | Timezone.asiaHoChiMinh: 'Asia/Ho_Chi_Minh', 288 | Timezone.asiaHongKong: 'Asia/Hong_Kong', 289 | Timezone.asiaJakarta: 'Asia/Jakarta', 290 | Timezone.asiaJerusalem: 'Asia/Jerusalem', 291 | Timezone.asiaKathmandu: 'Asia/Kathmandu', 292 | Timezone.asiaKolkata: 'Asia/Kolkata', 293 | Timezone.asiaKuwait: 'Asia/Kuwait', 294 | Timezone.asiaMuscat: 'Asia/Muscat', 295 | Timezone.asiaQatar: 'Asia/Qatar', 296 | Timezone.asiaRiyadh: 'Asia/Riyadh', 297 | Timezone.asiaSeoul: 'Asia/Seoul', 298 | Timezone.asiaShanghai: 'Asia/Shanghai', 299 | Timezone.asiaSingapore: 'Asia/Singapore', 300 | Timezone.asiaTaipei: 'Asia/Taipei', 301 | Timezone.asiaTehran: 'Asia/Tehran', 302 | Timezone.asiaTokyo: 'Asia/Tokyo', 303 | Timezone.atlanticReykjavik: 'Atlantic/Reykjavik', 304 | Timezone.australiaACT: 'Australia/ACT', 305 | Timezone.australiaAdelaide: 'Australia/Adelaide', 306 | Timezone.australiaBrisbane: 'Australia/Brisbane', 307 | Timezone.australiaPerth: 'Australia/Perth', 308 | Timezone.australiaSydney: 'Australia/Sydney', 309 | Timezone.europeAmsterdam: 'Europe/Amsterdam', 310 | Timezone.europeAthens: 'Europe/Athens', 311 | Timezone.europeBelgrade: 'Europe/Belgrade', 312 | Timezone.europeBerlin: 'Europe/Berlin', 313 | Timezone.europeBrussels: 'Europe/Brussels', 314 | Timezone.europeCopenhagen: 'Europe/Copenhagen', 315 | Timezone.europeDublin: 'Europe/Dublin', 316 | Timezone.europeHelsinki: 'Europe/Helsinki', 317 | Timezone.europeIstanbul: 'Europe/Istanbul', 318 | Timezone.europeLisbon: 'Europe/Lisbon', 319 | Timezone.europeLondon: 'Europe/London', 320 | Timezone.europeLuxembourg: 'Europe/Luxembourg', 321 | Timezone.europeMadrid: 'Europe/Madrid', 322 | Timezone.europeMalta: 'Europe/Malta', 323 | Timezone.europeMoscow: 'Europe/Moscow', 324 | Timezone.europeOslo: 'Europe/Oslo', 325 | Timezone.europeParis: 'Europe/Paris', 326 | Timezone.europeRiga: 'Europe/Riga', 327 | Timezone.europeRome: 'Europe/Rome', 328 | Timezone.europeStockholm: 'Europe/Stockholm', 329 | Timezone.europeTallinn: 'Europe/Tallinn', 330 | Timezone.europeVilnius: 'Europe/Vilnius', 331 | Timezone.europeWarsaw: 'Europe/Warsaw', 332 | Timezone.europeZurich: 'Europe/Zurich', 333 | Timezone.pacificAuckland: 'Pacific/Auckland', 334 | Timezone.pacificChatham: 'Pacific/Chatham', 335 | Timezone.pacificFakaofo: 'Pacific/Fakaofo', 336 | Timezone.pacificHonolulu: 'Pacific/Honolulu', 337 | Timezone.pacificNorfolk: 'Pacific/Norfolk', 338 | Timezone.usMountain: 'US/Mountain', 339 | Timezone.exchange: 'exchange', 340 | }; 341 | 342 | const _$SeriesFormatEnumMap = { 343 | SeriesFormat.price: 'price', 344 | SeriesFormat.volume: 'volume', 345 | }; 346 | 347 | ChartingLibraryWidgetOptions _$ChartingLibraryWidgetOptionsFromJson( 348 | Map json) => 349 | ChartingLibraryWidgetOptions( 350 | interval: json['interval'] as String, 351 | symbol: json['symbol'] as String?, 352 | autoSaveDelay: json['auto_save_delay'] as int?, 353 | autosize: json['autosize'] as bool?, 354 | debug: json['debug'] as bool?, 355 | disabledFeatures: (json['disabled_features'] as List?) 356 | ?.map((e) => e as String) 357 | .toList(), 358 | drawingsAccess: json['drawings_access'] == null 359 | ? null 360 | : AccessList.fromJson( 361 | json['drawings_access'] as Map), 362 | enabledFeatures: (json['enabled_features'] as List?) 363 | ?.map((e) => e as String) 364 | .toList(), 365 | fullscreen: json['fullscreen'] as bool?, 366 | height: json['height'] as int?, 367 | locale: json['locale'] as String, 368 | numericFormatting: json['numeric_formatting'] == null 369 | ? null 370 | : NumericFormattingParams.fromJson( 371 | json['numeric_formatting'] as Map), 372 | savedData: json['saved_data'] as Map?, 373 | savedDataMetaInfo: json['saved_data_meta_info'] == null 374 | ? null 375 | : SavedStateMetaInfo.fromJson( 376 | json['saved_data_meta_info'] as Map), 377 | studiesAccess: json['studies_access'] == null 378 | ? null 379 | : AccessList.fromJson(json['studies_access'] as Map), 380 | studyCountLimit: json['study_count_limit'] as int?, 381 | symbolSearchRequestDelay: json['symbol_search_request_delay'] as int?, 382 | timeframe: json['timeframe'] as String?, 383 | timezone: _$enumDecodeNullable(_$TimezoneEnumMap, json['timezone']), 384 | toolbarBg: json['toolbar_bg'] as String?, 385 | width: json['width'] as int?, 386 | chartsStorageUrl: json['charts_storage_url'] as String?, 387 | chartsStorageApiVersion: json['charts_storage_api_version'] as String?, 388 | clientId: json['client_id'] as String?, 389 | userId: json['user_id'] as String?, 390 | loadLastChart: json['load_last_chart'] as bool?, 391 | studiesOverrides: json['studies_overrides'] as Map?, 392 | overrides: json['overrides'] as Map?, 393 | snapshotUrl: json['snapshot_url'] as String?, 394 | preset: json['preset'] as String?, 395 | timeFrames: json['time_frames'] == null 396 | ? null 397 | : TimeFrameItem.fromJson(json['time_frames'] as Map), 398 | customCssUrl: json['custom_css_url'] as String?, 399 | favorites: json['favorites'] == null 400 | ? null 401 | : Favorites.fromJson(json['favorites'] as Map), 402 | loadingScreen: json['loading_screen'] == null 403 | ? null 404 | : LoadingScreenOptions.fromJson( 405 | json['loading_screen'] as Map), 406 | theme: _$enumDecodeNullable(_$ChartThemeEnumMap, json['theme']), 407 | compareSymbols: (json['compare_symbols'] as List?) 408 | ?.map((e) => CompareSymbol.fromJson(e as Map)) 409 | .toList(), 410 | ); 411 | 412 | Map _$ChartingLibraryWidgetOptionsToJson( 413 | ChartingLibraryWidgetOptions instance) { 414 | final val = { 415 | 'interval': instance.interval, 416 | }; 417 | 418 | void writeNotNull(String key, dynamic value) { 419 | if (value != null) { 420 | val[key] = value; 421 | } 422 | } 423 | 424 | writeNotNull('symbol', instance.symbol); 425 | writeNotNull('auto_save_delay', instance.autoSaveDelay); 426 | writeNotNull('autosize', instance.autosize); 427 | writeNotNull('debug', instance.debug); 428 | writeNotNull('disabled_features', instance.disabledFeatures); 429 | writeNotNull('drawings_access', instance.drawingsAccess); 430 | writeNotNull('enabled_features', instance.enabledFeatures); 431 | writeNotNull('fullscreen', instance.fullscreen); 432 | writeNotNull('height', instance.height); 433 | val['locale'] = instance.locale; 434 | writeNotNull('numeric_formatting', instance.numericFormatting); 435 | writeNotNull('saved_data', instance.savedData); 436 | writeNotNull('saved_data_meta_info', instance.savedDataMetaInfo); 437 | writeNotNull('studies_access', instance.studiesAccess); 438 | writeNotNull('study_count_limit', instance.studyCountLimit); 439 | writeNotNull( 440 | 'symbol_search_request_delay', instance.symbolSearchRequestDelay); 441 | writeNotNull('timeframe', instance.timeframe); 442 | writeNotNull('timezone', _$TimezoneEnumMap[instance.timezone]); 443 | writeNotNull('toolbar_bg', instance.toolbarBg); 444 | writeNotNull('width', instance.width); 445 | writeNotNull('charts_storage_url', instance.chartsStorageUrl); 446 | writeNotNull('charts_storage_api_version', instance.chartsStorageApiVersion); 447 | writeNotNull('client_id', instance.clientId); 448 | writeNotNull('user_id', instance.userId); 449 | writeNotNull('load_last_chart', instance.loadLastChart); 450 | writeNotNull('studies_overrides', instance.studiesOverrides); 451 | writeNotNull('overrides', instance.overrides); 452 | writeNotNull('snapshot_url', instance.snapshotUrl); 453 | writeNotNull('preset', instance.preset); 454 | writeNotNull('time_frames', instance.timeFrames); 455 | writeNotNull('custom_css_url', instance.customCssUrl); 456 | writeNotNull('favorites', instance.favorites); 457 | writeNotNull('loading_screen', instance.loadingScreen); 458 | writeNotNull('theme', _$ChartThemeEnumMap[instance.theme]); 459 | writeNotNull('compare_symbols', instance.compareSymbols); 460 | return val; 461 | } 462 | 463 | K? _$enumDecodeNullable( 464 | Map enumValues, 465 | dynamic source, { 466 | K? unknownValue, 467 | }) { 468 | if (source == null) { 469 | return null; 470 | } 471 | return _$enumDecode(enumValues, source, unknownValue: unknownValue); 472 | } 473 | 474 | const _$ChartThemeEnumMap = { 475 | ChartTheme.light: 'Light', 476 | ChartTheme.dark: 'Dark', 477 | }; 478 | 479 | SavedStateMetaInfo _$SavedStateMetaInfoFromJson(Map json) => 480 | SavedStateMetaInfo( 481 | uid: json['uid'] as int, 482 | name: json['name'] as String, 483 | description: json['description'] as String, 484 | ); 485 | 486 | Map _$SavedStateMetaInfoToJson(SavedStateMetaInfo instance) => 487 | { 488 | 'uid': instance.uid, 489 | 'name': instance.name, 490 | 'description': instance.description, 491 | }; 492 | 493 | CompareSymbol _$CompareSymbolFromJson(Map json) => 494 | CompareSymbol( 495 | symbol: json['symbol'] as String, 496 | title: json['title'] as String, 497 | ); 498 | 499 | Map _$CompareSymbolToJson(CompareSymbol instance) => 500 | { 501 | 'symbol': instance.symbol, 502 | 'title': instance.title, 503 | }; 504 | 505 | LoadingScreenOptions _$LoadingScreenOptionsFromJson( 506 | Map json) => 507 | LoadingScreenOptions( 508 | foregroundColor: json['foregroundColor'] as String?, 509 | backgroundColor: json['backgroundColor'] as String?, 510 | ); 511 | 512 | Map _$LoadingScreenOptionsToJson( 513 | LoadingScreenOptions instance) { 514 | final val = {}; 515 | 516 | void writeNotNull(String key, dynamic value) { 517 | if (value != null) { 518 | val[key] = value; 519 | } 520 | } 521 | 522 | writeNotNull('foregroundColor', instance.foregroundColor); 523 | writeNotNull('backgroundColor', instance.backgroundColor); 524 | return val; 525 | } 526 | 527 | Favorites _$FavoritesFromJson(Map json) => Favorites( 528 | intervals: 529 | (json['intervals'] as List).map((e) => e as String).toList(), 530 | chartTypes: (json['chartTypes'] as List) 531 | .map((e) => e as String) 532 | .toList(), 533 | ); 534 | 535 | Map _$FavoritesToJson(Favorites instance) => { 536 | 'intervals': instance.intervals, 537 | 'chartTypes': instance.chartTypes, 538 | }; 539 | 540 | TimeFrameItem _$TimeFrameItemFromJson(Map json) => 541 | TimeFrameItem( 542 | text: json['text'] as String, 543 | resolution: json['resolution'] as String, 544 | description: json['description'] as String?, 545 | title: json['title'] as String?, 546 | ); 547 | 548 | Map _$TimeFrameItemToJson(TimeFrameItem instance) { 549 | final val = { 550 | 'text': instance.text, 551 | 'resolution': instance.resolution, 552 | }; 553 | 554 | void writeNotNull(String key, dynamic value) { 555 | if (value != null) { 556 | val[key] = value; 557 | } 558 | } 559 | 560 | writeNotNull('description', instance.description); 561 | writeNotNull('title', instance.title); 562 | return val; 563 | } 564 | 565 | NumericFormattingParams _$NumericFormattingParamsFromJson( 566 | Map json) => 567 | NumericFormattingParams( 568 | decimalSign: json['decimal_sign'] as String, 569 | ); 570 | 571 | Map _$NumericFormattingParamsToJson( 572 | NumericFormattingParams instance) => 573 | { 574 | 'decimal_sign': instance.decimalSign, 575 | }; 576 | 577 | AccessList _$AccessListFromJson(Map json) => AccessList( 578 | type: _$enumDecode(_$AccessListTypeEnumMap, json['type']), 579 | tools: (json['tools'] as List) 580 | .map((e) => AccessListItem.fromJson(e as Map)) 581 | .toList(), 582 | ); 583 | 584 | Map _$AccessListToJson(AccessList instance) => 585 | { 586 | 'type': _$AccessListTypeEnumMap[instance.type], 587 | 'tools': instance.tools, 588 | }; 589 | 590 | const _$AccessListTypeEnumMap = { 591 | AccessListType.black: 'black', 592 | AccessListType.white: 'white', 593 | }; 594 | 595 | AccessListItem _$AccessListItemFromJson(Map json) => 596 | AccessListItem( 597 | name: json['name'] as String, 598 | grayed: json['grayed'] as bool?, 599 | ); 600 | 601 | Map _$AccessListItemToJson(AccessListItem instance) { 602 | final val = { 603 | 'name': instance.name, 604 | }; 605 | 606 | void writeNotNull(String key, dynamic value) { 607 | if (value != null) { 608 | val[key] = value; 609 | } 610 | } 611 | 612 | writeNotNull('grayed', instance.grayed); 613 | return val; 614 | } 615 | -------------------------------------------------------------------------------- /flutter_app/lib/data/historical.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/foundation.dart'; 2 | import 'package:chart/commons/random.dart'; 3 | import 'package:chart/commons/tzdt.dart'; 4 | import 'package:chart/components/tvchart/tvchart_types.dart'; 5 | import 'package:timezone/timezone.dart'; 6 | 7 | Historical get historical { 8 | var instance = Historical._instance; 9 | if (instance == null) { 10 | instance = Historical._internal(); 11 | Historical._instance = instance; 12 | } 13 | 14 | return instance; 15 | } 16 | 17 | class Historical { 18 | static Historical? _instance; 19 | Historical._internal() { 20 | // Index 21 | _data.putIfAbsent( 22 | 'COMPOSITE', 23 | () => SymbolData( 24 | code: 'COMPOSITE', 25 | name: 'IDX Composite', 26 | description: 'IDX Composite', 27 | type: SymbolType.Index, 28 | ), 29 | ); 30 | 31 | _data.putIfAbsent( 32 | 'LQ45', 33 | () => SymbolData( 34 | code: 'LQ45', 35 | name: 'LQ45', 36 | description: 'LQ45', 37 | type: SymbolType.Index, 38 | ), 39 | ); 40 | 41 | // Stock 42 | _data.putIfAbsent( 43 | 'ADHI', 44 | () => SymbolData( 45 | code: 'ADHI', 46 | name: 'Adhi Karya (Persero) Tbk.', 47 | description: 'Adhi Karya (Persero) Tbk.', 48 | type: SymbolType.Stock, 49 | ), 50 | ); 51 | 52 | _data.putIfAbsent( 53 | 'BBCA', 54 | () => SymbolData( 55 | code: 'BBCA', 56 | name: 'Bank Central Asia Tbk.', 57 | description: 'Bank Central Asia Tbk.', 58 | type: SymbolType.Stock, 59 | ), 60 | ); 61 | 62 | _data.putIfAbsent( 63 | 'BMRI', 64 | () => SymbolData( 65 | code: 'BMRI', 66 | name: 'Bank Mandiri (Persero) Tbk.', 67 | description: 'Bank Mandiri (Persero) Tbk.', 68 | type: SymbolType.Stock, 69 | ), 70 | ); 71 | 72 | _data.putIfAbsent( 73 | 'KLBF', 74 | () => SymbolData( 75 | code: 'KLBF', 76 | name: 'Kalbe Farma Tbk.', 77 | description: 'Kalbe Farma Tbk.', 78 | type: SymbolType.Stock, 79 | ), 80 | ); 81 | 82 | _data.putIfAbsent( 83 | 'PGAS', 84 | () => SymbolData( 85 | code: 'PGAS', 86 | name: 'Perusahaan Gas Negara (Persero) Tbk.', 87 | description: 'Perusahaan Gas Negara (Persero) Tbk.', 88 | type: SymbolType.Stock, 89 | ), 90 | ); 91 | } 92 | 93 | final Map _data = {}; 94 | 95 | List searchSymbol( 96 | String userInput, 97 | String rawSymbolType, 98 | ) { 99 | final symbolType = getSymbolFromString(rawSymbolType); 100 | List searchResult = []; 101 | 102 | _data.forEach((key, value) { 103 | final userInputCase = userInput.toUpperCase(); 104 | final codeCase = value.code.toUpperCase(); 105 | final nameCase = value.name.toUpperCase(); 106 | final symbolCase = value.symbolString.toUpperCase(); 107 | final typeMatch = symbolType == null || symbolType == value.type; 108 | 109 | final isMatch = typeMatch && 110 | (codeCase.contains(userInputCase) || 111 | nameCase.contains(userInputCase) || 112 | symbolCase.contains(userInputCase)); 113 | 114 | if (isMatch) { 115 | searchResult.add(value.getSearchSymbolInfo()); 116 | } 117 | }); 118 | 119 | return searchResult; 120 | } 121 | 122 | SymbolData? getSymbol(String symbol) { 123 | if (symbol.contains(':')) { 124 | for (final s in _data.entries) { 125 | if (symbol == s.value.symbolString) { 126 | return s.value; 127 | } 128 | } 129 | } 130 | return _data[symbol]; 131 | } 132 | 133 | Future> getDataRange( 134 | String code, 135 | TZDateTime from, 136 | TZDateTime to, 137 | ) async { 138 | var d = _data[code]; 139 | if (d == null) return []; 140 | return (await d.getDataRange(from, to)); 141 | } 142 | } 143 | 144 | class SymbolData { 145 | final String code; 146 | String name; 147 | String description; 148 | 149 | SymbolType type; 150 | WeekSessions session; 151 | SymbolExchange exchange; 152 | SymbolExchange listedExchange; 153 | Timezone timezone; 154 | SeriesFormat format; 155 | 156 | double pricescale; 157 | double minmov; 158 | 159 | List resolutions; 160 | List data; 161 | 162 | SymbolData({ 163 | required this.code, 164 | this.name = '', 165 | this.description = '', 166 | this.type = SymbolType.Index, 167 | WeekSessions? session, 168 | this.exchange = SymbolExchange.idx, 169 | this.listedExchange = SymbolExchange.idx, 170 | this.timezone = Timezone.asiaJakarta, 171 | this.format = SeriesFormat.price, 172 | this.pricescale = 100, 173 | this.minmov = 1, 174 | List? resolutions, 175 | List? data, 176 | }) : session = session ?? _defaultSession(), 177 | data = data ?? _buildDummyData(), 178 | resolutions = resolutions ?? _defaultResolutions(); 179 | 180 | String get symbolString => '${exchange.jsValue}:$code'; 181 | 182 | LibrarySymbolInfo getLibrarySymbolInfo() { 183 | return LibrarySymbolInfo( 184 | name: symbolString, 185 | ticker: code, 186 | fullName: name, 187 | description: description, 188 | type: type.jsValue, 189 | session: session.toString(), 190 | exchange: exchange.jsValue, 191 | listedExchange: listedExchange.jsValue, 192 | timezone: timezone, 193 | format: format, 194 | pricescale: pricescale, 195 | minmov: minmov, 196 | supportedResolutions: List.from(resolutions), 197 | ); 198 | } 199 | 200 | SearchSymbolResultItem getSearchSymbolInfo() { 201 | return SearchSymbolResultItem( 202 | symbol: symbolString, 203 | ticker: code, 204 | fullName: name, 205 | type: type.jsValue, 206 | description: description, 207 | exchange: exchange.jsValue, 208 | ); 209 | } 210 | 211 | Future> getDataRange( 212 | TZDateTime from, 213 | TZDateTime to, 214 | ) async { 215 | // Simulate request delay 216 | await Future.delayed(const Duration(seconds: 1)); 217 | 218 | List result = []; 219 | 220 | for (int i = 0; i < data.length; i++) { 221 | final d = data[i]; 222 | 223 | if (d.dt.isBefore(from)) { 224 | continue; 225 | } else if (d.dt.isAfter(to)) { 226 | break; 227 | } 228 | 229 | result.add(d); 230 | } 231 | 232 | return result; 233 | } 234 | 235 | static List _defaultResolutions() { 236 | return ['1D']; 237 | } 238 | 239 | static WeekSessions _defaultSession() { 240 | return WeekSessions.weekdayOnly( 241 | DualSession( 242 | session1: SingleSession( 243 | from: tzToday(9, 0), 244 | to: tzToday(11, 30), 245 | ), 246 | session2: SingleSession( 247 | from: tzToday(13, 30), 248 | to: tzToday(15, 00), 249 | ), 250 | ), 251 | ); 252 | } 253 | 254 | static List _buildDummyData() { 255 | List dummy = []; 256 | 257 | final today = tzToday(0, 0); 258 | var time = tzDate(today.year - 1, 1, 1); 259 | double price = 10000; 260 | 261 | while (time.isBefore(today)) { 262 | List values = []; 263 | for (int i = 0; i < 10; i++) { 264 | values.add(doubleRandomRange(price - 100, price + 100).roundToDouble()); 265 | } 266 | 267 | double open = values.first; 268 | double close = values.last; 269 | double high = open; 270 | double low = open; 271 | 272 | for (final value in values) { 273 | if (value > high) high = value; 274 | if (value < low) low = value; 275 | } 276 | 277 | // 1,000 ~ 1,000,000 278 | int volume = intRandomRange(10000, 1000000); 279 | 280 | dummy.add( 281 | OHLCData( 282 | dt: time, 283 | open: open, 284 | high: high, 285 | low: low, 286 | close: close, 287 | volume: volume, 288 | ), 289 | ); 290 | 291 | price = close; 292 | time = time.add(const Duration(days: 1)); 293 | } 294 | 295 | return dummy; 296 | } 297 | } 298 | 299 | abstract class Session { 300 | @override 301 | String toString(); 302 | } 303 | 304 | class NoSession extends Session { 305 | @override 306 | String toString() => ''; 307 | } 308 | 309 | class SingleSession extends Session { 310 | final TZDateTime from; 311 | final TZDateTime to; 312 | 313 | SingleSession({ 314 | required this.from, 315 | required this.to, 316 | }); 317 | 318 | @override 319 | String toString() { 320 | String fromStr = tzFormat(from, 'HHmm'); 321 | String toStr = tzFormat(to, 'HHmm'); 322 | return '$fromStr-$toStr'; 323 | } 324 | } 325 | 326 | class DualSession extends Session { 327 | final SingleSession session1; 328 | final SingleSession session2; 329 | 330 | DualSession({ 331 | required this.session1, 332 | required this.session2, 333 | }); 334 | 335 | @override 336 | String toString() { 337 | String s1 = session1.toString(); 338 | String s2 = session2.toString(); 339 | return '$s1,$s2'; 340 | } 341 | } 342 | 343 | class WeekSessions { 344 | final Session monday; 345 | final Session tuesday; 346 | final Session wednesday; 347 | final Session thursday; 348 | final Session friday; 349 | final Session saturday; 350 | final Session sunday; 351 | 352 | WeekSessions({ 353 | required this.monday, 354 | required this.tuesday, 355 | required this.wednesday, 356 | required this.thursday, 357 | required this.friday, 358 | required this.saturday, 359 | required this.sunday, 360 | }); 361 | 362 | factory WeekSessions.weekdayOnly(Session session) { 363 | return WeekSessions( 364 | monday: session, 365 | tuesday: session, 366 | wednesday: session, 367 | thursday: session, 368 | friday: session, 369 | saturday: NoSession(), 370 | sunday: NoSession(), 371 | ); 372 | } 373 | 374 | @override 375 | String toString() { 376 | // 1 = Sunday 377 | // 2 = Monday 378 | // ... 379 | // 7 = Saturday 380 | List iterable = [ 381 | sunday, 382 | monday, 383 | tuesday, 384 | wednesday, 385 | thursday, 386 | friday, 387 | saturday, 388 | ]; 389 | 390 | String result = ''; 391 | for (int i = 0; i < iterable.length; i++) { 392 | String sString = iterable[i].toString(); 393 | 394 | if (sString.isNotEmpty) { 395 | String dayNumber = (i + 1).toString(); 396 | 397 | if (result.isNotEmpty) { 398 | result += '|$sString:$dayNumber'; 399 | } else { 400 | result = '$sString:$dayNumber'; 401 | } 402 | } 403 | } 404 | 405 | return result; 406 | } 407 | } 408 | 409 | @immutable 410 | class OHLCData { 411 | final TZDateTime dt; 412 | final double open; 413 | final double high; 414 | final double low; 415 | final double close; 416 | final int volume; 417 | 418 | const OHLCData({ 419 | required this.dt, 420 | required this.open, 421 | required this.high, 422 | required this.low, 423 | required this.close, 424 | required this.volume, 425 | }); 426 | } 427 | 428 | enum SymbolType { 429 | // ignore: constant_identifier_names 430 | Stock, 431 | // ignore: constant_identifier_names 432 | Index, 433 | // ignore: constant_identifier_names 434 | Forex, 435 | } 436 | 437 | SymbolType? getSymbolFromString(String symbolString) { 438 | switch (symbolString) { 439 | case 'stock': 440 | return SymbolType.Stock; 441 | 442 | case 'index': 443 | return SymbolType.Index; 444 | 445 | case 'forex': 446 | return SymbolType.Forex; 447 | 448 | default: 449 | return null; 450 | } 451 | } 452 | 453 | extension on SymbolType { 454 | String get jsValue { 455 | switch (this) { 456 | case SymbolType.Stock: 457 | return 'stock'; 458 | 459 | case SymbolType.Index: 460 | return 'index'; 461 | 462 | case SymbolType.Forex: 463 | return 'forex'; 464 | } 465 | } 466 | } 467 | 468 | enum SymbolExchange { 469 | idx, 470 | } 471 | 472 | extension on SymbolExchange { 473 | String get jsValue { 474 | switch (this) { 475 | case SymbolExchange.idx: 476 | return 'IDX'; 477 | } 478 | } 479 | } 480 | -------------------------------------------------------------------------------- /flutter_app/lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | import 'package:chart/commons/localhost.dart'; 4 | import 'package:flutter/foundation.dart'; 5 | import 'package:flutter/material.dart'; 6 | import 'package:chart/commons/tzdt.dart'; 7 | import 'package:chart/pages/home.dart'; 8 | import 'package:flutter_inappwebview/flutter_inappwebview.dart'; 9 | import 'package:timezone/data/latest.dart'; 10 | 11 | void main() async { 12 | WidgetsFlutterBinding.ensureInitialized(); 13 | 14 | if (!kReleaseMode && Platform.isAndroid) { 15 | await AndroidInAppWebViewController.setWebContentsDebuggingEnabled(true); 16 | } 17 | 18 | initializeTimeZones(); 19 | setupTZLocation(); 20 | 21 | runApp(const MyApp()); 22 | } 23 | 24 | class MyApp extends StatefulWidget { 25 | const MyApp({Key? key}) : super(key: key); 26 | 27 | @override 28 | _MyAppState createState() => _MyAppState(); 29 | } 30 | 31 | class _MyAppState extends State with WidgetsBindingObserver { 32 | @override 33 | void initState() { 34 | super.initState(); 35 | WidgetsBinding.instance!.addObserver(this); 36 | } 37 | 38 | @override 39 | void dispose() { 40 | WidgetsBinding.instance!.removeObserver(this); 41 | super.dispose(); 42 | } 43 | 44 | @override 45 | void didChangeAppLifecycleState(AppLifecycleState state) { 46 | super.didChangeAppLifecycleState(state); 47 | 48 | if (state == AppLifecycleState.resumed) { 49 | // Turn it back on when app switch to foreground 50 | localhostManager.startServer(); 51 | } else if (state == AppLifecycleState.paused || 52 | state == AppLifecycleState.detached) { 53 | // Turn off server when app switch to background 54 | localhostManager.stopServer(); 55 | } 56 | } 57 | 58 | @override 59 | Widget build(BuildContext context) { 60 | return MaterialApp( 61 | debugShowCheckedModeBanner: false, 62 | title: 'TV Chart Demo', 63 | theme: ThemeData.light(), 64 | darkTheme: ThemeData.dark(), 65 | themeMode: ThemeMode.system, 66 | home: const HomePage(), 67 | ); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /flutter_app/lib/pages/home.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:chart/components/tvchart/tvchart.dart'; 3 | 4 | class HomePage extends StatefulWidget { 5 | const HomePage({Key? key}) : super(key: key); 6 | 7 | @override 8 | _HomePageState createState() => _HomePageState(); 9 | } 10 | 11 | class _HomePageState extends State with TickerProviderStateMixin { 12 | late TabController _tabController; 13 | bool _isOnChartTab = false; 14 | 15 | @override 16 | void initState() { 17 | super.initState(); 18 | _tabController = TabController(length: 2, vsync: this); 19 | _tabController.addListener(() { 20 | if (!_tabController.indexIsChanging) { 21 | setState(() { 22 | _isOnChartTab = _tabController.index == 1; 23 | }); 24 | } 25 | }); 26 | } 27 | 28 | @override 29 | void dispose() { 30 | _tabController.dispose(); 31 | super.dispose(); 32 | } 33 | 34 | @override 35 | Widget build(BuildContext context) { 36 | return OrientationBuilder( 37 | builder: (context, orientation) { 38 | final hideAppBar = 39 | _isOnChartTab && orientation == Orientation.landscape; 40 | 41 | return Scaffold( 42 | appBar: !hideAppBar 43 | ? AppBar( 44 | brightness: Brightness.dark, 45 | title: const Text('Chart Example'), 46 | bottom: TabBar( 47 | controller: _tabController, 48 | tabs: const [ 49 | Tab(text: 'Home'), 50 | Tab(text: 'Chart Library'), 51 | ], 52 | ), 53 | ) 54 | : null, 55 | body: Container( 56 | padding: EdgeInsets.only( 57 | top: !hideAppBar ? 0 : MediaQuery.of(context).padding.top, 58 | ), 59 | child: TabBarView( 60 | controller: _tabController, 61 | children: const [ 62 | Center( 63 | child: Text('Home Body'), 64 | ), 65 | TVChart(), 66 | ], 67 | ), 68 | ), 69 | ); 70 | }, 71 | ); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /flutter_app/pubspec.lock: -------------------------------------------------------------------------------- 1 | # Generated by pub 2 | # See https://dart.dev/tools/pub/glossary#lockfile 3 | packages: 4 | _fe_analyzer_shared: 5 | dependency: transitive 6 | description: 7 | name: _fe_analyzer_shared 8 | url: "https://pub.dartlang.org" 9 | source: hosted 10 | version: "26.0.0" 11 | analyzer: 12 | dependency: transitive 13 | description: 14 | name: analyzer 15 | url: "https://pub.dartlang.org" 16 | source: hosted 17 | version: "2.3.0" 18 | args: 19 | dependency: transitive 20 | description: 21 | name: args 22 | url: "https://pub.dartlang.org" 23 | source: hosted 24 | version: "2.2.0" 25 | async: 26 | dependency: transitive 27 | description: 28 | name: async 29 | url: "https://pub.dartlang.org" 30 | source: hosted 31 | version: "2.8.1" 32 | boolean_selector: 33 | dependency: transitive 34 | description: 35 | name: boolean_selector 36 | url: "https://pub.dartlang.org" 37 | source: hosted 38 | version: "2.1.0" 39 | build: 40 | dependency: transitive 41 | description: 42 | name: build 43 | url: "https://pub.dartlang.org" 44 | source: hosted 45 | version: "2.1.0" 46 | build_config: 47 | dependency: transitive 48 | description: 49 | name: build_config 50 | url: "https://pub.dartlang.org" 51 | source: hosted 52 | version: "1.0.0" 53 | build_daemon: 54 | dependency: transitive 55 | description: 56 | name: build_daemon 57 | url: "https://pub.dartlang.org" 58 | source: hosted 59 | version: "3.0.0" 60 | build_resolvers: 61 | dependency: transitive 62 | description: 63 | name: build_resolvers 64 | url: "https://pub.dartlang.org" 65 | source: hosted 66 | version: "2.0.4" 67 | build_runner: 68 | dependency: "direct dev" 69 | description: 70 | name: build_runner 71 | url: "https://pub.dartlang.org" 72 | source: hosted 73 | version: "2.1.2" 74 | build_runner_core: 75 | dependency: transitive 76 | description: 77 | name: build_runner_core 78 | url: "https://pub.dartlang.org" 79 | source: hosted 80 | version: "7.1.0" 81 | built_collection: 82 | dependency: transitive 83 | description: 84 | name: built_collection 85 | url: "https://pub.dartlang.org" 86 | source: hosted 87 | version: "5.1.0" 88 | built_value: 89 | dependency: transitive 90 | description: 91 | name: built_value 92 | url: "https://pub.dartlang.org" 93 | source: hosted 94 | version: "8.1.1" 95 | characters: 96 | dependency: transitive 97 | description: 98 | name: characters 99 | url: "https://pub.dartlang.org" 100 | source: hosted 101 | version: "1.1.0" 102 | charcode: 103 | dependency: transitive 104 | description: 105 | name: charcode 106 | url: "https://pub.dartlang.org" 107 | source: hosted 108 | version: "1.3.1" 109 | checked_yaml: 110 | dependency: transitive 111 | description: 112 | name: checked_yaml 113 | url: "https://pub.dartlang.org" 114 | source: hosted 115 | version: "2.0.1" 116 | cli_util: 117 | dependency: transitive 118 | description: 119 | name: cli_util 120 | url: "https://pub.dartlang.org" 121 | source: hosted 122 | version: "0.3.3" 123 | clock: 124 | dependency: transitive 125 | description: 126 | name: clock 127 | url: "https://pub.dartlang.org" 128 | source: hosted 129 | version: "1.1.0" 130 | code_builder: 131 | dependency: transitive 132 | description: 133 | name: code_builder 134 | url: "https://pub.dartlang.org" 135 | source: hosted 136 | version: "4.1.0" 137 | collection: 138 | dependency: transitive 139 | description: 140 | name: collection 141 | url: "https://pub.dartlang.org" 142 | source: hosted 143 | version: "1.15.0" 144 | convert: 145 | dependency: transitive 146 | description: 147 | name: convert 148 | url: "https://pub.dartlang.org" 149 | source: hosted 150 | version: "3.0.1" 151 | crypto: 152 | dependency: transitive 153 | description: 154 | name: crypto 155 | url: "https://pub.dartlang.org" 156 | source: hosted 157 | version: "3.0.1" 158 | cupertino_icons: 159 | dependency: "direct main" 160 | description: 161 | name: cupertino_icons 162 | url: "https://pub.dartlang.org" 163 | source: hosted 164 | version: "1.0.3" 165 | dart_style: 166 | dependency: transitive 167 | description: 168 | name: dart_style 169 | url: "https://pub.dartlang.org" 170 | source: hosted 171 | version: "2.0.3" 172 | fake_async: 173 | dependency: transitive 174 | description: 175 | name: fake_async 176 | url: "https://pub.dartlang.org" 177 | source: hosted 178 | version: "1.2.0" 179 | file: 180 | dependency: transitive 181 | description: 182 | name: file 183 | url: "https://pub.dartlang.org" 184 | source: hosted 185 | version: "6.1.2" 186 | fixnum: 187 | dependency: transitive 188 | description: 189 | name: fixnum 190 | url: "https://pub.dartlang.org" 191 | source: hosted 192 | version: "1.0.0" 193 | flutter: 194 | dependency: "direct main" 195 | description: flutter 196 | source: sdk 197 | version: "0.0.0" 198 | flutter_inappwebview: 199 | dependency: "direct main" 200 | description: 201 | name: flutter_inappwebview 202 | url: "https://pub.dartlang.org" 203 | source: hosted 204 | version: "5.3.2" 205 | flutter_lints: 206 | dependency: "direct dev" 207 | description: 208 | name: flutter_lints 209 | url: "https://pub.dartlang.org" 210 | source: hosted 211 | version: "1.0.4" 212 | flutter_test: 213 | dependency: "direct dev" 214 | description: flutter 215 | source: sdk 216 | version: "0.0.0" 217 | frontend_server_client: 218 | dependency: transitive 219 | description: 220 | name: frontend_server_client 221 | url: "https://pub.dartlang.org" 222 | source: hosted 223 | version: "2.1.0" 224 | glob: 225 | dependency: transitive 226 | description: 227 | name: glob 228 | url: "https://pub.dartlang.org" 229 | source: hosted 230 | version: "2.0.1" 231 | graphs: 232 | dependency: transitive 233 | description: 234 | name: graphs 235 | url: "https://pub.dartlang.org" 236 | source: hosted 237 | version: "2.0.0" 238 | http_multi_server: 239 | dependency: transitive 240 | description: 241 | name: http_multi_server 242 | url: "https://pub.dartlang.org" 243 | source: hosted 244 | version: "3.0.1" 245 | http_parser: 246 | dependency: transitive 247 | description: 248 | name: http_parser 249 | url: "https://pub.dartlang.org" 250 | source: hosted 251 | version: "4.0.0" 252 | intl: 253 | dependency: "direct main" 254 | description: 255 | name: intl 256 | url: "https://pub.dartlang.org" 257 | source: hosted 258 | version: "0.17.0" 259 | io: 260 | dependency: transitive 261 | description: 262 | name: io 263 | url: "https://pub.dartlang.org" 264 | source: hosted 265 | version: "1.0.3" 266 | js: 267 | dependency: transitive 268 | description: 269 | name: js 270 | url: "https://pub.dartlang.org" 271 | source: hosted 272 | version: "0.6.3" 273 | json_annotation: 274 | dependency: "direct main" 275 | description: 276 | name: json_annotation 277 | url: "https://pub.dartlang.org" 278 | source: hosted 279 | version: "4.1.0" 280 | json_serializable: 281 | dependency: "direct dev" 282 | description: 283 | name: json_serializable 284 | url: "https://pub.dartlang.org" 285 | source: hosted 286 | version: "5.0.2" 287 | lints: 288 | dependency: transitive 289 | description: 290 | name: lints 291 | url: "https://pub.dartlang.org" 292 | source: hosted 293 | version: "1.0.1" 294 | logging: 295 | dependency: transitive 296 | description: 297 | name: logging 298 | url: "https://pub.dartlang.org" 299 | source: hosted 300 | version: "1.0.1" 301 | matcher: 302 | dependency: transitive 303 | description: 304 | name: matcher 305 | url: "https://pub.dartlang.org" 306 | source: hosted 307 | version: "0.12.10" 308 | meta: 309 | dependency: transitive 310 | description: 311 | name: meta 312 | url: "https://pub.dartlang.org" 313 | source: hosted 314 | version: "1.7.0" 315 | mime: 316 | dependency: transitive 317 | description: 318 | name: mime 319 | url: "https://pub.dartlang.org" 320 | source: hosted 321 | version: "1.0.0" 322 | package_config: 323 | dependency: transitive 324 | description: 325 | name: package_config 326 | url: "https://pub.dartlang.org" 327 | source: hosted 328 | version: "2.0.0" 329 | path: 330 | dependency: transitive 331 | description: 332 | name: path 333 | url: "https://pub.dartlang.org" 334 | source: hosted 335 | version: "1.8.0" 336 | pedantic: 337 | dependency: transitive 338 | description: 339 | name: pedantic 340 | url: "https://pub.dartlang.org" 341 | source: hosted 342 | version: "1.11.1" 343 | pool: 344 | dependency: transitive 345 | description: 346 | name: pool 347 | url: "https://pub.dartlang.org" 348 | source: hosted 349 | version: "1.5.0" 350 | pub_semver: 351 | dependency: transitive 352 | description: 353 | name: pub_semver 354 | url: "https://pub.dartlang.org" 355 | source: hosted 356 | version: "2.0.0" 357 | pubspec_parse: 358 | dependency: transitive 359 | description: 360 | name: pubspec_parse 361 | url: "https://pub.dartlang.org" 362 | source: hosted 363 | version: "1.0.0" 364 | shelf: 365 | dependency: transitive 366 | description: 367 | name: shelf 368 | url: "https://pub.dartlang.org" 369 | source: hosted 370 | version: "1.2.0" 371 | shelf_web_socket: 372 | dependency: transitive 373 | description: 374 | name: shelf_web_socket 375 | url: "https://pub.dartlang.org" 376 | source: hosted 377 | version: "1.0.1" 378 | sky_engine: 379 | dependency: transitive 380 | description: flutter 381 | source: sdk 382 | version: "0.0.99" 383 | source_gen: 384 | dependency: transitive 385 | description: 386 | name: source_gen 387 | url: "https://pub.dartlang.org" 388 | source: hosted 389 | version: "1.0.3" 390 | source_helper: 391 | dependency: transitive 392 | description: 393 | name: source_helper 394 | url: "https://pub.dartlang.org" 395 | source: hosted 396 | version: "1.3.0" 397 | source_span: 398 | dependency: transitive 399 | description: 400 | name: source_span 401 | url: "https://pub.dartlang.org" 402 | source: hosted 403 | version: "1.8.1" 404 | stack_trace: 405 | dependency: transitive 406 | description: 407 | name: stack_trace 408 | url: "https://pub.dartlang.org" 409 | source: hosted 410 | version: "1.10.0" 411 | stream_channel: 412 | dependency: transitive 413 | description: 414 | name: stream_channel 415 | url: "https://pub.dartlang.org" 416 | source: hosted 417 | version: "2.1.0" 418 | stream_transform: 419 | dependency: transitive 420 | description: 421 | name: stream_transform 422 | url: "https://pub.dartlang.org" 423 | source: hosted 424 | version: "2.0.0" 425 | string_scanner: 426 | dependency: transitive 427 | description: 428 | name: string_scanner 429 | url: "https://pub.dartlang.org" 430 | source: hosted 431 | version: "1.1.0" 432 | term_glyph: 433 | dependency: transitive 434 | description: 435 | name: term_glyph 436 | url: "https://pub.dartlang.org" 437 | source: hosted 438 | version: "1.2.0" 439 | test_api: 440 | dependency: transitive 441 | description: 442 | name: test_api 443 | url: "https://pub.dartlang.org" 444 | source: hosted 445 | version: "0.4.2" 446 | timezone: 447 | dependency: "direct main" 448 | description: 449 | name: timezone 450 | url: "https://pub.dartlang.org" 451 | source: hosted 452 | version: "0.8.0" 453 | timing: 454 | dependency: transitive 455 | description: 456 | name: timing 457 | url: "https://pub.dartlang.org" 458 | source: hosted 459 | version: "1.0.0" 460 | typed_data: 461 | dependency: transitive 462 | description: 463 | name: typed_data 464 | url: "https://pub.dartlang.org" 465 | source: hosted 466 | version: "1.3.0" 467 | vector_math: 468 | dependency: transitive 469 | description: 470 | name: vector_math 471 | url: "https://pub.dartlang.org" 472 | source: hosted 473 | version: "2.1.0" 474 | watcher: 475 | dependency: transitive 476 | description: 477 | name: watcher 478 | url: "https://pub.dartlang.org" 479 | source: hosted 480 | version: "1.0.0" 481 | web_socket_channel: 482 | dependency: transitive 483 | description: 484 | name: web_socket_channel 485 | url: "https://pub.dartlang.org" 486 | source: hosted 487 | version: "2.1.0" 488 | yaml: 489 | dependency: transitive 490 | description: 491 | name: yaml 492 | url: "https://pub.dartlang.org" 493 | source: hosted 494 | version: "3.1.0" 495 | sdks: 496 | dart: ">=2.12.0 <3.0.0" 497 | flutter: ">=1.22.2" 498 | -------------------------------------------------------------------------------- /flutter_app/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: chart 2 | description: A new Flutter project. 3 | 4 | publish_to: 'none' 5 | 6 | version: 1.0.0+1 7 | 8 | environment: 9 | sdk: ">=2.12.0 <3.0.0" 10 | 11 | dependencies: 12 | flutter: 13 | sdk: flutter 14 | 15 | cupertino_icons: ^1.0.2 16 | flutter_inappwebview: ^5.3.2 17 | intl: ^0.17.0 18 | json_annotation: ^4.1.0 19 | timezone: ^0.8.0 20 | 21 | dev_dependencies: 22 | flutter_test: 23 | sdk: flutter 24 | 25 | build_runner: ^2.1.2 26 | flutter_lints: ^1.0.4 27 | json_serializable: ^5.0.2 28 | 29 | flutter: 30 | uses-material-design: true 31 | 32 | assets: 33 | - assets/tvchart/ 34 | - assets/tvchart/public/ 35 | - assets/tvchart/public/bundles/ 36 | -------------------------------------------------------------------------------- /flutter_app/test/widget_test.dart: -------------------------------------------------------------------------------- 1 | // This is a basic Flutter widget test. 2 | // 3 | // To perform an interaction with a widget in your test, use the WidgetTester 4 | // utility that Flutter provides. For example, you can send tap and scroll 5 | // gestures. You can also use WidgetTester to find child widgets in the widget 6 | // tree, read text, and verify that the values of widget properties are correct. 7 | 8 | import 'package:flutter/material.dart'; 9 | import 'package:flutter_test/flutter_test.dart'; 10 | 11 | import 'package:chart/main.dart'; 12 | 13 | void main() { 14 | testWidgets('Counter increments smoke test', (WidgetTester tester) async { 15 | // Build our app and trigger a frame. 16 | await tester.pumpWidget(const MyApp()); 17 | 18 | // Verify that our counter starts at 0. 19 | expect(find.text('0'), findsOneWidget); 20 | expect(find.text('1'), findsNothing); 21 | 22 | // Tap the '+' icon and trigger a frame. 23 | await tester.tap(find.byIcon(Icons.add)); 24 | await tester.pump(); 25 | 26 | // Verify that our counter has incremented. 27 | expect(find.text('0'), findsNothing); 28 | expect(find.text('1'), findsOneWidget); 29 | }); 30 | } 31 | -------------------------------------------------------------------------------- /flutter_app/web/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SuperPenguin/charting-library-flutter/d780e5b3682e3d4bb3e5f8ee832f1f5f7e63832f/flutter_app/web/favicon.png -------------------------------------------------------------------------------- /flutter_app/web/icons/Icon-192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SuperPenguin/charting-library-flutter/d780e5b3682e3d4bb3e5f8ee832f1f5f7e63832f/flutter_app/web/icons/Icon-192.png -------------------------------------------------------------------------------- /flutter_app/web/icons/Icon-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SuperPenguin/charting-library-flutter/d780e5b3682e3d4bb3e5f8ee832f1f5f7e63832f/flutter_app/web/icons/Icon-512.png -------------------------------------------------------------------------------- /flutter_app/web/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | chart 30 | 31 | 32 | 33 | 36 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /flutter_app/web/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "chart", 3 | "short_name": "chart", 4 | "start_url": ".", 5 | "display": "standalone", 6 | "background_color": "#0175C2", 7 | "theme_color": "#0175C2", 8 | "description": "A new Flutter project.", 9 | "orientation": "portrait-primary", 10 | "prefer_related_applications": false, 11 | "icons": [ 12 | { 13 | "src": "icons/Icon-192.png", 14 | "sizes": "192x192", 15 | "type": "image/png" 16 | }, 17 | { 18 | "src": "icons/Icon-512.png", 19 | "sizes": "512x512", 20 | "type": "image/png" 21 | } 22 | ] 23 | } 24 | --------------------------------------------------------------------------------