├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ ├── feature_request.md │ └── security_report.md └── PULL_REQUEST_TEMPLATE.md ├── .gitignore ├── .gitmodules ├── .metadata ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── android ├── .gitignore ├── app │ ├── build.gradle │ └── src │ │ ├── debug │ │ └── AndroidManifest.xml │ │ ├── main │ │ ├── AndroidManifest.xml │ │ ├── kotlin │ │ │ └── com │ │ │ │ └── example │ │ │ │ ├── app_openemr │ │ │ │ └── MainActivity.kt │ │ │ │ └── openemr_app │ │ │ │ └── MainActivity.kt │ │ └── res │ │ │ ├── drawable │ │ │ └── launch_background.xml │ │ │ ├── mipmap-hdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-mdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xhdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xxhdpi │ │ │ ├── ic_launcher.png │ │ │ └── splash.png │ │ │ ├── mipmap-xxxhdpi │ │ │ └── ic_launcher.png │ │ │ └── values │ │ │ └── styles.xml │ │ └── profile │ │ └── AndroidManifest.xml ├── build.gradle ├── gradle.properties ├── gradle │ └── wrapper │ │ └── gradle-wrapper.properties └── settings.gradle ├── img ├── 1.png ├── 2.png ├── 3.png ├── 4.png ├── 5.png ├── 6.png ├── 7.png ├── auth.gif ├── database.gif ├── google_auth.gif ├── ip.gif └── storage.gif ├── ios ├── .gitignore ├── Flutter │ ├── AppFrameworkInfo.plist │ ├── Debug.xcconfig │ └── Release.xcconfig ├── Runner.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ ├── IDEWorkspaceChecks.plist │ │ │ └── WorkspaceSettings.xcsettings │ └── xcshareddata │ │ └── xcschemes │ │ └── Runner.xcscheme ├── Runner.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ ├── IDEWorkspaceChecks.plist │ │ └── WorkspaceSettings.xcsettings └── Runner │ ├── AppDelegate.swift │ ├── Assets.xcassets │ ├── AppIcon.appiconset │ │ ├── 1024.png │ │ ├── 114.png │ │ ├── 120.png │ │ ├── 180.png │ │ ├── 29.png │ │ ├── 40.png │ │ ├── 57.png │ │ ├── 58.png │ │ ├── 60.png │ │ ├── 80.png │ │ ├── 87.png │ │ └── Contents.json │ └── 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 ├── assets │ ├── fonts │ │ ├── gfFontIcon.ttf │ │ ├── gfFontIcons2.ttf │ │ ├── gfIconFonts.ttf │ │ ├── gfSocialFonts.ttf │ │ └── loader.ttf │ ├── gif │ │ ├── loader.gif │ │ ├── loader1.gif │ │ └── success1.gif │ ├── icons │ │ └── gflogo.png │ └── images │ │ ├── avatar.png │ │ ├── avatar1.png │ │ ├── avatar10.png │ │ ├── avatar11.png │ │ ├── avatar12.png │ │ ├── avatar2.png │ │ ├── avatar3.png │ │ ├── avatar4.png │ │ ├── avatar5.png │ │ ├── avatar6.png │ │ ├── avatar7.png │ │ ├── avatar8.png │ │ ├── avatar9.png │ │ ├── card.png │ │ ├── card1.png │ │ ├── card2.png │ │ ├── card3.png │ │ ├── card4.png │ │ ├── card5.png │ │ ├── firebase.png │ │ ├── gflogo.png │ │ ├── image.png │ │ ├── image1.png │ │ ├── image2.png │ │ ├── img.png │ │ ├── img1.png │ │ ├── img2.png │ │ ├── logo.png │ │ ├── orange.png │ │ ├── pink.png │ │ ├── purple.png │ │ └── red.png ├── const │ └── strings.dart ├── main.dart ├── models │ ├── patient.dart │ └── user.dart ├── screens │ ├── addpatient │ │ ├── add_patient.dart │ │ └── local_widgets │ │ │ └── custom_dropdown_field.dart │ ├── codescanner │ │ └── codescanner.dart │ ├── drawer │ │ ├── drawer.dart │ │ └── webview.dart │ ├── home.dart │ ├── login │ │ ├── create_account.dart │ │ ├── login.dart │ │ └── login2.dart │ ├── medicine │ │ └── medicine_recognition_ML_Kit.dart │ ├── patientList │ │ └── patient_list.dart │ ├── ppg │ │ ├── chart.dart │ │ └── heartRate.dart │ ├── register │ │ └── register.dart │ ├── shimmer │ │ └── shimmer.dart │ └── telehealth │ │ ├── chat.dart │ │ ├── local_widgets │ │ └── profileShimmer.dart │ │ ├── profile.dart │ │ ├── signaling.dart │ │ └── telehealth.dart └── utils │ ├── common.dart │ ├── customlistloadingshimmer.dart │ ├── network.dart │ ├── rest_ds.dart │ ├── system_padding.dart │ └── websocket.dart ├── pubspec.lock ├── pubspec.yaml └── test └── widget_test.dart /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug Report 3 | about: Report any bugs present in the code 4 | --- 5 | 6 | **Expected Behavior** 7 | 8 | **Actual Behavior** 9 | 10 | **Steps to Reproduce the Problem** 11 | 12 | **Screenshots/Video showcasing the issue** 13 | 14 | **What might be causing this behavior and your solution to it** 15 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature Request 3 | about: Suggest a feature for the Project 4 | --- 5 | 6 | **Describe the Feature** 7 | 8 | 9 | 10 | **Need of this Feature** 11 | 12 | 13 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/security_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Security Report 3 | about: Privately report a Security Vulnerability 4 | --- 5 | 6 | **Security Report** 7 | 8 | 12 | 13 | 15 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | **IMPORTANT: Please do not create a Pull Request without creating an issue first.** 2 | 3 | 4 | 5 | **Description** 6 | 7 | 8 | 9 | **Testing Methods** 10 | 11 | 12 | 13 | **Screenshots/Videos** 14 | 15 | 16 | 17 | **New Packages Added** 18 | 19 | 20 | 21 | **Closing Issues** 22 | 23 | 24 | -------------------------------------------------------------------------------- /.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 | .dart_tool/ 26 | .flutter-plugins 27 | .flutter-plugins-dependencies 28 | .packages 29 | .pub-cache/ 30 | .pub/ 31 | /build/ 32 | 33 | # Web related 34 | lib/generated_plugin_registrant.dart 35 | 36 | # Exceptions to above rules. 37 | !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages 38 | 39 | #Keys 40 | google-services.json -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "app-golang-openemr"] 2 | path = app-golang-openemr 3 | url = https://github.com/openemr/app-golang-openemr 4 | -------------------------------------------------------------------------------- /.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: 0b8abb4724aa590dd0f429683339b1e045a1594d 8 | channel: stable 9 | 10 | project_type: app 11 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Code of Conduct 2 | 3 | ## 1. Purpose 4 | 5 | A primary goal of OpenEMR is to be inclusive to the largest number of contributors, with the most varied and diverse backgrounds possible. As such, we are committed to providing a friendly, safe and welcoming environment for all, regardless of gender, sexual orientation, ability, ethnicity, socioeconomic status, and religion (or lack thereof). 6 | 7 | This code of conduct outlines our expectations for all those who participate in our community, as well as the consequences for unacceptable behavior. 8 | 9 | We invite all those who participate in OpenEMR to help us create safe and positive experiences for everyone. 10 | 11 | ## 2. Open Source Citizenship 12 | 13 | A supplemental goal of this Code of Conduct is to increase open source citizenship by encouraging participants to recognize and strengthen the relationships between our actions and their effects on our community. 14 | 15 | Communities mirror the societies in which they exist and positive action is essential to counteract the many forms of inequality and abuses of power that exist in society. 16 | 17 | If you see someone who is making an extra effort to ensure our community is welcoming, friendly, and encourages all participants to contribute to the fullest extent, we want to know. 18 | 19 | ## 3. Expected Behavior 20 | 21 | The following behaviors are expected and requested of all community members: 22 | 23 | * Participate in an authentic and active way. In doing so, you contribute to the health and longevity of this community. 24 | * Exercise consideration and respect in your speech and actions. 25 | * Attempt collaboration before conflict. 26 | * Refrain from demeaning, discriminatory, or harassing behavior and speech. 27 | * Be mindful of your surroundings and of your fellow participants. Alert community leaders if you notice a dangerous situation, someone in distress, or violations of this Code of Conduct, even if they seem inconsequential. 28 | * Remember that community event venues may be shared with members of the public; please be respectful to all patrons of these locations. 29 | 30 | ## 4. Unacceptable Behavior 31 | 32 | The following behaviors are considered harassment and are unacceptable within our community: 33 | 34 | * Violence, threats of violence or violent language directed against another person. 35 | * Sexist, racist, homophobic, transphobic, ableist or otherwise discriminatory jokes and language. 36 | * Posting or displaying sexually explicit or violent material. 37 | * Posting or threatening to post other people’s personally identifying information ("doxing"). 38 | * Personal insults, particularly those related to gender, sexual orientation, race, religion, or disability. 39 | * Inappropriate photography or recording. 40 | * Inappropriate physical contact. You should have someone’s consent before touching them. 41 | * Unwelcome sexual attention. This includes, sexualized comments or jokes; inappropriate touching, groping, and unwelcomed sexual advances. 42 | * Deliberate intimidation, stalking or following (online or in person). 43 | * Advocating for, or encouraging, any of the above behavior. 44 | * Sustained disruption of community events, including talks and presentations. 45 | 46 | ## 5. Consequences of Unacceptable Behavior 47 | 48 | Unacceptable behavior from any community member, including sponsors and those with decision-making authority, will not be tolerated. 49 | 50 | Anyone asked to stop unacceptable behavior is expected to comply immediately. 51 | 52 | If a community member engages in unacceptable behavior, the community organizers may take any action they deem appropriate, up to and including a temporary ban or permanent expulsion from the community without warning (and without refund in the case of a paid event). 53 | 54 | ## 6. Reporting Guidelines 55 | 56 | If you are subject to or witness unacceptable behavior, or have any other concerns, please notify a community organizer as soon as possible. This can be done by sending a direct message to "admins" on the community forums found at https://community.open-emr.org/. 57 | 58 | 59 | 60 | Additionally, community organizers are available to help community members engage with local law enforcement or to otherwise help those experiencing unacceptable behavior feel safe. In the context of in-person events, organizers will also provide escorts as desired by the person experiencing distress. 61 | 62 | ## 7. Addressing Grievances 63 | 64 | If you feel you have been falsely or unfairly accused of violating this Code of Conduct, you should notify OpenEMR with a concise description of your grievance. Your grievance will be handled in accordance with our existing governing policies. 65 | 66 | 67 | 68 | ## 8. Scope 69 | 70 | We expect all community participants (contributors, paid or otherwise; sponsors; and other guests) to abide by this Code of Conduct in all community venues–online and in-person–as well as in all one-on-one communications pertaining to community business. 71 | 72 | This code of conduct and its related procedures also applies to unacceptable behavior occurring outside the scope of community activities when such behavior has the potential to adversely affect the safety and well-being of community members. 73 | 74 | ## 10. License and attribution 75 | 76 | This Code of Conduct is distributed under a [Creative Commons Attribution-ShareAlike license](http://creativecommons.org/licenses/by-sa/3.0/). 77 | 78 | Portions of text derived from the [Django Code of Conduct](https://www.djangoproject.com/conduct/) and the [Geek Feminism Anti-Harassment Policy](http://geekfeminism.wikia.com/wiki/Conference_anti-harassment/Policy). 79 | 80 | Retrieved on November 22, 2016 from [http://citizencodeofconduct.org/](http://citizencodeofconduct.org/) -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | Thank you for your contribution. OpenEMR (and global healthcare) continues to get better because of people like you! 2 | 3 | The maintainers of OpenEMR want to get your pull request in as seamlessly as possible, so please ensure your code is consistent with our [development policies](https://open-emr.org/wiki/index.php/Development_Policies). 4 | 5 | 6 | We look forward to your contribution... 7 | 8 | ## Financial contributions 9 | 10 | We also welcome financial contributions in full transparency on our [open collective](https://opencollective.com/openemr). 11 | Anyone can file an expense. If the expense makes sense for the development of the community, it will be "merged" in the ledger of our open collective by the core contributors and the person who filed the expense will be reimbursed. 12 | 13 | ## Credits 14 | 15 | ### Contributors 16 | 17 | Thank you to all the people who have already contributed to openemr! 18 | 19 | 20 | ### Backers 21 | 22 | Thank you to all our backers! [[Become a backer](https://opencollective.com/openemr#backer)] 23 | 24 | 25 | 26 | ### Sponsors 27 | 28 | Thank you to all our sponsors! (please ask your company to also support this open source project by [becoming a sponsor](https://opencollective.com/openemr#sponsor)) 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # OpenEMRv2.2 2 | 3 | [OpenEMR](https://open-emr.org) is the most popular open source electronic health records and medical practice management solution. 4 | ## What's New? 5 | - Warnings has been removed 6 | - Deprecated has been fixed 7 | - Api has been improved 8 | - Databse has been added to medical recognition 9 | 10 | ## Future Work 11 | -> Error message based on API response (In Progress) 12 | 13 | ## For Developers 14 | 15 | If using OpenEMR directly from the code repository, then the following commands will build OpenEMR apk : 16 | 17 | ```shell 18 | flutter pub get 19 | flutter build apk|appbundle|ios|ios-framework 20 | ``` 21 | 22 | To run openemr in a device 23 | 24 | ```shell 25 | flutter pub get 26 | flutter run 27 | ``` 28 | ### How to use calling feature 29 | 1. Run the [app-golang-openemr](https://github.com/openemr/app-golang-openemr/tree/c6930bb8f84e572234daaa071add316334a247f5) 30 | 2. Enter the server ip address in the prompt 31 | ![](./img/ip.gif) 32 | 33 | ### How to Setup Firebase 34 | 35 | #### Project Creation 36 | 37 | 1. Go to [Firebase console](https://console.firebase.google.com/) 38 | 2. Login and click on `Add Project` card 39 | ![](./img/1.png) 40 | 3. Enter desired project name and click on `Continue` button 41 | ![](./img/2.png) 42 | 4. Disable Google Analytics if you want but we suggest you to keep it as it is and click on `Continue` button 43 | ![](./img/3.png) 44 | 5. Select default or desired account and click on `Continue`. (will not appear if you have disabled Google Analytics in previous step) 45 | ![](./img/4.png) 46 | 47 | #### Android - Connection 48 | 49 | 1. Select `Android` on home-page of your project 50 | ![](./img/5.png) 51 | 2. Enter a `com.example.openemr` as package name. You can checkout this post if you want to [use custom package name](https://medium.com/@skyblazar.cc/how-to-change-the-package-name-of-your-flutter-app-4529e6e6e6fc) 52 | ![](./img/6.png) 53 | 3. Enter the `SHA-1 hash`. [You can get the SHA-1 using this link](https://developers.google.com/android/guides/client-auth) 54 | 4. Click on `register app` button 55 | 5. Click on `Download google-services.json`. A json file will be downloaded to your desktop. 56 | ![](./img/7.png) 57 | 6. Click on `next` button then again click on `next` button followed by `skip this step` button. 58 | 7. Place the `google-services.json` in `android/app` directory. 59 | 8. Go to `android/build.gradle` and uncomment `line 12` 60 | 9. Go to `android/app/build.gradle` and uncomment `line 26 & 65` 61 | 62 | #### IOS - Connection 63 | 64 | Coming soon 65 | 66 | #### Enable Firebase services 67 | 68 | 1. Authentication(Used for login/register) 69 | - Enable Email / Password 70 | ![](./img/auth.gif) 71 | - Enable Google 72 | ![](./img/google_auth.gif) 73 | 2. Database(Used to store messages) 74 | ![](./img/database.gif) 75 | 3. Firestore(Used to store images shared in chat) 76 | ![](./img/storage.gif) 77 | 78 | #### Final step - turn firebase flag on 79 | 80 | Go to `lib/screens/home.dart` and change `firebaseFlag` to `true` from `false` 81 | 82 | ```diff 83 | - final firebaseFlag = false; 84 | + final firebaseFlag = true; 85 | ``` 86 | 87 | ## License 88 | 89 | [GNU GPL](LICENSE) 90 | -------------------------------------------------------------------------------- /android/.gitignore: -------------------------------------------------------------------------------- 1 | gradle-wrapper.jar 2 | /.gradle 3 | /captures/ 4 | /gradlew 5 | /gradlew.bat 6 | /local.properties 7 | GeneratedPluginRegistrant.java 8 | 9 | # Remember to never publicly share your keystore. 10 | # See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app 11 | key.properties 12 | -------------------------------------------------------------------------------- /android/app/build.gradle: -------------------------------------------------------------------------------- 1 | def localProperties = new Properties() 2 | def localPropertiesFile = rootProject.file('local.properties') 3 | if (localPropertiesFile.exists()) { 4 | localPropertiesFile.withReader('UTF-8') { reader -> 5 | localProperties.load(reader) 6 | } 7 | } 8 | 9 | def flutterRoot = localProperties.getProperty('flutter.sdk') 10 | if (flutterRoot == null) { 11 | throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") 12 | } 13 | 14 | def flutterVersionCode = localProperties.getProperty('flutter.versionCode') 15 | if (flutterVersionCode == null) { 16 | flutterVersionCode = '1' 17 | } 18 | 19 | def flutterVersionName = localProperties.getProperty('flutter.versionName') 20 | if (flutterVersionName == null) { 21 | flutterVersionName = '1.0' 22 | } 23 | 24 | apply plugin: 'com.android.application' 25 | // Uncomment the line if you want to use firebase 26 | // apply plugin: 'com.google.gms.google-services' 27 | apply plugin: 'kotlin-android' 28 | apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" 29 | 30 | android { 31 | compileSdkVersion 28 32 | 33 | sourceSets { 34 | main.java.srcDirs += 'src/main/kotlin' 35 | } 36 | 37 | lintOptions { 38 | disable 'InvalidPackage' 39 | } 40 | 41 | defaultConfig { 42 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). 43 | applicationId "com.example.openemr" 44 | minSdkVersion 21 45 | targetSdkVersion 28 46 | versionCode flutterVersionCode.toInteger() 47 | versionName flutterVersionName 48 | } 49 | 50 | buildTypes { 51 | release { 52 | // TODO: Add your own signing config for the release build. 53 | // Signing with the debug keys for now, so `flutter run --release` works. 54 | signingConfig signingConfigs.debug 55 | } 56 | } 57 | } 58 | 59 | flutter { 60 | source '../..' 61 | } 62 | 63 | dependencies { 64 | // Uncomment the line if you want to use firebase 65 | // implementation 'com.google.firebase:firebase-analytics:17.2.2' 66 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 67 | api 'com.google.firebase:firebase-ml-vision-image-label-model:17.0.2' 68 | 69 | } 70 | -------------------------------------------------------------------------------- /android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 8 | 12 | 19 | 23 | 27 | 32 | 36 | 37 | 38 | 39 | 40 | 41 | 43 | 46 | 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /android/app/src/main/kotlin/com/example/app_openemr/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.example.app_openemr 2 | 3 | import io.flutter.embedding.android.FlutterActivity 4 | 5 | class MainActivity: FlutterActivity() { 6 | } 7 | -------------------------------------------------------------------------------- /android/app/src/main/kotlin/com/example/openemr_app/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.example.openemr 2 | 3 | import io.flutter.embedding.android.FlutterActivity 4 | 5 | class MainActivity: FlutterActivity() { 6 | } 7 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openemr/app-flutter-openemr/4d19907d06914da0d163a6bba9cc3645981cd93b/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openemr/app-flutter-openemr/4d19907d06914da0d163a6bba9cc3645981cd93b/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openemr/app-flutter-openemr/4d19907d06914da0d163a6bba9cc3645981cd93b/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openemr/app-flutter-openemr/4d19907d06914da0d163a6bba9cc3645981cd93b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxhdpi/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openemr/app-flutter-openemr/4d19907d06914da0d163a6bba9cc3645981cd93b/android/app/src/main/res/mipmap-xxhdpi/splash.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openemr/app-flutter-openemr/4d19907d06914da0d163a6bba9cc3645981cd93b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /android/app/src/profile/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | ext.kotlin_version = '1.3.50' 3 | repositories { 4 | google() 5 | jcenter() 6 | } 7 | 8 | dependencies { 9 | classpath 'com.android.tools.build:gradle:3.5.0' 10 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 11 | // Uncomment the line if you want to use firebase 12 | // classpath 'com.google.gms:google-services:4.3.3' 13 | } 14 | } 15 | 16 | allprojects { 17 | repositories { 18 | google() 19 | jcenter() 20 | } 21 | } 22 | 23 | rootProject.buildDir = '../build' 24 | subprojects { 25 | project.buildDir = "${rootProject.buildDir}/${project.name}" 26 | } 27 | subprojects { 28 | project.evaluationDependsOn(':app') 29 | } 30 | 31 | task clean(type: Delete) { 32 | delete rootProject.buildDir 33 | } 34 | -------------------------------------------------------------------------------- /android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx1536M 2 | android.enableR8=true 3 | android.useAndroidX=true 4 | android.enableJetifier=true 5 | -------------------------------------------------------------------------------- /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-5.6.2-all.zip 7 | -------------------------------------------------------------------------------- /android/settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | 3 | def localPropertiesFile = new File(rootProject.projectDir, "local.properties") 4 | def properties = new Properties() 5 | 6 | assert localPropertiesFile.exists() 7 | localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) } 8 | 9 | def flutterSdkPath = properties.getProperty("flutter.sdk") 10 | assert flutterSdkPath != null, "flutter.sdk not set in local.properties" 11 | apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle" 12 | 13 | def flutterProjectRoot = rootProject.projectDir.parentFile.toPath() 14 | 15 | def plugins = new Properties() 16 | def pluginsFile = new File(flutterProjectRoot.toFile(), '.flutter-plugins') 17 | if (pluginsFile.exists()) { 18 | pluginsFile.withReader('UTF-8') { reader -> plugins.load(reader) } 19 | } 20 | 21 | plugins.each { name, path -> 22 | def pluginDirectory = flutterProjectRoot.resolve(path).resolve('android').toFile() 23 | include ":$name" 24 | project(":$name").projectDir = pluginDirectory 25 | } 26 | -------------------------------------------------------------------------------- /img/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openemr/app-flutter-openemr/4d19907d06914da0d163a6bba9cc3645981cd93b/img/1.png -------------------------------------------------------------------------------- /img/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openemr/app-flutter-openemr/4d19907d06914da0d163a6bba9cc3645981cd93b/img/2.png -------------------------------------------------------------------------------- /img/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openemr/app-flutter-openemr/4d19907d06914da0d163a6bba9cc3645981cd93b/img/3.png -------------------------------------------------------------------------------- /img/4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openemr/app-flutter-openemr/4d19907d06914da0d163a6bba9cc3645981cd93b/img/4.png -------------------------------------------------------------------------------- /img/5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openemr/app-flutter-openemr/4d19907d06914da0d163a6bba9cc3645981cd93b/img/5.png -------------------------------------------------------------------------------- /img/6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openemr/app-flutter-openemr/4d19907d06914da0d163a6bba9cc3645981cd93b/img/6.png -------------------------------------------------------------------------------- /img/7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openemr/app-flutter-openemr/4d19907d06914da0d163a6bba9cc3645981cd93b/img/7.png -------------------------------------------------------------------------------- /img/auth.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openemr/app-flutter-openemr/4d19907d06914da0d163a6bba9cc3645981cd93b/img/auth.gif -------------------------------------------------------------------------------- /img/database.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openemr/app-flutter-openemr/4d19907d06914da0d163a6bba9cc3645981cd93b/img/database.gif -------------------------------------------------------------------------------- /img/google_auth.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openemr/app-flutter-openemr/4d19907d06914da0d163a6bba9cc3645981cd93b/img/google_auth.gif -------------------------------------------------------------------------------- /img/ip.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openemr/app-flutter-openemr/4d19907d06914da0d163a6bba9cc3645981cd93b/img/ip.gif -------------------------------------------------------------------------------- /img/storage.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openemr/app-flutter-openemr/4d19907d06914da0d163a6bba9cc3645981cd93b/img/storage.gif -------------------------------------------------------------------------------- /ios/.gitignore: -------------------------------------------------------------------------------- 1 | *.mode1v3 2 | *.mode2v3 3 | *.moved-aside 4 | *.pbxuser 5 | *.perspectivev3 6 | **/*sync/ 7 | .sconsign.dblite 8 | .tags* 9 | **/.vagrant/ 10 | **/DerivedData/ 11 | Icon? 12 | **/Pods/ 13 | **/.symlinks/ 14 | profile 15 | xcuserdata 16 | **/.generated/ 17 | Flutter/App.framework 18 | Flutter/Flutter.framework 19 | Flutter/Flutter.podspec 20 | Flutter/Generated.xcconfig 21 | Flutter/app.flx 22 | Flutter/app.zip 23 | Flutter/flutter_assets/ 24 | Flutter/flutter_export_environment.sh 25 | ServiceDefinitions.json 26 | Runner/GeneratedPluginRegistrant.* 27 | 28 | # Exceptions to above rules. 29 | !default.mode1v3 30 | !default.mode2v3 31 | !default.pbxuser 32 | !default.perspectivev3 33 | -------------------------------------------------------------------------------- /ios/Flutter/AppFrameworkInfo.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | App 9 | CFBundleIdentifier 10 | io.flutter.flutter.app 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | App 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1.0 23 | MinimumOSVersion 24 | 8.0 25 | 26 | 27 | -------------------------------------------------------------------------------- /ios/Flutter/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Generated.xcconfig" 2 | -------------------------------------------------------------------------------- /ios/Flutter/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Generated.xcconfig" 2 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 39 | 40 | 41 | 42 | 43 | 44 | 54 | 56 | 62 | 63 | 64 | 65 | 66 | 67 | 73 | 75 | 81 | 82 | 83 | 84 | 86 | 87 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Runner/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import Flutter 3 | 4 | @UIApplicationMain 5 | @objc class AppDelegate: FlutterAppDelegate { 6 | override func application( 7 | _ application: UIApplication, 8 | didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? 9 | ) -> Bool { 10 | GeneratedPluginRegistrant.register(with: self) 11 | return super.application(application, didFinishLaunchingWithOptions: launchOptions) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openemr/app-flutter-openemr/4d19907d06914da0d163a6bba9cc3645981cd93b/ios/Runner/Assets.xcassets/AppIcon.appiconset/1024.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/114.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openemr/app-flutter-openemr/4d19907d06914da0d163a6bba9cc3645981cd93b/ios/Runner/Assets.xcassets/AppIcon.appiconset/114.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/120.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openemr/app-flutter-openemr/4d19907d06914da0d163a6bba9cc3645981cd93b/ios/Runner/Assets.xcassets/AppIcon.appiconset/120.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/180.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openemr/app-flutter-openemr/4d19907d06914da0d163a6bba9cc3645981cd93b/ios/Runner/Assets.xcassets/AppIcon.appiconset/180.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/29.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openemr/app-flutter-openemr/4d19907d06914da0d163a6bba9cc3645981cd93b/ios/Runner/Assets.xcassets/AppIcon.appiconset/29.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/40.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openemr/app-flutter-openemr/4d19907d06914da0d163a6bba9cc3645981cd93b/ios/Runner/Assets.xcassets/AppIcon.appiconset/40.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/57.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openemr/app-flutter-openemr/4d19907d06914da0d163a6bba9cc3645981cd93b/ios/Runner/Assets.xcassets/AppIcon.appiconset/57.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/58.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openemr/app-flutter-openemr/4d19907d06914da0d163a6bba9cc3645981cd93b/ios/Runner/Assets.xcassets/AppIcon.appiconset/58.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/60.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openemr/app-flutter-openemr/4d19907d06914da0d163a6bba9cc3645981cd93b/ios/Runner/Assets.xcassets/AppIcon.appiconset/60.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/80.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openemr/app-flutter-openemr/4d19907d06914da0d163a6bba9cc3645981cd93b/ios/Runner/Assets.xcassets/AppIcon.appiconset/80.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/87.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openemr/app-flutter-openemr/4d19907d06914da0d163a6bba9cc3645981cd93b/ios/Runner/Assets.xcassets/AppIcon.appiconset/87.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | {"images":[{"size":"60x60","expected-size":"180","filename":"180.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"3x"},{"size":"40x40","expected-size":"80","filename":"80.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"2x"},{"size":"40x40","expected-size":"120","filename":"120.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"3x"},{"size":"60x60","expected-size":"120","filename":"120.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"2x"},{"size":"57x57","expected-size":"57","filename":"57.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"1x"},{"size":"29x29","expected-size":"58","filename":"58.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"2x"},{"size":"29x29","expected-size":"29","filename":"29.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"1x"},{"size":"29x29","expected-size":"87","filename":"87.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"3x"},{"size":"57x57","expected-size":"114","filename":"114.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"2x"},{"size":"20x20","expected-size":"40","filename":"40.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"2x"},{"size":"20x20","expected-size":"60","filename":"60.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"3x"},{"size":"1024x1024","filename":"1024.png","expected-size":"1024","idiom":"ios-marketing","folder":"Assets.xcassets/AppIcon.appiconset/","scale":"1x"}]} -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "LaunchImage.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "LaunchImage@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "LaunchImage@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openemr/app-flutter-openemr/4d19907d06914da0d163a6bba9cc3645981cd93b/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openemr/app-flutter-openemr/4d19907d06914da0d163a6bba9cc3645981cd93b/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openemr/app-flutter-openemr/4d19907d06914da0d163a6bba9cc3645981cd93b/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md: -------------------------------------------------------------------------------- 1 | # Launch Screen Assets 2 | 3 | You can customize the launch screen with your own desired assets by replacing the image files in this directory. 4 | 5 | You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. -------------------------------------------------------------------------------- /ios/Runner/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /ios/Runner/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /ios/Runner/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | openemr 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | $(FLUTTER_BUILD_NAME) 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | $(FLUTTER_BUILD_NUMBER) 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIMainStoryboardFile 28 | Main 29 | UISupportedInterfaceOrientations 30 | 31 | UIInterfaceOrientationPortrait 32 | UIInterfaceOrientationLandscapeLeft 33 | UIInterfaceOrientationLandscapeRight 34 | 35 | UISupportedInterfaceOrientations~ipad 36 | 37 | UIInterfaceOrientationPortrait 38 | UIInterfaceOrientationPortraitUpsideDown 39 | UIInterfaceOrientationLandscapeLeft 40 | UIInterfaceOrientationLandscapeRight 41 | 42 | UIViewControllerBasedStatusBarAppearance 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /ios/Runner/Runner-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | #import "GeneratedPluginRegistrant.h" 2 | -------------------------------------------------------------------------------- /lib/assets/fonts/gfFontIcon.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openemr/app-flutter-openemr/4d19907d06914da0d163a6bba9cc3645981cd93b/lib/assets/fonts/gfFontIcon.ttf -------------------------------------------------------------------------------- /lib/assets/fonts/gfFontIcons2.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openemr/app-flutter-openemr/4d19907d06914da0d163a6bba9cc3645981cd93b/lib/assets/fonts/gfFontIcons2.ttf -------------------------------------------------------------------------------- /lib/assets/fonts/gfIconFonts.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openemr/app-flutter-openemr/4d19907d06914da0d163a6bba9cc3645981cd93b/lib/assets/fonts/gfIconFonts.ttf -------------------------------------------------------------------------------- /lib/assets/fonts/gfSocialFonts.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openemr/app-flutter-openemr/4d19907d06914da0d163a6bba9cc3645981cd93b/lib/assets/fonts/gfSocialFonts.ttf -------------------------------------------------------------------------------- /lib/assets/fonts/loader.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openemr/app-flutter-openemr/4d19907d06914da0d163a6bba9cc3645981cd93b/lib/assets/fonts/loader.ttf -------------------------------------------------------------------------------- /lib/assets/gif/loader.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openemr/app-flutter-openemr/4d19907d06914da0d163a6bba9cc3645981cd93b/lib/assets/gif/loader.gif -------------------------------------------------------------------------------- /lib/assets/gif/loader1.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openemr/app-flutter-openemr/4d19907d06914da0d163a6bba9cc3645981cd93b/lib/assets/gif/loader1.gif -------------------------------------------------------------------------------- /lib/assets/gif/success1.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openemr/app-flutter-openemr/4d19907d06914da0d163a6bba9cc3645981cd93b/lib/assets/gif/success1.gif -------------------------------------------------------------------------------- /lib/assets/icons/gflogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openemr/app-flutter-openemr/4d19907d06914da0d163a6bba9cc3645981cd93b/lib/assets/icons/gflogo.png -------------------------------------------------------------------------------- /lib/assets/images/avatar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openemr/app-flutter-openemr/4d19907d06914da0d163a6bba9cc3645981cd93b/lib/assets/images/avatar.png -------------------------------------------------------------------------------- /lib/assets/images/avatar1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openemr/app-flutter-openemr/4d19907d06914da0d163a6bba9cc3645981cd93b/lib/assets/images/avatar1.png -------------------------------------------------------------------------------- /lib/assets/images/avatar10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openemr/app-flutter-openemr/4d19907d06914da0d163a6bba9cc3645981cd93b/lib/assets/images/avatar10.png -------------------------------------------------------------------------------- /lib/assets/images/avatar11.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openemr/app-flutter-openemr/4d19907d06914da0d163a6bba9cc3645981cd93b/lib/assets/images/avatar11.png -------------------------------------------------------------------------------- /lib/assets/images/avatar12.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openemr/app-flutter-openemr/4d19907d06914da0d163a6bba9cc3645981cd93b/lib/assets/images/avatar12.png -------------------------------------------------------------------------------- /lib/assets/images/avatar2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openemr/app-flutter-openemr/4d19907d06914da0d163a6bba9cc3645981cd93b/lib/assets/images/avatar2.png -------------------------------------------------------------------------------- /lib/assets/images/avatar3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openemr/app-flutter-openemr/4d19907d06914da0d163a6bba9cc3645981cd93b/lib/assets/images/avatar3.png -------------------------------------------------------------------------------- /lib/assets/images/avatar4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openemr/app-flutter-openemr/4d19907d06914da0d163a6bba9cc3645981cd93b/lib/assets/images/avatar4.png -------------------------------------------------------------------------------- /lib/assets/images/avatar5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openemr/app-flutter-openemr/4d19907d06914da0d163a6bba9cc3645981cd93b/lib/assets/images/avatar5.png -------------------------------------------------------------------------------- /lib/assets/images/avatar6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openemr/app-flutter-openemr/4d19907d06914da0d163a6bba9cc3645981cd93b/lib/assets/images/avatar6.png -------------------------------------------------------------------------------- /lib/assets/images/avatar7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openemr/app-flutter-openemr/4d19907d06914da0d163a6bba9cc3645981cd93b/lib/assets/images/avatar7.png -------------------------------------------------------------------------------- /lib/assets/images/avatar8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openemr/app-flutter-openemr/4d19907d06914da0d163a6bba9cc3645981cd93b/lib/assets/images/avatar8.png -------------------------------------------------------------------------------- /lib/assets/images/avatar9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openemr/app-flutter-openemr/4d19907d06914da0d163a6bba9cc3645981cd93b/lib/assets/images/avatar9.png -------------------------------------------------------------------------------- /lib/assets/images/card.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openemr/app-flutter-openemr/4d19907d06914da0d163a6bba9cc3645981cd93b/lib/assets/images/card.png -------------------------------------------------------------------------------- /lib/assets/images/card1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openemr/app-flutter-openemr/4d19907d06914da0d163a6bba9cc3645981cd93b/lib/assets/images/card1.png -------------------------------------------------------------------------------- /lib/assets/images/card2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openemr/app-flutter-openemr/4d19907d06914da0d163a6bba9cc3645981cd93b/lib/assets/images/card2.png -------------------------------------------------------------------------------- /lib/assets/images/card3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openemr/app-flutter-openemr/4d19907d06914da0d163a6bba9cc3645981cd93b/lib/assets/images/card3.png -------------------------------------------------------------------------------- /lib/assets/images/card4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openemr/app-flutter-openemr/4d19907d06914da0d163a6bba9cc3645981cd93b/lib/assets/images/card4.png -------------------------------------------------------------------------------- /lib/assets/images/card5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openemr/app-flutter-openemr/4d19907d06914da0d163a6bba9cc3645981cd93b/lib/assets/images/card5.png -------------------------------------------------------------------------------- /lib/assets/images/firebase.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openemr/app-flutter-openemr/4d19907d06914da0d163a6bba9cc3645981cd93b/lib/assets/images/firebase.png -------------------------------------------------------------------------------- /lib/assets/images/gflogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openemr/app-flutter-openemr/4d19907d06914da0d163a6bba9cc3645981cd93b/lib/assets/images/gflogo.png -------------------------------------------------------------------------------- /lib/assets/images/image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openemr/app-flutter-openemr/4d19907d06914da0d163a6bba9cc3645981cd93b/lib/assets/images/image.png -------------------------------------------------------------------------------- /lib/assets/images/image1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openemr/app-flutter-openemr/4d19907d06914da0d163a6bba9cc3645981cd93b/lib/assets/images/image1.png -------------------------------------------------------------------------------- /lib/assets/images/image2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openemr/app-flutter-openemr/4d19907d06914da0d163a6bba9cc3645981cd93b/lib/assets/images/image2.png -------------------------------------------------------------------------------- /lib/assets/images/img.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openemr/app-flutter-openemr/4d19907d06914da0d163a6bba9cc3645981cd93b/lib/assets/images/img.png -------------------------------------------------------------------------------- /lib/assets/images/img1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openemr/app-flutter-openemr/4d19907d06914da0d163a6bba9cc3645981cd93b/lib/assets/images/img1.png -------------------------------------------------------------------------------- /lib/assets/images/img2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openemr/app-flutter-openemr/4d19907d06914da0d163a6bba9cc3645981cd93b/lib/assets/images/img2.png -------------------------------------------------------------------------------- /lib/assets/images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openemr/app-flutter-openemr/4d19907d06914da0d163a6bba9cc3645981cd93b/lib/assets/images/logo.png -------------------------------------------------------------------------------- /lib/assets/images/orange.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openemr/app-flutter-openemr/4d19907d06914da0d163a6bba9cc3645981cd93b/lib/assets/images/orange.png -------------------------------------------------------------------------------- /lib/assets/images/pink.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openemr/app-flutter-openemr/4d19907d06914da0d163a6bba9cc3645981cd93b/lib/assets/images/pink.png -------------------------------------------------------------------------------- /lib/assets/images/purple.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openemr/app-flutter-openemr/4d19907d06914da0d163a6bba9cc3645981cd93b/lib/assets/images/purple.png -------------------------------------------------------------------------------- /lib/assets/images/red.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openemr/app-flutter-openemr/4d19907d06914da0d163a6bba9cc3645981cd93b/lib/assets/images/red.png -------------------------------------------------------------------------------- /lib/const/strings.dart: -------------------------------------------------------------------------------- 1 | const loginendpoint = "/apis/api/auth"; 2 | const patientendpoint = "/apis/api/patient"; 3 | const addpatientendpoint = "/apis/api/patient"; -------------------------------------------------------------------------------- /lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:openemr/screens/home.dart'; 4 | // import './screens/home.dart'; 5 | 6 | void main(){ 7 | HttpOverrides.global = new MyHttpOverrides(); 8 | runApp(new MyApp()); 9 | } 10 | 11 | class MyApp extends StatelessWidget { 12 | @override 13 | Widget build(BuildContext context) => MaterialApp( 14 | title: 'OpenEMR', 15 | debugShowCheckedModeBanner: false, 16 | theme: ThemeData( 17 | primarySwatch: Colors.blue, 18 | ), 19 | home: HomePage(), 20 | ); 21 | } 22 | 23 | class MyHttpOverrides extends HttpOverrides{ 24 | @override 25 | HttpClient createHttpClient(SecurityContext context){ 26 | return super.createHttpClient(context) 27 | ..badCertificateCallback = (X509Certificate cert, String host, int port)=> true; 28 | } 29 | } -------------------------------------------------------------------------------- /lib/models/patient.dart: -------------------------------------------------------------------------------- 1 | class Patient { 2 | String _id; 3 | String _pid; 4 | String _pubpid; 5 | String _title; 6 | String _fname; 7 | String _mname; 8 | String _lname; 9 | String _street; 10 | String _postalCode; 11 | String _city; 12 | String _state; 13 | String _countryCode; 14 | String _phoneContact; 15 | String _dob; 16 | String _sex; 17 | String _race; 18 | String _ethnicity; 19 | String _username; 20 | String _tokenType; 21 | String _accessToken; 22 | String _userId; 23 | 24 | String get pid => _pid; 25 | String get title => _title; 26 | String get fname => _fname; 27 | String get mname => _mname; 28 | String get lname => _lname; 29 | String get sex => _sex; 30 | 31 | Patient( 32 | this._accessToken, 33 | this._tokenType, 34 | this._userId, 35 | this._username, 36 | this._city, 37 | this._countryCode, 38 | this._dob, 39 | this._ethnicity, 40 | this._fname, 41 | this._id, 42 | this._lname, 43 | this._mname, 44 | this._phoneContact, 45 | this._pid, 46 | this._postalCode, 47 | this._pubpid, 48 | this._race, 49 | this._sex, 50 | this._state, 51 | this._street, 52 | this._title); 53 | 54 | Patient.map(dynamic obj) { 55 | this._accessToken = obj["access_token"]; 56 | this._tokenType = obj["token_type"]; 57 | this._userId = obj["user_id"]; 58 | this._username = obj["username"]; 59 | this._city = obj["city"]; 60 | this._countryCode = obj["country_code"]; 61 | this._dob = obj["DOB"]; 62 | this._ethnicity = obj["ethnicity"]; 63 | this._fname = obj["fname"]; 64 | this._id = obj["id"]; 65 | this._lname = obj["lname"]; 66 | this._mname = obj["mname"]; 67 | this._phoneContact = obj["phone_contact"]; 68 | this._pid = obj["pid"]; 69 | this._postalCode = obj["postal_code"]; 70 | this._pubpid = obj["pubpid"]; 71 | this._race = obj["race"]; 72 | this._sex = obj["sex"]; 73 | this._state = obj["state"]; 74 | this._street = obj["street"]; 75 | this._title = obj["title"]; 76 | } 77 | 78 | Map toMap() { 79 | var map = new Map(); 80 | map["access_token"] = _accessToken; 81 | map["token_type"] = _tokenType; 82 | map["user_id"] = _userId; 83 | map["username"] = _username; 84 | map["city"] = _city; 85 | map["country_code"] = _countryCode; 86 | map["DOB"] = _dob; 87 | map["ethnicity"] = _ethnicity; 88 | map["fname"] = _fname; 89 | map["id"] = _id; 90 | map["lname"] = _lname; 91 | map["mname"] = _mname; 92 | map["phone_contact"] = _phoneContact; 93 | map["pid"] = _pid; 94 | map["postal_code"] = _postalCode; 95 | map["pubpid"] = _pubpid; 96 | map["race"] = _race; 97 | map["sex"] = _sex; 98 | map["state"] = _state; 99 | map["street"] = _street; 100 | map["title"] = _title; 101 | return map; 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /lib/models/user.dart: -------------------------------------------------------------------------------- 1 | class User { 2 | String _username; 3 | String _tokenType; 4 | String _accessToken; 5 | String _baseUrl; 6 | String _password; 7 | User(this._username, this._tokenType, this._accessToken, this._baseUrl, 8 | this._password); 9 | 10 | User.map(dynamic obj) { 11 | this._username = obj["username"]; 12 | this._tokenType = obj["token_type"]; 13 | this._accessToken = obj["access_token"]; 14 | this._baseUrl = obj["baseUrl"]; 15 | this._password = obj["password"]; 16 | } 17 | 18 | set username(String username) { 19 | this._username = username; 20 | } 21 | 22 | set password(String password) { 23 | this._password = password; 24 | } 25 | 26 | set url(String url) { 27 | this._baseUrl = url; 28 | } 29 | 30 | String get username => _username; 31 | String get tokenType => _tokenType; 32 | String get accessToken => _accessToken; 33 | String get baseUrl => _baseUrl; 34 | String get password => _password; 35 | 36 | Map toMap() { 37 | var map = new Map(); 38 | map["username"] = _username; 39 | map["tokenType"] = _tokenType; 40 | map["accessToken"] = _accessToken; 41 | map["baseUrl"] = _baseUrl; 42 | map["password"] = _password; 43 | return map; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /lib/screens/addpatient/local_widgets/custom_dropdown_field.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class CustomDropdownField extends StatefulWidget { 4 | final String label; 5 | final List options; 6 | final Function updateValue; 7 | final String value; 8 | 9 | CustomDropdownField({ 10 | this.label, 11 | this.options, 12 | this.updateValue, 13 | this.value, 14 | }); 15 | 16 | @override 17 | _CustomDropdownFieldState createState() => _CustomDropdownFieldState(); 18 | } 19 | 20 | class _CustomDropdownFieldState extends State { 21 | @override 22 | Widget build(BuildContext context) { 23 | return Container( 24 | padding: const EdgeInsets.symmetric(horizontal: 10), 25 | child: Row( 26 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 27 | children: [ 28 | Text( 29 | widget.label, 30 | style: TextStyle( 31 | color: Colors.black, 32 | fontSize: 16, 33 | ), 34 | ), 35 | DropdownButton( 36 | value: widget.value, 37 | icon: Icon(Icons.arrow_drop_down_rounded), 38 | iconSize: 20, 39 | elevation: 16, 40 | underline: Container( 41 | height: 2, 42 | color: Colors.black, 43 | ), 44 | onChanged: (String newValue) { 45 | setState(() { 46 | widget.updateValue(newValue); 47 | }); 48 | }, 49 | items: widget.options.map>((String value) { 50 | return DropdownMenuItem( 51 | value: value, 52 | child: Text(value), 53 | ); 54 | }).toList(), 55 | ), 56 | ], 57 | ), 58 | ); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /lib/screens/codescanner/codescanner.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'dart:io' show Platform; 3 | 4 | import 'package:barcode_scan/barcode_scan.dart'; 5 | import 'package:flutter/material.dart'; 6 | import 'package:flutter/services.dart'; 7 | import 'package:getwidget/getwidget.dart'; 8 | import 'package:flutter/cupertino.dart'; 9 | import '../../screens/drawer/webview.dart'; 10 | 11 | class CodeScanner extends StatefulWidget { 12 | @override 13 | _CodeScannerState createState() => _CodeScannerState(); 14 | } 15 | 16 | class _CodeScannerState extends State { 17 | ScanResult scanResult; 18 | 19 | final _flashOnController = TextEditingController(text: "Flash on"); 20 | final _flashOffController = TextEditingController(text: "Flash off"); 21 | final _cancelController = TextEditingController(text: "Cancel"); 22 | 23 | var _aspectTolerance = 0.00; 24 | var _numberOfCameras = 0; 25 | var _selectedCamera = -1; 26 | var _useAutoFocus = true; 27 | var _autoEnableFlash = false; 28 | 29 | static final _possibleFormats = BarcodeFormat.values.toList() 30 | ..removeWhere((e) => e == BarcodeFormat.unknown); 31 | 32 | List selectedFormats = [..._possibleFormats]; 33 | 34 | @override 35 | // ignore: type_annotate_public_apis 36 | initState() { 37 | super.initState(); 38 | 39 | Future.delayed(Duration.zero, () async { 40 | _numberOfCameras = await BarcodeScanner.numberOfCameras; 41 | setState(() {}); 42 | }); 43 | } 44 | 45 | @override 46 | Widget build(BuildContext context) { 47 | var contentList = [ 48 | if (scanResult != null) 49 | Card( 50 | child: Column( 51 | children: [ 52 | ListTile( 53 | title: Text("Result Type"), 54 | subtitle: Text(scanResult.type?.toString() ?? ""), 55 | ), 56 | ListTile( 57 | title: Text("Raw Content"), 58 | subtitle: Text(scanResult.rawContent ?? ""), 59 | trailing: IconButton( 60 | icon: Icon(Icons.content_copy), 61 | onPressed: () { 62 | Clipboard.setData( 63 | ClipboardData(text: scanResult.rawContent)); 64 | })), 65 | ListTile( 66 | title: Text("Format"), 67 | subtitle: Text(scanResult.format?.toString() ?? ""), 68 | ), 69 | ListTile( 70 | title: Text("Format note"), 71 | subtitle: Text(scanResult.formatNote ?? ""), 72 | ), 73 | Uri.parse(scanResult.rawContent).isAbsolute 74 | ? GFButton( 75 | onPressed: () { 76 | var uri = scanResult.rawContent; 77 | Navigator.push( 78 | context, 79 | MaterialPageRoute( 80 | builder: (BuildContext context) => WebViews( 81 | url: uri, 82 | ), 83 | )); 84 | }, 85 | shape: GFButtonShape.pills, 86 | child: const Text( 87 | 'Open', 88 | ), 89 | color: GFColors.DARK, 90 | ) 91 | : GFButton( 92 | onPressed: () { 93 | var uri = "https://www.google.com/search?q=" + 94 | Uri.encodeFull(scanResult.rawContent); 95 | Navigator.push( 96 | context, 97 | MaterialPageRoute( 98 | builder: (BuildContext context) => WebViews( 99 | url: uri, 100 | ), 101 | )); 102 | }, 103 | shape: GFButtonShape.pills, 104 | child: const Text( 105 | 'Search', 106 | ), 107 | color: GFColors.DARK, 108 | ) 109 | ], 110 | ), 111 | ), 112 | ListTile( 113 | title: Text("Camera selection"), 114 | dense: true, 115 | enabled: false, 116 | ), 117 | RadioListTile( 118 | activeColor: GFColors.DARK, 119 | onChanged: (v) => setState(() => _selectedCamera = -1), 120 | value: -1, 121 | title: Text("Default camera"), 122 | groupValue: _selectedCamera, 123 | ), 124 | ]; 125 | 126 | for (var i = 0; i < _numberOfCameras; i++) { 127 | contentList.add(RadioListTile( 128 | activeColor: GFColors.DARK, 129 | onChanged: (v) => setState(() => _selectedCamera = i), 130 | value: i, 131 | title: Text("Camera ${i + 1}"), 132 | groupValue: _selectedCamera, 133 | )); 134 | } 135 | 136 | if (Platform.isAndroid) { 137 | contentList.addAll([ 138 | ListTile( 139 | title: Text("Android specific options"), 140 | dense: true, 141 | enabled: false, 142 | ), 143 | ListTile( 144 | title: 145 | Text("Aspect tolerance (${_aspectTolerance.toStringAsFixed(2)})"), 146 | subtitle: Slider( 147 | activeColor: GFColors.DARK, 148 | min: -1.0, 149 | max: 1.0, 150 | value: _aspectTolerance, 151 | onChanged: (value) { 152 | setState(() { 153 | _aspectTolerance = value; 154 | }); 155 | }, 156 | ), 157 | ), 158 | CheckboxListTile( 159 | activeColor: GFColors.DARK, 160 | title: Text("Use autofocus"), 161 | value: _useAutoFocus, 162 | onChanged: (checked) { 163 | setState(() { 164 | _useAutoFocus = checked; 165 | }); 166 | }, 167 | ) 168 | ]); 169 | } 170 | 171 | contentList.addAll([ 172 | ListTile( 173 | title: Text("Other options"), 174 | dense: true, 175 | enabled: false, 176 | ), 177 | CheckboxListTile( 178 | activeColor: GFColors.DARK, 179 | title: Text("Start with flash"), 180 | value: _autoEnableFlash, 181 | onChanged: (checked) { 182 | setState(() { 183 | _autoEnableFlash = checked; 184 | }); 185 | }, 186 | ) 187 | ]); 188 | 189 | contentList.addAll([ 190 | ListTile( 191 | title: Text("Barcode formats"), 192 | dense: true, 193 | enabled: false, 194 | ), 195 | ListTile( 196 | trailing: Checkbox( 197 | activeColor: GFColors.DARK, 198 | tristate: true, 199 | materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, 200 | value: selectedFormats.length == _possibleFormats.length 201 | ? true 202 | : selectedFormats.length == 0 ? false : null, 203 | onChanged: (checked) { 204 | setState(() { 205 | selectedFormats = [ 206 | if (checked ?? false) ..._possibleFormats, 207 | ]; 208 | }); 209 | }, 210 | ), 211 | dense: true, 212 | enabled: false, 213 | title: Text("Detect barcode formats"), 214 | subtitle: Text( 215 | 'If all are unselected, all possible platform formats will be used', 216 | ), 217 | ), 218 | ]); 219 | 220 | contentList.addAll(_possibleFormats.map( 221 | (format) => CheckboxListTile( 222 | activeColor: GFColors.DARK, 223 | value: selectedFormats.contains(format), 224 | onChanged: (i) { 225 | setState(() => selectedFormats.contains(format) 226 | ? selectedFormats.remove(format) 227 | : selectedFormats.add(format)); 228 | }, 229 | title: Text(format.toString()), 230 | ), 231 | )); 232 | 233 | return MaterialApp( 234 | debugShowCheckedModeBanner: false, 235 | home: Scaffold( 236 | appBar: AppBar( 237 | backgroundColor: GFColors.DARK, 238 | leading: InkWell( 239 | onTap: () { 240 | Navigator.pop(context); 241 | }, 242 | child: Icon( 243 | CupertinoIcons.back, 244 | color: GFColors.SUCCESS, 245 | ), 246 | ), 247 | title: const Text( 248 | 'Code Scanner', 249 | style: TextStyle(fontSize: 17), 250 | ), 251 | centerTitle: true, 252 | actions: [ 253 | IconButton( 254 | icon: Icon(Icons.camera), 255 | tooltip: "Scan", 256 | onPressed: scan, 257 | ) 258 | ], 259 | ), 260 | body: ListView( 261 | scrollDirection: Axis.vertical, 262 | shrinkWrap: true, 263 | children: contentList, 264 | ), 265 | ), 266 | ); 267 | } 268 | 269 | Future scan() async { 270 | try { 271 | var options = ScanOptions( 272 | strings: { 273 | "cancel": _cancelController.text, 274 | "flash_on": _flashOnController.text, 275 | "flash_off": _flashOffController.text, 276 | }, 277 | restrictFormat: selectedFormats, 278 | useCamera: _selectedCamera, 279 | autoEnableFlash: _autoEnableFlash, 280 | android: AndroidOptions( 281 | aspectTolerance: _aspectTolerance, 282 | useAutoFocus: _useAutoFocus, 283 | ), 284 | ); 285 | 286 | var result = await BarcodeScanner.scan(options: options); 287 | 288 | setState(() => scanResult = result); 289 | } on PlatformException catch (e) { 290 | var result = ScanResult( 291 | type: ResultType.Error, 292 | format: BarcodeFormat.unknown, 293 | ); 294 | 295 | if (e.code == BarcodeScanner.cameraAccessDenied) { 296 | setState(() { 297 | result.rawContent = 'The user did not grant the camera permission!'; 298 | }); 299 | } else { 300 | result.rawContent = 'Unknown error: $e'; 301 | } 302 | setState(() { 303 | scanResult = result; 304 | }); 305 | } 306 | } 307 | } 308 | -------------------------------------------------------------------------------- /lib/screens/drawer/drawer.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:getwidget/getwidget.dart'; 4 | import '../../screens/drawer/webview.dart'; 5 | 6 | class DrawerPage extends StatefulWidget { 7 | @override 8 | _DrawerPageState createState() => _DrawerPageState(); 9 | } 10 | 11 | class _DrawerPageState extends State { 12 | @override 13 | Widget build(BuildContext context) => GFDrawer( 14 | color: Colors.white, 15 | child: ListView( 16 | padding: EdgeInsets.zero, 17 | children: [ 18 | Container( 19 | decoration: BoxDecoration( 20 | gradient: LinearGradient( 21 | begin: Alignment.topCenter, 22 | end: Alignment.bottomCenter, 23 | colors: const [Color(0xFFD685FF), Color(0xFF7466CC)])), 24 | height: 250, 25 | child: GFDrawerHeader( 26 | closeButton: InkWell( 27 | onTap: () { 28 | Navigator.pop(context); 29 | }, 30 | child: Icon( 31 | CupertinoIcons.back, 32 | color: GFColors.SUCCESS, 33 | ), 34 | ), 35 | decoration: BoxDecoration( 36 | gradient: LinearGradient( 37 | begin: Alignment.topCenter, 38 | end: Alignment.bottomCenter, 39 | colors: const [Color(0xFFD685FF), Color(0xFF7466CC)], 40 | ), 41 | ), 42 | child: Column( 43 | mainAxisAlignment: MainAxisAlignment.start, 44 | crossAxisAlignment: CrossAxisAlignment.center, 45 | children: [ 46 | GFAvatar( 47 | backgroundColor: GFColors.DARK, 48 | shape: GFAvatarShape.square, 49 | borderRadius: BorderRadius.all(Radius.circular(20)), 50 | backgroundImage: AssetImage( 51 | 'lib/assets/images/gflogo.png', 52 | ), 53 | ), 54 | const SizedBox( 55 | height: 5, 56 | ), 57 | Text( 58 | 'OpenEMR', 59 | style: TextStyle( 60 | fontSize: 20, 61 | fontWeight: FontWeight.w500, 62 | color: Colors.white), 63 | ), 64 | const SizedBox( 65 | height: 5, 66 | ), 67 | const Text( 68 | 'open-emr.org', 69 | style: TextStyle(color: Colors.white), 70 | ), 71 | ], 72 | ), 73 | ), 74 | ), 75 | Container( 76 | padding: const EdgeInsets.only( 77 | left: 10, 78 | ), 79 | color: Colors.white, 80 | child: Column( 81 | children: [ 82 | InkWell( 83 | onTap: () { 84 | Navigator.push( 85 | context, 86 | MaterialPageRoute( 87 | builder: (BuildContext context) => const WebViews( 88 | url: 'https://github.com/openemr/openemr'), 89 | ), 90 | ); 91 | }, 92 | child: const Padding( 93 | padding: EdgeInsets.only(left: 2), 94 | child: GFListTile( 95 | avatar: Icon(Icons.store), 96 | title: Text('Main Repository', 97 | style: 98 | TextStyle(fontSize: 16, color: Colors.black87)), 99 | ), 100 | ), 101 | ), 102 | InkWell( 103 | onTap: () { 104 | Navigator.push( 105 | context, 106 | MaterialPageRoute( 107 | builder: (BuildContext context) => const WebViews( 108 | url: 109 | 'https://github.com/openemr/app-flutter-openemr/blob/master/README.md'), 110 | ), 111 | ); 112 | }, 113 | child: const Padding( 114 | padding: EdgeInsets.only(left: 2), 115 | child: GFListTile( 116 | avatar: Icon(CupertinoIcons.eye_solid), 117 | title: Text('Documentation', 118 | style: 119 | TextStyle(fontSize: 16, color: Colors.black87)), 120 | ), 121 | ), 122 | ), 123 | Divider(color: GFColors.FOCUS, indent: 20, endIndent: 30), 124 | const Padding( 125 | padding: EdgeInsets.only(left: 2), 126 | child: Text("Last update: 04/08/21"), 127 | ), 128 | const Padding( 129 | padding: EdgeInsets.only(left: 2), 130 | child: Text("v2.2"), 131 | ), 132 | ], 133 | ), 134 | ), 135 | ], 136 | ), 137 | ); 138 | } 139 | -------------------------------------------------------------------------------- /lib/screens/drawer/webview.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:flutter/cupertino.dart'; 4 | import 'package:flutter/material.dart'; 5 | import 'package:getwidget/getwidget.dart'; 6 | import 'package:webview_flutter/webview_flutter.dart'; 7 | 8 | class WebViews extends StatefulWidget { 9 | const WebViews({Key key, this.url}) : super(key: key); 10 | final String url; 11 | @override 12 | _WebViewsState createState() => _WebViewsState(); 13 | } 14 | 15 | class _WebViewsState extends State { 16 | final Completer _controller = 17 | Completer(); 18 | @override 19 | Widget build(BuildContext context) => Scaffold( 20 | appBar: AppBar( 21 | backgroundColor: GFColors.DARK, 22 | title: Image.asset( 23 | 'lib/assets/icons/gflogo.png', 24 | width: 150, 25 | ), 26 | centerTitle: true, 27 | leading: InkWell( 28 | onTap: () { 29 | Navigator.pop(context); 30 | }, 31 | child: Icon( 32 | CupertinoIcons.back, 33 | color: GFColors.SUCCESS, 34 | ), 35 | ), 36 | ), 37 | body: Builder( 38 | builder: (BuildContext context) => WebView( 39 | initialUrl: widget.url, 40 | javascriptMode: JavascriptMode.unrestricted, 41 | onWebViewCreated: _controller.complete, 42 | ))); 43 | } 44 | -------------------------------------------------------------------------------- /lib/screens/home.dart: -------------------------------------------------------------------------------- 1 | import 'package:cloud_firestore/cloud_firestore.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:flutter/cupertino.dart'; 4 | import 'package:getwidget/getwidget.dart'; 5 | import 'package:openemr/models/user.dart'; 6 | import 'package:openemr/screens/codescanner/codescanner.dart'; 7 | import 'package:openemr/screens/login/login2.dart'; 8 | import 'package:openemr/screens/medicine/medicine_recognition_ML_Kit.dart'; 9 | import 'package:openemr/screens/patientList/patient_list.dart'; 10 | import 'package:openemr/screens/ppg/heartRate.dart'; 11 | import 'package:openemr/screens/telehealth/telehealth.dart'; 12 | import 'package:openemr/utils/rest_ds.dart'; 13 | import 'package:shared_preferences/shared_preferences.dart'; 14 | import '../screens/drawer/drawer.dart'; 15 | import 'login/login.dart'; 16 | import 'package:firebase_auth/firebase_auth.dart'; 17 | 18 | class HomePage extends StatefulWidget { 19 | @override 20 | _HomePageState createState() => _HomePageState(); 21 | } 22 | 23 | class _HomePageState extends State { 24 | final userRef = Firestore.instance.collection('username'); 25 | final FirebaseAuth _auth = FirebaseAuth.instance; 26 | final firebaseFlag = false; 27 | List gfComponents = [ 28 | { 29 | 'icon': CupertinoIcons.heart_solid, 30 | 'title': 'PPG', 31 | 'route': PPG(), 32 | }, 33 | { 34 | 'icon': Icons.video_call, 35 | 'title': 'Telehealth', 36 | 'authentication': "firebase", 37 | 'failRoute': LoginFirebaseScreen(), 38 | 'route': Telehealth(), 39 | }, 40 | { 41 | 'icon': Icons.people, 42 | 'title': 'Patient List', 43 | 'route': PatientListPage(), 44 | 'authentication': "webapp", 45 | 'failRoute': LoginScreen(), 46 | }, 47 | { 48 | 'icon': Icons.translate, 49 | 'title': 'Medicine Recognition', 50 | 'route': MedicineRecognitionMLKit(), 51 | }, 52 | { 53 | 'icon': Icons.scanner, 54 | 'title': 'Code scanner', 55 | 'route': CodeScanner(), 56 | }, 57 | ]; 58 | 59 | void _showSnackBar(String text) { 60 | ScaffoldMessenger.of(context) 61 | .showSnackBar(new SnackBar(content: new Text(text))); 62 | } 63 | 64 | @override 65 | Widget build(BuildContext context) => Scaffold( 66 | drawer: DrawerPage(), 67 | appBar: AppBar( 68 | backgroundColor: GFColors.DARK, 69 | title: Image.asset( 70 | 'lib/assets/icons/gflogo.png', 71 | width: 150, 72 | ), 73 | centerTitle: true, 74 | ), 75 | body: ListView( 76 | physics: const ScrollPhysics(), 77 | scrollDirection: Axis.vertical, 78 | shrinkWrap: true, 79 | children: [ 80 | Container( 81 | margin: const EdgeInsets.only( 82 | left: 15, 83 | bottom: 20, 84 | top: 20, 85 | right: 15, 86 | ), 87 | child: GridView.builder( 88 | scrollDirection: Axis.vertical, 89 | shrinkWrap: true, 90 | physics: const ScrollPhysics(), 91 | itemCount: gfComponents.length, 92 | gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( 93 | crossAxisCount: 2, 94 | crossAxisSpacing: 20, 95 | mainAxisSpacing: 20), 96 | itemBuilder: (BuildContext context, int index) => 97 | buildSquareTile( 98 | gfComponents[index]['title'], 99 | gfComponents[index]['icon'], 100 | gfComponents[index]['route'], 101 | gfComponents[index]['authentication'], 102 | gfComponents[index]['failRoute'], 103 | gfComponents[index]['disabled'] ?? false, 104 | ), 105 | ), 106 | ), 107 | ], 108 | ), 109 | ); 110 | 111 | Widget buildSquareTile( 112 | String title, 113 | IconData icon, 114 | Widget route, 115 | String auth, 116 | Widget failRoute, 117 | bool disabled, 118 | ) => 119 | InkWell( 120 | onTap: !disabled 121 | ? () async { 122 | if (auth == "webapp") { 123 | final prefs = await SharedPreferences.getInstance(); 124 | var username = prefs.getString('username'); 125 | var password = prefs.getString('password'); 126 | var url = prefs.getString('baseUrl'); 127 | RestDatasource api = new RestDatasource(); 128 | api.login(username, password, url).then((User user) { 129 | prefs.setString( 130 | 'token', user.tokenType + " " + user.accessToken); 131 | Navigator.push( 132 | context, 133 | MaterialPageRoute( 134 | builder: (BuildContext context) => route), 135 | ); 136 | }).catchError((Object error) { 137 | Navigator.push( 138 | context, 139 | MaterialPageRoute( 140 | builder: (BuildContext context) => failRoute), 141 | ); 142 | }); 143 | } else if (auth == "firebase") { 144 | if (firebaseFlag) { 145 | var user = await _auth.currentUser(); 146 | SharedPreferences prefs = 147 | await SharedPreferences.getInstance(); 148 | var loggedUserId = prefs.getString('loggedUserId'); 149 | 150 | if (user != null && user.isEmailVerified) { 151 | Navigator.push( 152 | context, 153 | MaterialPageRoute( 154 | builder: (BuildContext context) => route), 155 | ); 156 | } else if (user != null && loggedUserId != null) { 157 | DocumentSnapshot documentSnapshot = 158 | await userRef.document(loggedUserId).get(); 159 | if (documentSnapshot.exists) { 160 | Navigator.push( 161 | context, 162 | MaterialPageRoute( 163 | builder: (BuildContext context) => route), 164 | ); 165 | } else { 166 | Navigator.push( 167 | context, 168 | MaterialPageRoute( 169 | builder: (BuildContext context) => failRoute), 170 | ); 171 | } 172 | } else { 173 | Navigator.push( 174 | context, 175 | MaterialPageRoute( 176 | builder: (BuildContext context) => failRoute), 177 | ); 178 | } 179 | } else { 180 | _showSnackBar("Check readme to enable firebase"); 181 | } 182 | } else { 183 | Navigator.push( 184 | context, 185 | MaterialPageRoute(builder: (BuildContext context) => route), 186 | ); 187 | } 188 | } 189 | : null, 190 | child: Container( 191 | decoration: BoxDecoration( 192 | color: !disabled ? Color(0xFF333333) : Colors.grey[500], 193 | borderRadius: const BorderRadius.all(Radius.circular(7)), 194 | boxShadow: [ 195 | BoxShadow( 196 | color: Colors.black.withOpacity(0.61), 197 | blurRadius: 6, 198 | spreadRadius: 0, 199 | ), 200 | ], 201 | ), 202 | child: Column( 203 | mainAxisAlignment: MainAxisAlignment.spaceEvenly, 204 | children: [ 205 | Icon( 206 | icon, 207 | color: !disabled 208 | ? GFColors.SUCCESS 209 | : Colors.white.withOpacity(0.7), 210 | size: 30, 211 | ), 212 | // Icon((icon),), 213 | Text(title, 214 | style: const TextStyle(color: GFColors.WHITE, fontSize: 20), 215 | textAlign: TextAlign.center) 216 | ], 217 | ), 218 | ), 219 | ); 220 | } 221 | -------------------------------------------------------------------------------- /lib/screens/login/create_account.dart: -------------------------------------------------------------------------------- 1 | import 'package:cloud_firestore/cloud_firestore.dart'; 2 | import 'package:firebase_auth/firebase_auth.dart'; 3 | import 'package:flutter/material.dart'; 4 | import 'package:getwidget/colors/gf_color.dart'; 5 | import 'package:getwidget/components/button/gf_button.dart'; 6 | import 'package:google_sign_in/google_sign_in.dart'; 7 | import 'package:openemr/screens/telehealth/telehealth.dart'; 8 | import 'package:openemr/utils/customlistloadingshimmer.dart'; 9 | 10 | class CreateAccount extends StatefulWidget { 11 | final FirebaseUser dispUser; 12 | 13 | CreateAccount({Key key, @required this.dispUser}) : super(key: key); 14 | @override 15 | _CreateAccountBuyerState createState() => _CreateAccountBuyerState(); 16 | } 17 | 18 | class _CreateAccountBuyerState extends State { 19 | final userRef = Firestore.instance.collection('username'); 20 | final GoogleSignIn googleSignIn = GoogleSignIn(); 21 | 22 | bool _isLoading = false; 23 | final _formKey = GlobalKey(); 24 | String _userid; 25 | 26 | void _toggleLoadingStatus(bool newLoadingState) { 27 | setState(() { 28 | _isLoading = newLoadingState; 29 | }); 30 | } 31 | 32 | handleRegister(context) async { 33 | final form = _formKey.currentState; 34 | 35 | if (form.validate()) { 36 | form.save(); 37 | _toggleLoadingStatus(true); 38 | await userRef.document(widget.dispUser.uid).setData({ 39 | "id": _userid, 40 | "name": widget.dispUser.displayName, 41 | }); 42 | 43 | _toggleLoadingStatus(false); 44 | Navigator.of(context).pushReplacement( 45 | MaterialPageRoute(builder: (context) => Telehealth())); 46 | } 47 | } 48 | 49 | @override 50 | Widget build(BuildContext parentContext) { 51 | double width = MediaQuery.of(context).size.width; 52 | 53 | return Scaffold( 54 | backgroundColor: GFColors.LIGHT, 55 | body: Padding( 56 | padding: EdgeInsets.only(left: width * 0.1, right: width * 0.1), 57 | child: Center( 58 | child: SingleChildScrollView( 59 | child: Form( 60 | key: _formKey, 61 | child: Column( 62 | mainAxisAlignment: MainAxisAlignment.center, 63 | crossAxisAlignment: CrossAxisAlignment.center, 64 | children: [ 65 | SizedBox( 66 | height: 25, 67 | ), 68 | Image.asset( 69 | 'lib/assets/images/firebase.png', 70 | width: width * 0.25, 71 | ), 72 | SizedBox( 73 | height: 20, 74 | ), 75 | _isLoading 76 | ? customListLoadingShimmer(context, 77 | loadingMessage: 'Creating Account', listLength: 4) 78 | : Column( 79 | children: [ 80 | SizedBox( 81 | child: TextFormField( 82 | validator: (value) { 83 | if (value.isEmpty) { 84 | return 'Username can\'t be blank'; 85 | } 86 | return null; 87 | }, 88 | onSaved: (val) => _userid = val, 89 | decoration: InputDecoration( 90 | border: OutlineInputBorder(), 91 | labelText: 'Username'), 92 | ), 93 | ), 94 | SizedBox( 95 | height: 20, 96 | ), 97 | GFButton( 98 | onPressed: () => handleRegister(context), 99 | text: 'Register', 100 | color: GFColors.DARK, 101 | ), 102 | ], 103 | ), 104 | ], 105 | ), 106 | ), 107 | ), 108 | ), 109 | )); 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /lib/screens/login/login.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:getwidget/getwidget.dart'; 4 | import 'package:openemr/utils/customlistloadingshimmer.dart'; 5 | import 'package:openemr/utils/rest_ds.dart'; 6 | import 'package:shared_preferences/shared_preferences.dart'; 7 | import '../../models/user.dart'; 8 | 9 | class LoginScreen extends StatefulWidget { 10 | @override 11 | _LoginScreenState createState() => _LoginScreenState(); 12 | } 13 | 14 | class _LoginScreenState extends State { 15 | User user; 16 | bool _isLoading = false; 17 | 18 | final formKey = new GlobalKey(); 19 | String _username, _password, _url; 20 | 21 | void _showSnackBar(String text) { 22 | ScaffoldMessenger.of(context) 23 | .showSnackBar(new SnackBar(content: new Text(text))); 24 | } 25 | 26 | void _toggleLoadingStatus(bool newLoadingState) { 27 | setState(() => _isLoading = newLoadingState); 28 | } 29 | 30 | @override 31 | Widget build(BuildContext context) { 32 | double width = MediaQuery.of(context).size.width; 33 | return GestureDetector( 34 | onTap: () { 35 | FocusScopeNode currentFocus = FocusScope.of(context); 36 | if (!currentFocus.hasPrimaryFocus) { 37 | currentFocus.unfocus(); 38 | } 39 | }, 40 | child: Scaffold( 41 | backgroundColor: GFColors.LIGHT, 42 | body: Padding( 43 | padding: EdgeInsets.only(left: width * 0.1, right: width * 0.1), 44 | child: Center( 45 | child: SingleChildScrollView( 46 | child: _isLoading 47 | ? customListLoadingShimmer( 48 | context, 49 | listLength: 3, 50 | loadingMessage: 'Authenticating...', 51 | ) 52 | : Form( 53 | key: formKey, 54 | child: Column( 55 | mainAxisAlignment: MainAxisAlignment.center, 56 | crossAxisAlignment: CrossAxisAlignment.center, 57 | children: [ 58 | SizedBox( 59 | height: 25, 60 | ), 61 | Image.asset( 62 | 'lib/assets/images/gflogo.png', 63 | width: width * 0.25, 64 | ), 65 | SizedBox( 66 | height: 20, 67 | ), 68 | SizedBox( 69 | child: TextFormField( 70 | validator: (value) { 71 | if (value.isEmpty) { 72 | return 'Please enter username'; 73 | } 74 | return null; 75 | }, 76 | onSaved: (val) => _username = val, 77 | decoration: InputDecoration( 78 | border: OutlineInputBorder(), 79 | labelText: 'Username'), 80 | ), 81 | ), 82 | SizedBox( 83 | height: 20, 84 | ), 85 | SizedBox( 86 | child: TextFormField( 87 | validator: (value) { 88 | if (value.isEmpty) { 89 | return 'Please enter password'; 90 | } 91 | return null; 92 | }, 93 | onSaved: (val) => _password = val, 94 | obscureText: true, 95 | decoration: InputDecoration( 96 | border: OutlineInputBorder(), 97 | labelText: 'Password'), 98 | ), 99 | ), 100 | SizedBox( 101 | height: 20, 102 | ), 103 | SizedBox( 104 | child: TextFormField( 105 | validator: (value) { 106 | if (value.isEmpty) { 107 | return 'Please enter url'; 108 | } 109 | return null; 110 | }, 111 | onSaved: (val) => _url = val, 112 | decoration: InputDecoration( 113 | border: OutlineInputBorder(), 114 | labelText: 'URL', 115 | hintText: "http://example.com"), 116 | ), 117 | ), 118 | SizedBox( 119 | height: 20, 120 | ), 121 | GFButton( 122 | onPressed: () => submit(context), 123 | text: 'login', 124 | color: GFColors.DARK, 125 | ), 126 | ], 127 | ), 128 | ), 129 | ), 130 | ), 131 | )), 132 | ); 133 | } 134 | 135 | void submit(context) async { 136 | final prefs = await SharedPreferences.getInstance(); 137 | final form = formKey.currentState; 138 | RestDatasource api = new RestDatasource(); 139 | if (form.validate()) { 140 | form.save(); 141 | _toggleLoadingStatus(true); 142 | api.login(_username.trim(), _password.trim(), _url).then((User user) { 143 | prefs.setString('token', user.tokenType + " " + user.accessToken); 144 | prefs.setString('username', user.username); 145 | prefs.setString('password', user.password); 146 | prefs.setString('baseUrl', user.baseUrl); 147 | _toggleLoadingStatus(false); 148 | Navigator.pop(context); 149 | }).catchError((Object error) { 150 | _toggleLoadingStatus(false); 151 | _showSnackBar(error.toString()); 152 | }); 153 | } 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /lib/screens/login/login2.dart: -------------------------------------------------------------------------------- 1 | import 'package:cloud_firestore/cloud_firestore.dart'; 2 | import 'package:flutter/cupertino.dart'; 3 | import 'package:flutter/material.dart'; 4 | import 'package:flutter_auth_buttons/flutter_auth_buttons.dart'; 5 | import 'package:getwidget/getwidget.dart'; 6 | import 'package:google_sign_in/google_sign_in.dart'; 7 | import 'package:openemr/screens/login/create_account.dart'; 8 | import 'package:openemr/screens/register/register.dart'; 9 | import 'package:openemr/screens/telehealth/telehealth.dart'; 10 | import 'package:openemr/utils/customlistloadingshimmer.dart'; 11 | import 'package:shared_preferences/shared_preferences.dart'; 12 | import '../../models/user.dart'; 13 | import 'package:firebase_auth/firebase_auth.dart'; 14 | 15 | class LoginFirebaseScreen extends StatefulWidget { 16 | final String snackBarMessage; 17 | 18 | LoginFirebaseScreen({this.snackBarMessage}); 19 | 20 | @override 21 | _LoginFirebaseScreenState createState() => _LoginFirebaseScreenState(); 22 | } 23 | 24 | class _LoginFirebaseScreenState extends State { 25 | final FirebaseAuth _auth = FirebaseAuth.instance; 26 | final GoogleSignIn googleSignIn = GoogleSignIn(); 27 | final userRef = Firestore.instance.collection('username'); 28 | User currentUserWithInfo; 29 | User user; 30 | bool _isLoading = false; 31 | 32 | final formKey = new GlobalKey(); 33 | String _email, _password; 34 | 35 | @override 36 | void initState() { 37 | Future.delayed(Duration.zero, () { 38 | if (widget.snackBarMessage != null) { 39 | _showSnackBar(widget.snackBarMessage); 40 | } 41 | }); 42 | super.initState(); 43 | } 44 | 45 | void _showSnackBar(String text) { 46 | ScaffoldMessenger.of(context) 47 | .showSnackBar(new SnackBar(content: new Text(text))); 48 | } 49 | 50 | void _toggleLoadingStatus(bool newLoadingState) { 51 | setState(() { 52 | _isLoading = newLoadingState; 53 | }); 54 | } 55 | 56 | @override 57 | Widget build(BuildContext context) { 58 | double width = MediaQuery.of(context).size.width; 59 | return GestureDetector( 60 | onTap: () { 61 | FocusScopeNode currentFocus = FocusScope.of(context); 62 | if (!currentFocus.hasPrimaryFocus) { 63 | currentFocus.unfocus(); 64 | } 65 | }, 66 | child: Scaffold( 67 | backgroundColor: GFColors.LIGHT, 68 | body: Padding( 69 | padding: EdgeInsets.only(left: width * 0.1, right: width * 0.1), 70 | child: Center( 71 | child: SingleChildScrollView( 72 | child: Form( 73 | key: formKey, 74 | child: Column( 75 | mainAxisAlignment: MainAxisAlignment.center, 76 | crossAxisAlignment: CrossAxisAlignment.center, 77 | children: [ 78 | SizedBox( 79 | height: 25, 80 | ), 81 | Image.asset( 82 | 'lib/assets/images/firebase.png', 83 | width: width * 0.25, 84 | ), 85 | SizedBox( 86 | height: 20, 87 | ), 88 | _isLoading 89 | ? customListLoadingShimmer(context, 90 | loadingMessage: 'Authenticating', listLength: 2) 91 | : Column( 92 | children: [ 93 | SizedBox( 94 | child: TextFormField( 95 | validator: (value) { 96 | if (value.isEmpty) { 97 | return 'Please enter your email'; 98 | } 99 | return null; 100 | }, 101 | onSaved: (val) => _email = val, 102 | decoration: InputDecoration( 103 | border: OutlineInputBorder(), 104 | labelText: 'E-mail'), 105 | ), 106 | ), 107 | SizedBox( 108 | height: 20, 109 | ), 110 | SizedBox( 111 | child: TextFormField( 112 | validator: (value) { 113 | if (value.isEmpty) { 114 | return 'Please enter password'; 115 | } 116 | return null; 117 | }, 118 | onSaved: (val) => _password = val, 119 | obscureText: true, 120 | decoration: InputDecoration( 121 | border: OutlineInputBorder(), 122 | labelText: 'Password'), 123 | ), 124 | ), 125 | SizedBox( 126 | height: 20, 127 | ), 128 | GFButton( 129 | onPressed: () => handleSignIn(context), 130 | text: 'login', 131 | color: GFColors.DARK, 132 | ), 133 | GFButton( 134 | onPressed: () => handleRegister(context), 135 | text: 'Register', 136 | color: GFColors.DARK, 137 | type: GFButtonType.outline2x, 138 | ), 139 | Padding( 140 | padding: EdgeInsets.only(top: 25, bottom: 25), 141 | child: Text( 142 | "-------OR--------", 143 | style: TextStyle( 144 | fontSize: 14, 145 | fontWeight: FontWeight.w300, 146 | color: Colors.grey), 147 | ), 148 | ), 149 | GoogleSignInButton( 150 | onPressed: () { 151 | _toggleLoadingStatus(true); 152 | signInWithGoogle(); 153 | }, 154 | textStyle: TextStyle( 155 | fontSize: 14.0, 156 | fontWeight: FontWeight.w400, 157 | color: Colors.white, 158 | ), 159 | darkMode: true, 160 | ), 161 | SizedBox( 162 | height: 25, 163 | ), 164 | ], 165 | ) 166 | ], 167 | ), 168 | ), 169 | ), 170 | ), 171 | )), 172 | ); 173 | } 174 | 175 | void handleSignIn(context) async { 176 | FirebaseUser user; 177 | final form = formKey.currentState; 178 | _toggleLoadingStatus(true); 179 | if (form.validate()) { 180 | form.save(); 181 | try { 182 | AuthResult result = await _auth.signInWithEmailAndPassword( 183 | email: _email, password: _password); 184 | user = result.user; 185 | } catch (error) { 186 | if (error.message != null) { 187 | _showSnackBar(error.message); 188 | } else { 189 | _showSnackBar('An unexpected error occured!'); 190 | } 191 | _toggleLoadingStatus(false); 192 | return null; 193 | } 194 | } 195 | _toggleLoadingStatus(false); 196 | if (!user.isEmailVerified) { 197 | _showSnackBar("Email not verified"); 198 | await _auth.signOut(); 199 | } else { 200 | Navigator.pop(context); 201 | } 202 | } 203 | 204 | void handleRegister(context) async { 205 | Navigator.pushReplacement( 206 | context, 207 | MaterialPageRoute( 208 | builder: (BuildContext context) => RegisterFirebaseScreen()), 209 | ); 210 | } 211 | 212 | Future signInWithGoogle() async { 213 | final GoogleSignInAccount googleSignInAccount = await googleSignIn.signIn(); 214 | handleGSignIn(googleSignInAccount); 215 | } 216 | 217 | handleGSignIn(GoogleSignInAccount googleSignInAccount) async { 218 | if (googleSignInAccount != null) { 219 | final GoogleSignInAuthentication googleSignInAuthentication = 220 | await googleSignInAccount.authentication; 221 | 222 | final AuthCredential credential = GoogleAuthProvider.getCredential( 223 | accessToken: googleSignInAuthentication.accessToken, 224 | idToken: googleSignInAuthentication.idToken, 225 | ); 226 | 227 | final AuthResult authResult = 228 | await _auth.signInWithCredential(credential); 229 | final FirebaseUser user = authResult.user; 230 | 231 | try { 232 | await createUserInFirestore(user); 233 | } catch (err) { 234 | _showSnackBar(err); 235 | _toggleLoadingStatus(false); 236 | _signOut(); 237 | } 238 | 239 | shredprefUser(user.uid); 240 | 241 | _toggleLoadingStatus(false); 242 | } else { 243 | _showSnackBar('Please try again'); 244 | _toggleLoadingStatus(false); 245 | } 246 | } 247 | 248 | Future shredprefUser(String uid) async { 249 | SharedPreferences prefs = await SharedPreferences.getInstance(); 250 | prefs.setString('loggedUserId', uid); 251 | } 252 | 253 | createUserInFirestore(FirebaseUser user) async { 254 | DocumentSnapshot documentSnapshot = await userRef.document(user.uid).get(); 255 | //go to createAccount page - only for first reigstration 256 | if (!documentSnapshot.exists) { 257 | _toggleLoadingStatus(false); 258 | Navigator.of(context).pushReplacement(MaterialPageRoute( 259 | builder: (context) => CreateAccount( 260 | dispUser: user, 261 | ))); 262 | } else { 263 | _toggleLoadingStatus(false); 264 | Navigator.of(context).pushReplacement( 265 | MaterialPageRoute(builder: (context) => Telehealth())); 266 | } 267 | } 268 | 269 | Future _signOut() async { 270 | await googleSignIn.signOut(); 271 | await _auth.signOut(); 272 | 273 | SharedPreferences prefs = await SharedPreferences.getInstance(); 274 | prefs.remove('loggedUserId'); 275 | _toggleLoadingStatus(false); 276 | } 277 | } 278 | -------------------------------------------------------------------------------- /lib/screens/medicine/medicine_recognition_ML_Kit.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | import 'dart:io'; 3 | import 'package:http/http.dart' as http; 4 | 5 | import 'package:firebase_ml_vision/firebase_ml_vision.dart'; 6 | import 'package:flutter/cupertino.dart'; 7 | import 'package:flutter/material.dart'; 8 | import 'package:getwidget/colors/gf_color.dart'; 9 | import 'package:getwidget/components/appbar/gf_appbar.dart'; 10 | import 'package:image_picker/image_picker.dart'; 11 | 12 | class MedicineRecognitionMLKit extends StatefulWidget { 13 | @override 14 | _MedicineRecognitionMLKitState createState() => 15 | _MedicineRecognitionMLKitState(); 16 | } 17 | 18 | class _MedicineRecognitionMLKitState extends State { 19 | var _imageText = []; 20 | var _drugList = []; 21 | File image; 22 | ImagePicker imagePicker; 23 | 24 | String baseURL = "https://dailymed.nlm.nih.gov/dailymed/services"; 25 | String version = "v2"; 26 | String endpoint = "drugnames"; 27 | String format = "json"; 28 | String parameter_1 = "drug_name"; 29 | 30 | _callAPI(String drugName) async { 31 | try { 32 | final response = await http 33 | .get('$baseURL/$version/$endpoint\.$format?$parameter_1=$drugName'); 34 | print(response); 35 | if (response.statusCode == 200) { 36 | var data = json.decode(response.body); 37 | if (data.isNotEmpty) { 38 | int drugCount = data["metadata"]["total_elements"]; 39 | if (drugCount > 0) { 40 | setState(() { 41 | _drugList.add(drugName); 42 | }); 43 | } 44 | } 45 | } 46 | } on Exception catch (_) { 47 | setState(() { 48 | _drugList.clear(); 49 | _drugList.add("No data available"); 50 | }); 51 | } 52 | } 53 | 54 | captureFromCamera() async { 55 | setState(() { 56 | image = null; 57 | _imageText.clear(); 58 | _drugList.clear(); 59 | }); 60 | try { 61 | PickedFile pickedFile = 62 | await imagePicker.getImage(source: ImageSource.camera); 63 | File imageNew = File(pickedFile.path); 64 | setState(() { 65 | image = imageNew; 66 | convertImageToText(); 67 | }); 68 | } on Exception catch (e) { 69 | //print e to snackbar 70 | print(e); 71 | } 72 | } 73 | 74 | chooseFromGalery() async { 75 | setState(() { 76 | image = null; 77 | _imageText.clear(); 78 | _drugList.clear(); 79 | }); 80 | try { 81 | PickedFile pickedFile = 82 | await imagePicker.getImage(source: ImageSource.gallery); 83 | File imageNew = File(pickedFile.path); 84 | setState(() { 85 | image = imageNew; 86 | convertImageToText(); 87 | }); 88 | } on Exception catch (e) { 89 | print(e); 90 | } 91 | } 92 | 93 | convertImageToText() async { 94 | final FirebaseVisionImage firebaseVisionImage = 95 | FirebaseVisionImage.fromFile(image); 96 | final TextRecognizer textRecognizer = 97 | FirebaseVision.instance.textRecognizer(); 98 | VisionText visionText = 99 | await textRecognizer.processImage(firebaseVisionImage); 100 | 101 | setState(() { 102 | for (TextBlock textBlock in visionText.blocks) { 103 | for (TextLine textLine in textBlock.lines) { 104 | for (TextElement textElement in textLine.elements) { 105 | //remove all special symbols from string 106 | _imageText 107 | .add(textElement.text.replaceAll(new RegExp(r'[^\w\s]+'), '')); 108 | } 109 | } 110 | } 111 | //remove duplicates 112 | _imageText = _imageText.toSet().toList(); 113 | for (String item in _imageText) { 114 | _callAPI(item); 115 | } 116 | }); 117 | } 118 | 119 | @override 120 | void initState() { 121 | super.initState(); 122 | 123 | imagePicker = ImagePicker(); 124 | } 125 | 126 | @override 127 | Widget build(BuildContext context) { 128 | return Scaffold( 129 | appBar: GFAppBar( 130 | backgroundColor: GFColors.DARK, 131 | leading: InkWell( 132 | onTap: () { 133 | Navigator.pop(context); 134 | }, 135 | child: Container( 136 | child: Icon( 137 | CupertinoIcons.back, 138 | color: GFColors.SUCCESS, 139 | ), 140 | )), 141 | title: const Text( 142 | 'Medicine Recognition', 143 | style: TextStyle(fontSize: 17), 144 | ), 145 | centerTitle: true, 146 | ), 147 | body: Center( 148 | child: SingleChildScrollView( 149 | child: Padding( 150 | padding: EdgeInsets.all(40.0), 151 | child: (image == null) 152 | ? Icon( 153 | Icons.search, 154 | size: 150, 155 | color: Colors.grey.withOpacity(0.44), 156 | ) 157 | : Column( 158 | children: [ 159 | Text( 160 | "Captured Image", 161 | style: TextStyle( 162 | fontSize: 20, 163 | fontWeight: FontWeight.w400, 164 | color: Colors.purple), 165 | ), 166 | SizedBox(height: 10.0), 167 | Image.file( 168 | image, 169 | width: 140, 170 | height: 192, 171 | fit: BoxFit.fill, 172 | ), 173 | SizedBox(height: 20.0), 174 | 175 | Text( 176 | "All words found in the image", 177 | style: TextStyle( 178 | fontSize: 20, 179 | fontWeight: FontWeight.w400, 180 | color: Colors.purple), 181 | ), 182 | SizedBox(height: 10.0), 183 | Text(_imageText.toString()), 184 | SizedBox(height: 20.0), 185 | Text( 186 | "Medicine words", 187 | style: TextStyle( 188 | fontSize: 20, 189 | fontWeight: FontWeight.w400, 190 | color: Colors.purple), 191 | ), 192 | SizedBox(height: 10.0), 193 | Text(_drugList.toString()), 194 | // (_drugs.isNotEmpty) ? Text(_drugs) : Text("No data available"), 195 | ], 196 | ), 197 | ), 198 | ), 199 | ), 200 | floatingActionButtonLocation: FloatingActionButtonLocation.centerDocked, 201 | floatingActionButton: Padding( 202 | padding: const EdgeInsets.all(8.0), 203 | child: Row( 204 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 205 | children: [ 206 | FloatingActionButton( 207 | heroTag: null, 208 | backgroundColor: GFColors.DARK, 209 | onPressed: () async { 210 | captureFromCamera(); 211 | }, 212 | child: Icon(Icons.camera), 213 | ), 214 | FloatingActionButton( 215 | heroTag: null, 216 | backgroundColor: GFColors.DARK, 217 | onPressed: () async { 218 | chooseFromGalery(); 219 | }, 220 | child: Icon(Icons.file_upload), 221 | ) 222 | ], 223 | ), 224 | )); 225 | } 226 | } 227 | -------------------------------------------------------------------------------- /lib/screens/patientList/patient_list.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:getwidget/getwidget.dart'; 3 | import 'package:flutter/cupertino.dart'; 4 | import 'package:openemr/models/patient.dart'; 5 | import 'package:openemr/screens/addpatient/add_patient.dart'; 6 | import 'package:openemr/utils/rest_ds.dart'; 7 | import 'package:shared_preferences/shared_preferences.dart'; 8 | 9 | class PatientListPage extends StatefulWidget { 10 | @override 11 | _PatientListPageState createState() => _PatientListPageState(); 12 | } 13 | 14 | class _PatientListPageState extends State { 15 | @override 16 | void initState() { 17 | fetchList(); 18 | super.initState(); 19 | } 20 | 21 | fetchList() async { 22 | historyPatientId = []; 23 | starredPatientId = []; 24 | 25 | patientList = []; 26 | historyPatientList = []; 27 | starredPatientList = []; 28 | 29 | RestDatasource api = new RestDatasource(); 30 | final prefs = await SharedPreferences.getInstance(); 31 | historyPatientId = prefs.getStringList("historyPatient") == null 32 | ? [] 33 | : prefs.getStringList("historyPatient"); 34 | starredPatientId = prefs.getStringList("starredPatient") == null 35 | ? [] 36 | : prefs.getStringList("starredPatient"); 37 | api 38 | .getPatientList(prefs.getString('baseUrl'), prefs.getString('token')) 39 | .then((List list) { 40 | list.forEach((element) { 41 | if (historyPatientId.indexOf(element.pid) != -1) { 42 | historyPatientList.add(element); 43 | } 44 | if (starredPatientId.indexOf(element.pid) != -1) { 45 | starredPatientList.add(element); 46 | } 47 | }); 48 | setState(() { 49 | starredPatientList = starredPatientList; 50 | historyPatientList = historyPatientList; 51 | patientList = list; 52 | }); 53 | }).catchError((Object error) => print(error.toString())); 54 | } 55 | 56 | List historyPatientId = []; 57 | List starredPatientId = []; 58 | 59 | List patientList = []; 60 | List historyPatientList = []; 61 | List starredPatientList = []; 62 | 63 | @override 64 | Widget build(BuildContext context) => Scaffold( 65 | appBar: GFAppBar( 66 | backgroundColor: GFColors.DARK, 67 | leading: InkWell( 68 | onTap: () { 69 | Navigator.pop(context); 70 | }, 71 | child: Container( 72 | child: Icon( 73 | CupertinoIcons.back, 74 | color: GFColors.SUCCESS, 75 | ), 76 | )), 77 | title: const Text( 78 | 'Patient List', 79 | style: TextStyle(fontSize: 17), 80 | ), 81 | centerTitle: true, 82 | actions: [ 83 | IconButton( 84 | icon: Icon(Icons.refresh), 85 | tooltip: "Refresh", 86 | color: GFColors.SUCCESS, 87 | onPressed: fetchList, 88 | ), 89 | IconButton( 90 | icon: Icon(Icons.add), 91 | tooltip: "Add Patient", 92 | color: GFColors.SUCCESS, 93 | onPressed: () { 94 | Navigator.push( 95 | context, 96 | MaterialPageRoute( 97 | builder: (BuildContext context) => AddPatientScreen()), 98 | ); 99 | }, 100 | ), 101 | IconButton( 102 | icon: Icon(Icons.exit_to_app), 103 | tooltip: "Logout", 104 | color: GFColors.DANGER, 105 | onPressed: () async { 106 | final prefs = await SharedPreferences.getInstance(); 107 | prefs.remove('token'); 108 | prefs.remove('username'); 109 | prefs.remove('password'); 110 | prefs.remove('baseUrl'); 111 | Navigator.pushNamedAndRemoveUntil(context, '/', (_) => false); 112 | }, 113 | ) 114 | ], 115 | ), 116 | body: ListView( 117 | physics: const ScrollPhysics(), 118 | children: [ 119 | GFSearchBar( 120 | searchList: patientList, 121 | searchQueryBuilder: (query, list) => list 122 | .where((item) => 123 | item.fname.toLowerCase().contains(query.toLowerCase())) 124 | .toList(), 125 | overlaySearchListItemBuilder: (item) => Container( 126 | padding: const EdgeInsets.all(8), 127 | child: Row( 128 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 129 | children: [ 130 | Text( 131 | item.fname, 132 | style: const TextStyle(fontSize: 18), 133 | ) 134 | ], 135 | ), 136 | ), 137 | onItemSelected: (item) async { 138 | if (item != null) { 139 | final prefs = await SharedPreferences.getInstance(); 140 | historyPatientId.remove(item.pid.toString()); 141 | historyPatientId.insert(0, item.pid.toString()); 142 | historyPatientList.remove(item); 143 | historyPatientList.insert(0, item); 144 | prefs.setStringList("historyPatient", historyPatientId); 145 | if (historyPatientList.length > 10) { 146 | historyPatientList.removeLast(); 147 | } 148 | } 149 | this.setState(() { 150 | historyPatientList = historyPatientList; 151 | }); 152 | }), 153 | const Padding( 154 | padding: EdgeInsets.only(left: 15, top: 30, bottom: 10), 155 | child: GFTypography( 156 | text: 'Starred Patient', 157 | type: GFTypographyType.typo5, 158 | dividerWidth: 25, 159 | dividerColor: Color(0xFF19CA4B), 160 | ), 161 | ), 162 | ListView.builder( 163 | shrinkWrap: true, 164 | physics: ClampingScrollPhysics(), 165 | itemCount: starredPatientList.length, 166 | itemBuilder: (item, i) { 167 | Patient p = starredPatientList[i]; 168 | return GFListTile( 169 | titleText: p.fname + " " + p.lname, 170 | subtitleText: p.sex, 171 | icon: GFIconButton( 172 | onPressed: () async { 173 | final prefs = await SharedPreferences.getInstance(); 174 | starredPatientId.remove(p.pid.toString()); 175 | starredPatientList.remove(p); 176 | prefs.setStringList("starredPatient", starredPatientId); 177 | this.setState(() { 178 | starredPatientList = starredPatientList; 179 | }); 180 | }, 181 | icon: Icon( 182 | Icons.star, 183 | color: GFColors.DANGER, 184 | ), 185 | type: GFButtonType.transparent, 186 | ), 187 | ); 188 | }, 189 | ), 190 | const Padding( 191 | padding: EdgeInsets.only(left: 15, top: 30, bottom: 10), 192 | child: GFTypography( 193 | text: 'History', 194 | type: GFTypographyType.typo5, 195 | dividerWidth: 25, 196 | dividerColor: Color(0xFF19CA4B), 197 | ), 198 | ), 199 | ListView.builder( 200 | shrinkWrap: true, 201 | physics: ClampingScrollPhysics(), 202 | itemCount: historyPatientList.length, 203 | itemBuilder: (item, i) { 204 | Patient p = historyPatientList[i]; 205 | return GFListTile( 206 | titleText: (p.fname != null ? p.fname + " " : "") + 207 | (p.mname != null ? p.mname + " " : "") + 208 | (p.lname != null ? p.lname + " " : ""), 209 | subtitleText: p.sex, 210 | icon: GFIconButton( 211 | onPressed: () async { 212 | final prefs = await SharedPreferences.getInstance(); 213 | starredPatientId.insert(0, p.pid.toString()); 214 | starredPatientList.insert(0, p); 215 | prefs.setStringList("starredPatient", starredPatientId); 216 | this.setState(() { 217 | starredPatientList = starredPatientList; 218 | }); 219 | }, 220 | icon: Icon( 221 | Icons.star_border, 222 | color: GFColors.DANGER, 223 | ), 224 | type: GFButtonType.transparent, 225 | ), 226 | ); 227 | }, 228 | ), 229 | ], 230 | ), 231 | ); 232 | } 233 | -------------------------------------------------------------------------------- /lib/screens/ppg/chart.dart: -------------------------------------------------------------------------------- 1 | import 'package:charts_flutter/flutter.dart' as charts; 2 | import 'package:flutter/material.dart'; 3 | 4 | class Chart extends StatelessWidget { 5 | final List _data; 6 | 7 | Chart(this._data); 8 | 9 | @override 10 | Widget build(BuildContext context) { 11 | return new charts.TimeSeriesChart([ 12 | charts.Series( 13 | id: 'Values', 14 | colorFn: (_, __) => charts.MaterialPalette.green.shadeDefault, 15 | domainFn: (SensorValue values, _) => values.time, 16 | measureFn: (SensorValue values, _) => values.value, 17 | data: _data, 18 | ) 19 | ], 20 | animate: false, 21 | primaryMeasureAxis: charts.NumericAxisSpec( 22 | tickProviderSpec: 23 | charts.BasicNumericTickProviderSpec(zeroBound: false), 24 | renderSpec: charts.NoneRenderSpec(), 25 | ), 26 | domainAxis: new charts.DateTimeAxisSpec( 27 | renderSpec: new charts.NoneRenderSpec())); 28 | } 29 | } 30 | 31 | class SensorValue { 32 | final DateTime time; 33 | final double value; 34 | 35 | SensorValue(this.time, this.value); 36 | } -------------------------------------------------------------------------------- /lib/screens/ppg/heartRate.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:camera/camera.dart'; 4 | import 'package:flutter/material.dart'; 5 | import 'package:wakelock/wakelock.dart'; 6 | import 'chart.dart'; 7 | import 'package:getwidget/getwidget.dart'; 8 | import 'package:flutter/cupertino.dart'; 9 | 10 | class PPG extends StatefulWidget { 11 | @override 12 | PPGView createState() { 13 | return PPGView(); 14 | } 15 | } 16 | 17 | class PPGView extends State with SingleTickerProviderStateMixin { 18 | bool _toggled = false; // toggle button value 19 | List _data = 20 | List.empty(growable: true); // array to store the values 21 | CameraController _controller; 22 | double _alpha = 0.3; // factor for the mean value 23 | AnimationController _animationController; 24 | double _iconScale = 1; 25 | int _bpm = 0; // beats per minute 26 | int _fs = 30; // sampling frequency (fps) 27 | int _windowLen = 30 * 6; // window length to display - 6 seconds 28 | CameraImage _image; // store the last camera image 29 | double _avg; // store the average value during calculation 30 | DateTime _now; // store the now Datetime 31 | Timer _timer; // timer for image processing 32 | 33 | @override 34 | void initState() { 35 | super.initState(); 36 | _animationController = 37 | AnimationController(duration: Duration(milliseconds: 500), vsync: this); 38 | _animationController 39 | ..addListener(() { 40 | setState(() { 41 | _iconScale = 1.0 + _animationController.value * 0.4; 42 | }); 43 | }); 44 | } 45 | 46 | @override 47 | void dispose() { 48 | _timer?.cancel(); 49 | _toggled = false; 50 | _disposeController(); 51 | Wakelock.disable(); 52 | _animationController?.stop(); 53 | _animationController?.dispose(); 54 | super.dispose(); 55 | } 56 | 57 | @override 58 | Widget build(BuildContext context) { 59 | return Scaffold( 60 | backgroundColor: Colors.white, 61 | appBar: GFAppBar( 62 | backgroundColor: GFColors.DARK, 63 | leading: InkWell( 64 | onTap: () { 65 | Navigator.pop(context); 66 | }, 67 | child: Container( 68 | child: Icon( 69 | CupertinoIcons.back, 70 | color: GFColors.SUCCESS, 71 | ), 72 | )), 73 | title: const Text( 74 | 'Heart-rate Monitor', 75 | style: TextStyle(fontSize: 17), 76 | ), 77 | centerTitle: true, 78 | ), 79 | body: SafeArea( 80 | child: Column( 81 | children: [ 82 | Expanded( 83 | flex: 1, 84 | child: Row( 85 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 86 | children: [ 87 | Expanded( 88 | flex: 1, 89 | child: Padding( 90 | padding: EdgeInsets.all(12), 91 | child: ClipRRect( 92 | borderRadius: BorderRadius.all( 93 | Radius.circular(18), 94 | ), 95 | child: Stack( 96 | fit: StackFit.expand, 97 | alignment: Alignment.center, 98 | children: [ 99 | _controller != null && _toggled 100 | ? AspectRatio( 101 | aspectRatio: 102 | _controller.value.aspectRatio, 103 | child: CameraPreview(_controller), 104 | ) 105 | : Container( 106 | padding: EdgeInsets.all(12), 107 | alignment: Alignment.center, 108 | color: Colors.grey, 109 | ), 110 | Container( 111 | alignment: Alignment.center, 112 | padding: EdgeInsets.all(4), 113 | child: Text( 114 | _toggled 115 | ? "Cover both the camera and the flash with your finger" 116 | : "Camera feed will display here", 117 | style: TextStyle( 118 | backgroundColor: _toggled 119 | ? Colors.white 120 | : Colors.transparent), 121 | textAlign: TextAlign.center, 122 | ), 123 | ) 124 | ], 125 | ), 126 | ), 127 | ), 128 | ), 129 | Expanded( 130 | flex: 1, 131 | child: Center( 132 | child: Column( 133 | mainAxisSize: MainAxisSize.min, 134 | crossAxisAlignment: CrossAxisAlignment.center, 135 | children: [ 136 | Text( 137 | "Estimated BPM", 138 | style: TextStyle(fontSize: 18, color: Colors.grey), 139 | ), 140 | Text( 141 | (_bpm > 30 && _bpm < 150 ? _bpm.toString() : "--"), 142 | style: TextStyle( 143 | fontSize: 32, fontWeight: FontWeight.bold), 144 | ), 145 | ], 146 | )), 147 | ), 148 | ], 149 | )), 150 | Expanded( 151 | flex: 1, 152 | child: Center( 153 | child: Transform.scale( 154 | scale: _iconScale, 155 | child: IconButton( 156 | icon: 157 | Icon(_toggled ? Icons.favorite : Icons.favorite_border), 158 | color: Colors.red, 159 | iconSize: 128, 160 | onPressed: () { 161 | if (_toggled) { 162 | _untoggle(); 163 | } else { 164 | _toggle(); 165 | } 166 | }, 167 | ), 168 | ), 169 | ), 170 | ), 171 | Expanded( 172 | flex: 1, 173 | child: Container( 174 | margin: EdgeInsets.all(12), 175 | decoration: BoxDecoration( 176 | borderRadius: BorderRadius.all( 177 | Radius.circular(18), 178 | ), 179 | color: Colors.black), 180 | child: Chart(_data), 181 | ), 182 | ), 183 | ], 184 | ), 185 | ), 186 | ); 187 | } 188 | 189 | void _clearData() { 190 | // create array of 128 ~= 255/2 191 | _data.clear(); 192 | int now = DateTime.now().millisecondsSinceEpoch; 193 | for (int i = 0; i < _windowLen; i++) 194 | _data.insert( 195 | 0, 196 | SensorValue( 197 | DateTime.fromMillisecondsSinceEpoch(now - i * 1000 ~/ _fs), 128)); 198 | } 199 | 200 | void _toggle() { 201 | _clearData(); 202 | _initController().then((onValue) { 203 | Wakelock.enable(); 204 | _animationController?.repeat(reverse: true); 205 | setState(() { 206 | _toggled = true; 207 | }); 208 | // after is toggled 209 | _initTimer(); 210 | _updateBPM(); 211 | }); 212 | } 213 | 214 | void _untoggle() { 215 | _disposeController(); 216 | Wakelock.disable(); 217 | _animationController?.stop(); 218 | _animationController?.value = 0.0; 219 | setState(() { 220 | _toggled = false; 221 | }); 222 | } 223 | 224 | void _disposeController() { 225 | _controller?.dispose(); 226 | _controller = null; 227 | } 228 | 229 | Future _initController() async { 230 | try { 231 | List _cameras = await availableCameras(); 232 | _controller = CameraController(_cameras.first, ResolutionPreset.low); 233 | await _controller.initialize(); 234 | Future.delayed(Duration(milliseconds: 100)).then((onValue) { 235 | _controller.setFlashMode(FlashMode.torch); 236 | }); 237 | _controller.startImageStream((CameraImage image) { 238 | _image = image; 239 | }); 240 | } catch (Exception) { 241 | debugPrint(Exception); 242 | } 243 | } 244 | 245 | void _initTimer() { 246 | _timer = Timer.periodic(Duration(milliseconds: 1000 ~/ _fs), (timer) { 247 | if (_toggled) { 248 | if (_image != null) _scanImage(_image); 249 | } else { 250 | timer.cancel(); 251 | } 252 | }); 253 | } 254 | 255 | void _scanImage(CameraImage image) { 256 | _now = DateTime.now(); 257 | _avg = 258 | image.planes.first.bytes.reduce((value, element) => value + element) / 259 | image.planes.first.bytes.length; 260 | if (_data.length >= _windowLen) { 261 | _data.removeAt(0); 262 | } 263 | setState(() { 264 | _data.add(SensorValue(_now, _avg)); 265 | }); 266 | } 267 | 268 | void _updateBPM() async { 269 | // Bear in mind that the method used to calculate the BPM is very rudimentar 270 | // feel free to improve it :) 271 | 272 | // Since this function doesn't need to be so "exact" regarding the time it executes, 273 | // I only used the a Future.delay to repeat it from time to time. 274 | // Ofc you can also use a Timer object to time the callback of this function 275 | List _values; 276 | double _avg; 277 | int _n; 278 | double _m; 279 | double _threshold; 280 | double _bpm; 281 | int _counter; 282 | int _previous; 283 | while (_toggled) { 284 | _values = List.from(_data); // create a copy of the current data array 285 | _avg = 0; 286 | _n = _values.length; 287 | _m = 0; 288 | _values.forEach((SensorValue value) { 289 | _avg += value.value / _n; 290 | if (value.value > _m) _m = value.value; 291 | }); 292 | _threshold = (_m + _avg) / 2; 293 | _bpm = 0; 294 | _counter = 0; 295 | _previous = 0; 296 | for (int i = 1; i < _n; i++) { 297 | if (_values[i - 1].value < _threshold && 298 | _values[i].value > _threshold) { 299 | if (_previous != 0) { 300 | _counter++; 301 | _bpm += 60 * 302 | 1000 / 303 | (_values[i].time.millisecondsSinceEpoch - _previous); 304 | } 305 | _previous = _values[i].time.millisecondsSinceEpoch; 306 | } 307 | } 308 | if (_counter > 0) { 309 | _bpm = _bpm / _counter; 310 | print(_bpm); 311 | setState(() { 312 | this._bpm = ((1 - _alpha) * _bpm + _alpha * _bpm).toInt(); 313 | }); 314 | } 315 | await Future.delayed(Duration( 316 | milliseconds: 317 | 1000 * _windowLen ~/ _fs)); // wait for a new set of _data values 318 | } 319 | } 320 | } 321 | -------------------------------------------------------------------------------- /lib/screens/register/register.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:flutter_auth_buttons/flutter_auth_buttons.dart'; 4 | import 'package:getwidget/getwidget.dart'; 5 | import 'package:google_sign_in/google_sign_in.dart'; 6 | import 'package:openemr/screens/login/create_account.dart'; 7 | import 'package:openemr/screens/login/login2.dart'; 8 | import 'package:openemr/screens/telehealth/telehealth.dart'; 9 | import 'package:openemr/utils/customlistloadingshimmer.dart'; 10 | import 'package:shared_preferences/shared_preferences.dart'; 11 | import '../../models/user.dart'; 12 | import 'package:firebase_auth/firebase_auth.dart'; 13 | import 'package:cloud_firestore/cloud_firestore.dart'; 14 | 15 | class RegisterFirebaseScreen extends StatefulWidget { 16 | @override 17 | _RegisterFirebaseScreenState createState() => _RegisterFirebaseScreenState(); 18 | } 19 | 20 | class _RegisterFirebaseScreenState extends State { 21 | final FirebaseAuth _auth = FirebaseAuth.instance; 22 | final Firestore _store = Firestore.instance; 23 | final GoogleSignIn googleSignIn = GoogleSignIn(); 24 | final userRef = Firestore.instance.collection('username'); 25 | User user; 26 | bool _isLoading = false; 27 | 28 | final formKey = new GlobalKey(); 29 | String _email, _password, _name, _userid; 30 | 31 | void _showSnackBar(String text) { 32 | ScaffoldMessenger.of(context) 33 | .showSnackBar(new SnackBar(content: new Text(text))); 34 | } 35 | 36 | void _toggleLoadingStatus(bool newLoadingState) { 37 | setState(() { 38 | _isLoading = newLoadingState; 39 | }); 40 | } 41 | 42 | @override 43 | Widget build(BuildContext context) { 44 | double width = MediaQuery.of(context).size.width; 45 | return GestureDetector( 46 | onTap: () { 47 | FocusScopeNode currentFocus = FocusScope.of(context); 48 | if (!currentFocus.hasPrimaryFocus) { 49 | currentFocus.unfocus(); 50 | } 51 | }, 52 | child: Scaffold( 53 | backgroundColor: GFColors.LIGHT, 54 | body: Padding( 55 | padding: EdgeInsets.only(left: width * 0.1, right: width * 0.1), 56 | child: Center( 57 | child: SingleChildScrollView( 58 | child: Form( 59 | key: formKey, 60 | child: Column( 61 | mainAxisAlignment: MainAxisAlignment.center, 62 | crossAxisAlignment: CrossAxisAlignment.center, 63 | children: [ 64 | SizedBox( 65 | height: 25, 66 | ), 67 | Image.asset( 68 | 'lib/assets/images/firebase.png', 69 | width: width * 0.25, 70 | ), 71 | SizedBox( 72 | height: 20, 73 | ), 74 | _isLoading 75 | ? customListLoadingShimmer(context, 76 | loadingMessage: 'Authenticating', listLength: 4) 77 | : Column( 78 | children: [ 79 | SizedBox( 80 | child: TextFormField( 81 | validator: (value) { 82 | if (value.isEmpty) { 83 | return 'Please enter your full name'; 84 | } 85 | return null; 86 | }, 87 | onSaved: (val) => _name = val, 88 | decoration: InputDecoration( 89 | border: OutlineInputBorder(), 90 | labelText: 'Full Name'), 91 | ), 92 | ), 93 | SizedBox( 94 | height: 20, 95 | ), 96 | SizedBox( 97 | child: TextFormField( 98 | validator: (value) { 99 | if (value.isEmpty) { 100 | return 'Username can\'t be blank'; 101 | } 102 | return null; 103 | }, 104 | onSaved: (val) => _userid = val, 105 | decoration: InputDecoration( 106 | border: OutlineInputBorder(), 107 | labelText: 'Username'), 108 | ), 109 | ), 110 | SizedBox( 111 | height: 20, 112 | ), 113 | SizedBox( 114 | child: TextFormField( 115 | validator: (value) { 116 | if (value.isEmpty) { 117 | return 'Please enter your email'; 118 | } 119 | return null; 120 | }, 121 | onSaved: (val) => _email = val, 122 | decoration: InputDecoration( 123 | border: OutlineInputBorder(), 124 | labelText: 'E-mail'), 125 | ), 126 | ), 127 | SizedBox( 128 | height: 20, 129 | ), 130 | SizedBox( 131 | child: TextFormField( 132 | validator: (value) { 133 | if (value.isEmpty) { 134 | return 'Please enter password'; 135 | } 136 | return null; 137 | }, 138 | onSaved: (val) => _password = val, 139 | obscureText: true, 140 | decoration: InputDecoration( 141 | border: OutlineInputBorder(), 142 | labelText: 'Password'), 143 | ), 144 | ), 145 | SizedBox( 146 | height: 20, 147 | ), 148 | GFButton( 149 | onPressed: () => handleRegister(context), 150 | text: 'Register', 151 | color: GFColors.DARK, 152 | ), 153 | GFButton( 154 | onPressed: () => handleSignIn(context), 155 | text: 'login', 156 | color: GFColors.DARK, 157 | type: GFButtonType.outline2x, 158 | ), 159 | Padding( 160 | padding: EdgeInsets.only(top: 25, bottom: 25), 161 | child: Text( 162 | "-------OR--------", 163 | style: TextStyle( 164 | fontSize: 14, 165 | fontWeight: FontWeight.w300, 166 | color: Colors.grey), 167 | ), 168 | ), 169 | GoogleSignInButton( 170 | onPressed: () { 171 | _toggleLoadingStatus(true); 172 | signInWithGoogle(); 173 | }, 174 | textStyle: TextStyle( 175 | fontSize: 14.0, 176 | fontWeight: FontWeight.w400, 177 | color: Colors.white, 178 | ), 179 | darkMode: true, 180 | ), 181 | SizedBox( 182 | height: 25, 183 | ), 184 | ], 185 | ) 186 | ], 187 | ), 188 | ), 189 | ), 190 | ), 191 | )), 192 | ); 193 | } 194 | 195 | void handleSignIn(context) async { 196 | Navigator.pushReplacement( 197 | context, 198 | MaterialPageRoute( 199 | builder: (BuildContext context) => LoginFirebaseScreen()), 200 | ); 201 | } 202 | 203 | void handleRegister(context) async { 204 | FirebaseUser user; 205 | final form = formKey.currentState; 206 | if (form.validate()) { 207 | form.save(); 208 | _toggleLoadingStatus(true); 209 | QuerySnapshot ref = await _store 210 | .collection('username') 211 | .where("id", isEqualTo: _userid) 212 | .snapshots() 213 | .first; 214 | if (ref.documentChanges.isNotEmpty) { 215 | _toggleLoadingStatus(false); 216 | _showSnackBar("Username already exist"); 217 | return null; 218 | } 219 | try { 220 | AuthResult result = await _auth.createUserWithEmailAndPassword( 221 | email: _email, password: _password); 222 | user = result.user; 223 | } catch (error) { 224 | if (error.message != null) { 225 | _showSnackBar(error.message); 226 | } else { 227 | _showSnackBar('An unexpected error occured!'); 228 | } 229 | _toggleLoadingStatus(false); 230 | return null; 231 | } 232 | } 233 | await _store 234 | .collection('username') 235 | .document(user.uid) 236 | .setData({"id": _userid, "name": _name}); 237 | try { 238 | UserUpdateInfo updateInfo = UserUpdateInfo(); 239 | updateInfo.displayName = _name; 240 | await user.updateProfile(updateInfo); 241 | } catch (error) { 242 | if (error.message != null) { 243 | _showSnackBar(error.message); 244 | } else { 245 | _showSnackBar('An unexpected error occured!'); 246 | } 247 | return null; 248 | } 249 | await user.sendEmailVerification(); 250 | await _auth.signOut(); 251 | _toggleLoadingStatus(false); 252 | Navigator.pushReplacement( 253 | context, 254 | MaterialPageRoute( 255 | builder: (BuildContext context) => LoginFirebaseScreen( 256 | snackBarMessage: 257 | 'A verification link has been sent to your e-mail account'), 258 | ), 259 | ); 260 | } 261 | 262 | Future signInWithGoogle() async { 263 | final GoogleSignInAccount googleSignInAccount = await googleSignIn.signIn(); 264 | handleGSignIn(googleSignInAccount); 265 | } 266 | 267 | handleGSignIn(GoogleSignInAccount googleSignInAccount) async { 268 | if (googleSignInAccount != null) { 269 | final GoogleSignInAuthentication googleSignInAuthentication = 270 | await googleSignInAccount.authentication; 271 | 272 | final AuthCredential credential = GoogleAuthProvider.getCredential( 273 | accessToken: googleSignInAuthentication.accessToken, 274 | idToken: googleSignInAuthentication.idToken, 275 | ); 276 | 277 | final AuthResult authResult = 278 | await _auth.signInWithCredential(credential); 279 | final FirebaseUser user = authResult.user; 280 | 281 | try { 282 | await createUserInFirestore(user); 283 | } catch (err) { 284 | _showSnackBar(err); 285 | _toggleLoadingStatus(false); 286 | _signOut(); 287 | } 288 | 289 | shredprefUser(user.uid); 290 | 291 | _toggleLoadingStatus(false); 292 | } else { 293 | _showSnackBar('Please try again'); 294 | _toggleLoadingStatus(false); 295 | } 296 | } 297 | 298 | Future shredprefUser(String uid) async { 299 | SharedPreferences prefs = await SharedPreferences.getInstance(); 300 | prefs.setString('loggedUserId', uid); 301 | } 302 | 303 | createUserInFirestore(FirebaseUser user) async { 304 | DocumentSnapshot documentSnapshot = await userRef.document(user.uid).get(); 305 | //go to createAccount page - only for first reigstration 306 | if (!documentSnapshot.exists) { 307 | _toggleLoadingStatus(false); 308 | Navigator.of(context).pushReplacement(MaterialPageRoute( 309 | builder: (context) => CreateAccount( 310 | dispUser: user, 311 | ))); 312 | } else { 313 | _toggleLoadingStatus(false); 314 | Navigator.of(context).pushReplacement( 315 | MaterialPageRoute(builder: (context) => Telehealth())); 316 | } 317 | } 318 | 319 | Future _signOut() async { 320 | await googleSignIn.signOut(); 321 | await _auth.signOut(); 322 | 323 | SharedPreferences prefs = await SharedPreferences.getInstance(); 324 | prefs.remove('loggedUserId'); 325 | _toggleLoadingStatus(false); 326 | } 327 | } 328 | -------------------------------------------------------------------------------- /lib/screens/shimmer/shimmer.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter/cupertino.dart'; 3 | import 'package:getwidget/getwidget.dart'; 4 | 5 | class ShimmerPage extends StatefulWidget { 6 | @override 7 | _ShimmerPageState createState() => _ShimmerPageState(); 8 | } 9 | 10 | class _ShimmerPageState extends State { 11 | @override 12 | Widget build(BuildContext context) => Scaffold( 13 | appBar: AppBar( 14 | backgroundColor: GFColors.DARK, 15 | leading: InkWell( 16 | onTap: () { 17 | Navigator.pop(context); 18 | }, 19 | child: Icon( 20 | CupertinoIcons.back, 21 | color: GFColors.SUCCESS, 22 | ), 23 | ), 24 | title: const Text( 25 | 'Shimmer', 26 | style: TextStyle(fontSize: 17), 27 | ), 28 | centerTitle: true, 29 | ), 30 | body: Column(children: [ 31 | const Padding( 32 | padding: EdgeInsets.only(left: 15, top: 30, bottom: 20), 33 | child: GFTypography( 34 | text: 'Basic Shimmer Effect', 35 | type: GFTypographyType.typo5, 36 | dividerWidth: 25, 37 | dividerColor: Color(0xFF19CA4B), 38 | ), 39 | ), 40 | GFShimmer( 41 | mainColor: GFColors.DARK.withOpacity(0.22), 42 | child: Padding( 43 | padding: const EdgeInsets.symmetric(horizontal: 16), 44 | child: Row( 45 | crossAxisAlignment: CrossAxisAlignment.start, 46 | children: [ 47 | Container( 48 | width: 80, 49 | height: 80, 50 | color: Colors.white, 51 | ), 52 | const Padding( 53 | padding: EdgeInsets.symmetric(horizontal: 6), 54 | ), 55 | Expanded( 56 | child: Column( 57 | crossAxisAlignment: CrossAxisAlignment.start, 58 | children: [ 59 | Container( 60 | width: double.infinity, 61 | height: 12, 62 | color: Colors.white, 63 | ), 64 | const Padding( 65 | padding: EdgeInsets.symmetric(vertical: 2), 66 | ), 67 | Container( 68 | width: MediaQuery.of(context).size.width * 0.5, 69 | height: 12, 70 | color: Colors.white, 71 | ), 72 | const Padding( 73 | padding: EdgeInsets.symmetric(vertical: 2), 74 | ), 75 | Container( 76 | width: MediaQuery.of(context).size.width * 0.25, 77 | height: 12, 78 | color: Colors.white, 79 | ), 80 | ], 81 | ), 82 | ) 83 | ], 84 | ), 85 | ), 86 | ), 87 | // const Padding( 88 | // padding: EdgeInsets.only(left: 15, top: 30, bottom: 20), 89 | // child: GFTypography( 90 | // text: 'Shimmer Effect on Text', 91 | // type: GFTypographyType.typo5, 92 | // dividerWidth: 25, 93 | // dividerColor: Color(0xFF19CA4B), 94 | // ), 95 | // ), 96 | // GFShimmer( 97 | // child: Row(mainAxisAlignment: MainAxisAlignment.center, children: [ 98 | // const Text( 99 | // 'Hurray!! Order Placed', 100 | // style: TextStyle(fontSize: 25, fontWeight: FontWeight.w700), 101 | // ), 102 | // const SizedBox(width: 5), 103 | // Icon(Icons.sentiment_very_satisfied), 104 | // ]), 105 | // direction: GFShimmerDirection.rightToLeft, 106 | // showGradient: true, 107 | // gradient: LinearGradient( 108 | // begin: Alignment.bottomRight, 109 | // end: Alignment.centerLeft, 110 | // stops: const [0, 0.3, 0.6, 0.9, 1], 111 | // colors: const [ 112 | // GFColors.DANGER, 113 | // GFColors.PRIMARY, 114 | // GFColors.WARNING, 115 | // GFColors.SECONDARY, 116 | // Colors.red, 117 | // ], 118 | // ), 119 | // ), 120 | ])); 121 | } 122 | -------------------------------------------------------------------------------- /lib/screens/telehealth/chat.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'dart:io'; 3 | 4 | import 'package:cloud_firestore/cloud_firestore.dart'; 5 | import 'package:firebase_storage/firebase_storage.dart'; 6 | import 'package:flutter/cupertino.dart'; 7 | import 'package:flutter/material.dart'; 8 | import 'package:image_picker/image_picker.dart'; 9 | import 'package:intl/intl.dart'; 10 | import 'package:dash_chat/dash_chat.dart'; 11 | import 'package:getwidget/getwidget.dart'; 12 | import 'package:openemr/utils/customlistloadingshimmer.dart'; 13 | 14 | class ChatScreen extends StatefulWidget { 15 | final String messagesId; 16 | final String chatDocumentId; 17 | final String heading; 18 | final String userId; 19 | final String userName; 20 | 21 | ChatScreen( 22 | {Key key, 23 | @required this.messagesId, 24 | @required this.heading, 25 | @required this.userId, 26 | @required this.userName, 27 | @required this.chatDocumentId}) 28 | : super(key: key); 29 | 30 | @override 31 | _ChatScreenState createState() => _ChatScreenState(); 32 | } 33 | 34 | class _ChatScreenState extends State { 35 | final GlobalKey _chatViewKey = GlobalKey(); 36 | final Firestore _store = Firestore.instance; 37 | ChatUser chatUser; 38 | final picker = ImagePicker(); 39 | 40 | List messages = List.empty(growable: true); 41 | var m = List.empty(growable: true); 42 | 43 | var i = 0; 44 | 45 | @override 46 | void initState() { 47 | chatUser = ChatUser( 48 | name: widget.userName, 49 | uid: widget.userId, 50 | ); 51 | this.setState(() { 52 | chatUser = chatUser; 53 | }); 54 | super.initState(); 55 | } 56 | 57 | void systemMessage() { 58 | Timer(Duration(milliseconds: 300), () { 59 | if (i < 6) { 60 | setState(() { 61 | messages = [...messages, m[i]]; 62 | }); 63 | i++; 64 | } 65 | Timer(Duration(milliseconds: 300), () { 66 | _chatViewKey.currentState.scrollController 67 | ..animateTo( 68 | _chatViewKey.currentState.scrollController.position.maxScrollExtent, 69 | curve: Curves.easeOut, 70 | duration: const Duration(milliseconds: 300), 71 | ); 72 | }); 73 | }); 74 | } 75 | 76 | void onSend(ChatMessage message) async { 77 | print(message.toJson()); 78 | await _store.collection('messages').document(widget.messagesId).updateData({ 79 | "messages": FieldValue.arrayUnion([message.toJson()]) 80 | }); 81 | if (message.text != null && message.text != "") { 82 | _store 83 | .collection('chats') 84 | .document(widget.chatDocumentId) 85 | .updateData({"lastMessage": message.text}); 86 | } 87 | } 88 | 89 | void uploadImage(result) async { 90 | final StorageReference storageRef = FirebaseStorage.instance 91 | .ref() 92 | .child(widget.userId + "-" + DateTime.now().toString()); 93 | 94 | StorageUploadTask uploadTask = storageRef.putFile( 95 | result, 96 | StorageMetadata( 97 | contentType: 'image/jpg', 98 | ), 99 | ); 100 | StorageTaskSnapshot download = await uploadTask.onComplete; 101 | 102 | String url = await download.ref.getDownloadURL(); 103 | 104 | ChatMessage message = ChatMessage(text: "", user: chatUser, image: url); 105 | onSend(message); 106 | } 107 | 108 | @override 109 | Widget build(BuildContext context) { 110 | return Scaffold( 111 | appBar: AppBar( 112 | backgroundColor: GFColors.DARK, 113 | leading: InkWell( 114 | onTap: () { 115 | Navigator.pop(context); 116 | }, 117 | child: Icon( 118 | CupertinoIcons.back, 119 | color: GFColors.SUCCESS, 120 | ), 121 | ), 122 | title: Text( 123 | widget.heading, 124 | style: TextStyle(fontSize: 17), 125 | ), 126 | centerTitle: true, 127 | ), 128 | body: StreamBuilder( 129 | stream: Firestore.instance 130 | .collection('messages') 131 | .document(widget.messagesId) 132 | .snapshots(), 133 | builder: (context, snapshot) { 134 | if (snapshot.connectionState == ConnectionState.waiting || 135 | !snapshot.hasData) { 136 | return Center( 137 | child: customListLoadingShimmer(context, 138 | loadingMessage: 'Loading your Messages...', 139 | listLength: 6), 140 | ); 141 | } else { 142 | DocumentSnapshot items = snapshot.data; 143 | List msg = items.data["messages"] == null 144 | ? [] 145 | : items.data["messages"]; 146 | List messages = []; 147 | msg.forEach((item) => messages.add(ChatMessage.fromJson(item))); 148 | return DashChat( 149 | key: _chatViewKey, 150 | inverted: false, 151 | onSend: onSend, 152 | sendOnEnter: true, 153 | textInputAction: TextInputAction.send, 154 | user: chatUser, 155 | inputDecoration: InputDecoration.collapsed( 156 | hintText: "Add message here..."), 157 | dateFormat: DateFormat('yyyy-MMM-dd'), 158 | timeFormat: DateFormat('HH:mm'), 159 | messages: messages, 160 | showUserAvatar: true, 161 | showAvatarForEveryMessage: true, 162 | scrollToBottom: true, 163 | onPressAvatar: (ChatUser user) { 164 | print("OnPressAvatar: ${user.name}"); 165 | }, 166 | onLongPressAvatar: (ChatUser user) { 167 | print("OnLongPressAvatar: ${user.name}"); 168 | }, 169 | inputMaxLines: 5, 170 | messageContainerPadding: 171 | EdgeInsets.only(left: 5.0, right: 5.0), 172 | alwaysShowSend: false, 173 | inputTextStyle: TextStyle(fontSize: 16.0), 174 | inputContainerStyle: BoxDecoration( 175 | border: Border.all(width: 0.0), 176 | color: Colors.white, 177 | ), 178 | onLoadEarlier: () { 179 | print("laoding..."); 180 | }, 181 | shouldShowLoadEarlier: false, 182 | showTraillingBeforeSend: true, 183 | trailing: [ 184 | IconButton( 185 | icon: Icon(Icons.camera_alt), 186 | onPressed: () async { 187 | final pickedFile = await picker.getImage( 188 | source: ImageSource.camera, 189 | imageQuality: 80, 190 | maxHeight: 400, 191 | maxWidth: 400, 192 | ); 193 | 194 | if (pickedFile != null) { 195 | File result = File(pickedFile.path); 196 | uploadImage(result); 197 | } 198 | }, 199 | ), 200 | IconButton( 201 | icon: Icon(Icons.photo), 202 | onPressed: () async { 203 | final pickedFile = await picker.getImage( 204 | source: ImageSource.gallery, 205 | imageQuality: 80, 206 | maxHeight: 400, 207 | maxWidth: 400, 208 | ); 209 | 210 | if (pickedFile != null) { 211 | File result = File(pickedFile.path); 212 | uploadImage(result); 213 | } 214 | }, 215 | ), 216 | ], 217 | ); 218 | } 219 | })); 220 | } 221 | } 222 | -------------------------------------------------------------------------------- /lib/screens/telehealth/local_widgets/profileShimmer.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:getwidget/components/shimmer/gf_shimmer.dart'; 3 | 4 | Widget profileShimmer(BuildContext context) { 5 | return GFShimmer( 6 | child: Column( 7 | crossAxisAlignment: CrossAxisAlignment.center, 8 | children: [ 9 | SizedBox( 10 | height: 40, 11 | ), 12 | Container( 13 | height: 100, 14 | width: MediaQuery.of(context).size.width * 0.8, 15 | color: Colors.white, 16 | ), 17 | SizedBox( 18 | height: 10, 19 | ), 20 | Row( 21 | mainAxisAlignment: MainAxisAlignment.center, 22 | crossAxisAlignment: CrossAxisAlignment.start, 23 | children: [ 24 | CircleAvatar( 25 | backgroundColor: Colors.white, 26 | radius: 40, 27 | ), 28 | Container( 29 | padding: const EdgeInsets.fromLTRB(10, 5, 5, 5), 30 | child: Column( 31 | crossAxisAlignment: CrossAxisAlignment.start, 32 | children: [ 33 | Container( 34 | height: 25, 35 | width: MediaQuery.of(context).size.width * 0.55, 36 | color: Colors.white, 37 | ), 38 | SizedBox(height: 10), 39 | Container( 40 | height: 20, 41 | width: MediaQuery.of(context).size.width * 0.5, 42 | color: Colors.white, 43 | ), 44 | ], 45 | ), 46 | ) 47 | ], 48 | ), 49 | Container( 50 | margin: const EdgeInsets.symmetric(horizontal: 10, vertical: 15), 51 | child: Column( 52 | crossAxisAlignment: CrossAxisAlignment.start, 53 | children: [ 54 | Container( 55 | height: 25, 56 | width: MediaQuery.of(context).size.width * 0.7, 57 | color: Colors.white, 58 | ), 59 | SizedBox(height: 10), 60 | Container( 61 | height: 20, 62 | width: MediaQuery.of(context).size.width * 0.7, 63 | color: Colors.white, 64 | ), 65 | ], 66 | ), 67 | ), 68 | Row( 69 | mainAxisAlignment: MainAxisAlignment.center, 70 | children: [ 71 | Container( 72 | margin: const EdgeInsets.all(5), 73 | height: 100, 74 | width: 100, 75 | color: Colors.white, 76 | ), 77 | Container( 78 | margin: const EdgeInsets.all(5), 79 | height: 100, 80 | width: 100, 81 | color: Colors.white, 82 | ), 83 | Container( 84 | margin: const EdgeInsets.all(5), 85 | height: 100, 86 | width: 100, 87 | color: Colors.white, 88 | ), 89 | ], 90 | ) 91 | ], 92 | )); 93 | } 94 | -------------------------------------------------------------------------------- /lib/screens/telehealth/profile.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:getwidget/getwidget.dart'; 4 | import 'package:modal_progress_hud/modal_progress_hud.dart'; 5 | import 'package:openemr/screens/telehealth/telehealth.dart'; 6 | import '../../models/user.dart'; 7 | import 'package:firebase_auth/firebase_auth.dart'; 8 | import 'package:cloud_firestore/cloud_firestore.dart'; 9 | 10 | class FirebaseProfileScreen extends StatefulWidget { 11 | //user's current display name 12 | final String dispName; 13 | FirebaseProfileScreen({Key key, @required this.dispName}) : super(key: key); 14 | @override 15 | _FirebaseProfileScreenState createState() => _FirebaseProfileScreenState(); 16 | } 17 | 18 | class _FirebaseProfileScreenState extends State { 19 | final FirebaseAuth _auth = FirebaseAuth.instance; 20 | final Firestore _store = Firestore.instance; 21 | User user; 22 | 23 | final formKey = new GlobalKey(); 24 | String _name; 25 | 26 | //decides when to active/inactive spinner indicator 27 | bool showSpinner = false; 28 | 29 | void _showSnackBar(String text) { 30 | ScaffoldMessenger.of(context) 31 | .showSnackBar(new SnackBar(content: new Text(text))); 32 | } 33 | 34 | @override 35 | Widget build(BuildContext context) { 36 | double width = MediaQuery.of(context).size.width; 37 | return GestureDetector( 38 | onTap: () { 39 | FocusScopeNode currentFocus = FocusScope.of(context); 40 | if (!currentFocus.hasPrimaryFocus) { 41 | currentFocus.unfocus(); 42 | } 43 | }, 44 | child: Scaffold( 45 | backgroundColor: GFColors.LIGHT, 46 | body: ModalProgressHUD( 47 | // color: Colors.blueAccent, 48 | inAsyncCall: showSpinner, 49 | child: Padding( 50 | padding: EdgeInsets.only(left: width * 0.1, right: width * 0.1), 51 | child: Center( 52 | child: SingleChildScrollView( 53 | child: Form( 54 | key: formKey, 55 | child: Column( 56 | mainAxisAlignment: MainAxisAlignment.center, 57 | crossAxisAlignment: CrossAxisAlignment.center, 58 | children: [ 59 | SizedBox( 60 | height: 25, 61 | ), 62 | SizedBox( 63 | height: 20, 64 | ), 65 | SizedBox( 66 | child: TextFormField( 67 | //set initial value as the dispName 68 | initialValue: widget.dispName, 69 | validator: (value) { 70 | if (value.isEmpty) { 71 | return 'Display name can\'t be blank'; 72 | } 73 | return null; 74 | }, 75 | onSaved: (val) => _name = val, 76 | decoration: InputDecoration( 77 | border: OutlineInputBorder(), 78 | labelText: 'Display name'), 79 | ), 80 | ), 81 | SizedBox( 82 | height: 20, 83 | ), 84 | GFButton( 85 | onPressed: () => updateProfile(context), 86 | text: 'Update', 87 | color: GFColors.DARK, 88 | ), 89 | ], 90 | ), 91 | ), 92 | ), 93 | ), 94 | ), 95 | ), 96 | ), 97 | ); 98 | } 99 | 100 | void updateProfile(context) async { 101 | //start showing the spinner 102 | setState(() { 103 | showSpinner = true; 104 | }); 105 | FirebaseUser user; 106 | String errorMessage; 107 | final form = formKey.currentState; 108 | if (form.validate()) { 109 | form.save(); 110 | try { 111 | user = await _auth.currentUser(); 112 | UserUpdateInfo updateInfo = UserUpdateInfo(); 113 | updateInfo.displayName = _name; 114 | await user.updateProfile(updateInfo); 115 | } catch (error) { 116 | //stop showing the spinner 117 | setState(() { 118 | showSpinner = false; 119 | }); 120 | switch (error.code) { 121 | case "ERROR_USER_DISABLED": 122 | errorMessage = "Your acount has been disabled"; 123 | break; 124 | case "ERROR_USER_NOT_FOUND": 125 | errorMessage = "Account not found"; 126 | break; 127 | default: 128 | errorMessage = error.code == null 129 | ? "An undefined Error happened." 130 | : error.code; 131 | } 132 | } 133 | } 134 | if (errorMessage != null) { 135 | //stop showing the spinner 136 | setState(() { 137 | showSpinner = false; 138 | }); 139 | _showSnackBar(errorMessage); 140 | return null; 141 | } 142 | await _store 143 | .collection('username') 144 | .document(user.uid) 145 | .updateData({"name": _name}); 146 | Navigator.of(context).pushAndRemoveUntil( 147 | MaterialPageRoute(builder: (context) => Telehealth()), 148 | (route) => false); 149 | //stop showing the spinner 150 | setState(() { 151 | showSpinner = false; 152 | }); 153 | } 154 | } 155 | -------------------------------------------------------------------------------- /lib/screens/telehealth/signaling.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | import 'dart:async'; 3 | import 'package:flutter_webrtc/webrtc.dart'; 4 | import 'package:openemr/utils/websocket.dart'; 5 | 6 | enum SignalingState { 7 | CallStateNew, 8 | CallStateRinging, 9 | CallStateInvite, 10 | CallStateConnected, 11 | CallStateBye, 12 | ConnectionOpen, 13 | ConnectionClosed, 14 | ConnectionError, 15 | } 16 | 17 | /* 18 | * callbacks for Signaling API. 19 | */ 20 | typedef void SignalingStateCallback(SignalingState state); 21 | typedef void StreamStateCallback(MediaStream stream); 22 | typedef void OtherEventCallback(dynamic event); 23 | typedef void DataChannelMessageCallback( 24 | RTCDataChannel dc, RTCDataChannelMessage data); 25 | typedef void DataChannelCallback(RTCDataChannel dc); 26 | 27 | class Signaling { 28 | JsonEncoder _encoder = new JsonEncoder(); 29 | String _selfId; 30 | SimpleWebSocket _socket; 31 | var _sessionId; 32 | var _host; 33 | var _port = 8086; 34 | var _peerConnections = new Map(); 35 | var _dataChannels = new Map(); 36 | var _remoteCandidates = []; 37 | 38 | MediaStream _localStream; 39 | List _remoteStreams; 40 | SignalingStateCallback onStateChange; 41 | StreamStateCallback onLocalStream; 42 | StreamStateCallback onAddRemoteStream; 43 | StreamStateCallback onRemoveRemoteStream; 44 | OtherEventCallback onPeersUpdate; 45 | DataChannelMessageCallback onDataChannelMessage; 46 | DataChannelCallback onDataChannel; 47 | 48 | Map _iceServers = { 49 | 'iceServers': [ 50 | {'url': 'stun:stun.l.google.com:19302'}, 51 | ] 52 | }; 53 | 54 | final Map _config = { 55 | 'mandatory': {}, 56 | 'optional': [ 57 | {'DtlsSrtpKeyAgreement': true}, 58 | ], 59 | }; 60 | 61 | final Map _constraints = { 62 | 'mandatory': { 63 | 'OfferToReceiveAudio': true, 64 | 'OfferToReceiveVideo': true, 65 | }, 66 | 'optional': [], 67 | }; 68 | 69 | final Map _dcConstraints = { 70 | 'mandatory': { 71 | 'OfferToReceiveAudio': false, 72 | 'OfferToReceiveVideo': false, 73 | }, 74 | 'optional': [], 75 | }; 76 | 77 | Signaling(this._host, this._selfId); 78 | 79 | close() { 80 | if (_localStream != null) { 81 | _localStream.dispose(); 82 | _localStream = null; 83 | } 84 | 85 | _peerConnections.forEach((key, pc) { 86 | pc.close(); 87 | }); 88 | if (_socket != null) _socket.close(); 89 | } 90 | 91 | void switchCamera() { 92 | if (_localStream != null) { 93 | _localStream.getVideoTracks()[0].switchCamera(); 94 | } 95 | } 96 | 97 | void invite(String peerId, String media) { 98 | this._sessionId = this._selfId + '-' + peerId; 99 | 100 | if (this.onStateChange != null) { 101 | this.onStateChange(SignalingState.CallStateNew); 102 | } 103 | 104 | _createPeerConnection(peerId, media).then((pc) { 105 | _peerConnections[peerId] = pc; 106 | if (media == 'data') { 107 | _createDataChannel(peerId, pc); 108 | } 109 | _createOffer(peerId, pc, media); 110 | }); 111 | } 112 | 113 | void bye() { 114 | _send('bye', { 115 | 'session_id': this._sessionId, 116 | 'from': this._selfId, 117 | }); 118 | } 119 | 120 | void onMessage(message) async { 121 | Map mapData = message; 122 | var data = mapData['data']; 123 | 124 | switch (mapData['type']) { 125 | case 'peers': 126 | { 127 | List peers = data; 128 | if (this.onPeersUpdate != null) { 129 | Map event = new Map(); 130 | event['self'] = _selfId; 131 | event['peers'] = peers; 132 | this.onPeersUpdate(event); 133 | } 134 | } 135 | break; 136 | case 'offer': 137 | { 138 | var id = data['from']; 139 | var description = data['description']; 140 | var media = data['media']; 141 | var sessionId = data['session_id']; 142 | this._sessionId = sessionId; 143 | 144 | if (this.onStateChange != null) { 145 | this.onStateChange(SignalingState.CallStateNew); 146 | } 147 | 148 | var pc = await _createPeerConnection(id, media); 149 | _peerConnections[id] = pc; 150 | await pc.setRemoteDescription(new RTCSessionDescription( 151 | description['sdp'], description['type'])); 152 | await _createAnswer(id, pc, media); 153 | if (this._remoteCandidates.length > 0) { 154 | _remoteCandidates.forEach((candidate) async { 155 | await pc.addCandidate(candidate); 156 | }); 157 | _remoteCandidates.clear(); 158 | } 159 | } 160 | break; 161 | case 'answer': 162 | { 163 | var id = data['from']; 164 | var description = data['description']; 165 | 166 | var pc = _peerConnections[id]; 167 | if (pc != null) { 168 | await pc.setRemoteDescription(new RTCSessionDescription( 169 | description['sdp'], description['type'])); 170 | } 171 | } 172 | break; 173 | case 'candidate': 174 | { 175 | var id = data['from']; 176 | var candidateMap = data['candidate']; 177 | var pc = _peerConnections[id]; 178 | RTCIceCandidate candidate = new RTCIceCandidate( 179 | candidateMap['candidate'], 180 | candidateMap['sdpMid'], 181 | candidateMap['sdpMLineIndex']); 182 | if (pc != null) { 183 | await pc.addCandidate(candidate); 184 | } else { 185 | _remoteCandidates.add(candidate); 186 | } 187 | } 188 | break; 189 | case 'leave': 190 | { 191 | var id = data; 192 | var pc = _peerConnections.remove(id); 193 | _dataChannels.remove(id); 194 | 195 | if (_localStream != null) { 196 | _localStream.dispose(); 197 | _localStream = null; 198 | } 199 | 200 | if (pc != null) { 201 | pc.close(); 202 | } 203 | this._sessionId = null; 204 | if (this.onStateChange != null) { 205 | this.onStateChange(SignalingState.CallStateBye); 206 | } 207 | } 208 | break; 209 | case 'bye': 210 | { 211 | var to = data['to']; 212 | var sessionId = data['session_id']; 213 | print('bye: ' + sessionId); 214 | 215 | if (_localStream != null) { 216 | _localStream.dispose(); 217 | _localStream = null; 218 | } 219 | 220 | var pc = _peerConnections[to]; 221 | if (pc != null) { 222 | pc.close(); 223 | _peerConnections.remove(to); 224 | } 225 | 226 | var dc = _dataChannels[to]; 227 | if (dc != null) { 228 | dc.close(); 229 | _dataChannels.remove(to); 230 | } 231 | 232 | this._sessionId = null; 233 | if (this.onStateChange != null) { 234 | this.onStateChange(SignalingState.CallStateBye); 235 | } 236 | } 237 | break; 238 | case 'keepalive': 239 | { 240 | print('keepalive response!'); 241 | } 242 | break; 243 | default: 244 | break; 245 | } 246 | } 247 | 248 | void connect(name) async { 249 | var url = 'https://$_host:$_port/ws'; 250 | _socket = SimpleWebSocket(url); 251 | 252 | print('connect to $url'); 253 | 254 | _socket.onOpen = () { 255 | print('onOpen'); 256 | this?.onStateChange(SignalingState.ConnectionOpen); 257 | _send('new', {'name': name, 'id': _selfId, 'user_agent': "initiator"}); 258 | }; 259 | 260 | _socket.onMessage = (message) { 261 | print('Received data: ' + message); 262 | JsonDecoder decoder = new JsonDecoder(); 263 | this.onMessage(decoder.convert(message)); 264 | }; 265 | 266 | _socket.onClose = (int code, String reason) { 267 | print('Closed by server [$code => $reason]!'); 268 | if (this.onStateChange != null) { 269 | this.onStateChange(SignalingState.ConnectionClosed); 270 | } 271 | }; 272 | 273 | await _socket.connect(); 274 | } 275 | 276 | Future createStream(media) async { 277 | final Map mediaConstraints = { 278 | 'audio': true, 279 | 'video': { 280 | 'mandatory': { 281 | 'minWidth': '640', 282 | 'minHeight': '480', 283 | 'minFrameRate': '30', 284 | }, 285 | 'facingMode': 'user', 286 | 'optional': [], 287 | } 288 | }; 289 | 290 | MediaStream stream = await navigator.getUserMedia(mediaConstraints); 291 | if (this.onLocalStream != null) { 292 | this.onLocalStream(stream); 293 | } 294 | return stream; 295 | } 296 | 297 | _createPeerConnection(id, media) async { 298 | if (media != 'data') _localStream = await createStream(media); 299 | RTCPeerConnection pc = await createPeerConnection(_iceServers, _config); 300 | if (media != 'data') pc.addStream(_localStream); 301 | pc.onIceCandidate = (candidate) { 302 | _send('candidate', { 303 | 'to': id, 304 | 'from': _selfId, 305 | 'candidate': { 306 | 'sdpMLineIndex': candidate.sdpMlineIndex, 307 | 'sdpMid': candidate.sdpMid, 308 | 'candidate': candidate.candidate, 309 | }, 310 | 'session_id': this._sessionId, 311 | }); 312 | }; 313 | 314 | pc.onIceConnectionState = (state) {}; 315 | 316 | pc.onAddStream = (stream) { 317 | if (this.onAddRemoteStream != null) this.onAddRemoteStream(stream); 318 | //_remoteStreams.add(stream); 319 | }; 320 | 321 | pc.onRemoveStream = (stream) { 322 | if (this.onRemoveRemoteStream != null) this.onRemoveRemoteStream(stream); 323 | _remoteStreams.removeWhere((it) { 324 | return (it.id == stream.id); 325 | }); 326 | }; 327 | 328 | pc.onDataChannel = (channel) { 329 | _addDataChannel(id, channel); 330 | }; 331 | 332 | return pc; 333 | } 334 | 335 | _addDataChannel(id, RTCDataChannel channel) { 336 | channel.onDataChannelState = (e) {}; 337 | channel.onMessage = (RTCDataChannelMessage data) { 338 | if (this.onDataChannelMessage != null) 339 | this.onDataChannelMessage(channel, data); 340 | }; 341 | _dataChannels[id] = channel; 342 | 343 | if (this.onDataChannel != null) this.onDataChannel(channel); 344 | } 345 | 346 | _createDataChannel(id, RTCPeerConnection pc, {label: 'fileTransfer'}) async { 347 | RTCDataChannelInit dataChannelDict = new RTCDataChannelInit(); 348 | RTCDataChannel channel = await pc.createDataChannel(label, dataChannelDict); 349 | _addDataChannel(id, channel); 350 | } 351 | 352 | _createOffer(String id, RTCPeerConnection pc, String media) async { 353 | try { 354 | RTCSessionDescription s = 355 | await pc.createOffer(media == 'data' ? _dcConstraints : _constraints); 356 | pc.setLocalDescription(s); 357 | _send('offer', { 358 | 'to': id, 359 | 'from': _selfId, 360 | 'description': {'sdp': s.sdp, 'type': s.type}, 361 | 'session_id': this._sessionId, 362 | 'media': media, 363 | }); 364 | } catch (e) { 365 | print(e.toString()); 366 | } 367 | } 368 | 369 | _createAnswer(String id, RTCPeerConnection pc, media) async { 370 | try { 371 | RTCSessionDescription s = await pc 372 | .createAnswer(media == 'data' ? _dcConstraints : _constraints); 373 | pc.setLocalDescription(s); 374 | _send('answer', { 375 | 'to': id, 376 | 'from': _selfId, 377 | 'description': {'sdp': s.sdp, 'type': s.type}, 378 | 'session_id': this._sessionId, 379 | }); 380 | } catch (e) { 381 | print(e.toString()); 382 | } 383 | } 384 | 385 | _send(event, data) { 386 | var request = new Map(); 387 | request["type"] = event; 388 | request["data"] = data; 389 | _socket.send(_encoder.convert(request)); 390 | } 391 | } 392 | -------------------------------------------------------------------------------- /lib/utils/common.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | void showToast(BuildContext context, String msg) { 4 | ScaffoldMessenger.of(context).showSnackBar( 5 | SnackBar( 6 | content: Text(msg), 7 | ), 8 | ); 9 | } 10 | 11 | bool isValidUrl(url) { 12 | return Uri.parse(url).isAbsolute; 13 | } 14 | -------------------------------------------------------------------------------- /lib/utils/customlistloadingshimmer.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:getwidget/components/shimmer/gf_shimmer.dart'; 3 | 4 | Widget listItemShimmer(BuildContext context) { 5 | return GFShimmer( 6 | child: Container( 7 | alignment: Alignment.center, 8 | margin: const EdgeInsets.all(5), 9 | child: Row( 10 | children: [ 11 | CircleAvatar( 12 | backgroundColor: Colors.white, 13 | radius: 30, 14 | ), 15 | Container( 16 | padding: const EdgeInsets.fromLTRB(10, 5, 5, 5), 17 | child: Column( 18 | crossAxisAlignment: CrossAxisAlignment.start, 19 | children: [ 20 | Container( 21 | height: 25, 22 | width: MediaQuery.of(context).size.width * 0.55, 23 | color: Colors.white, 24 | ), 25 | SizedBox(height: 10), 26 | Container( 27 | height: 20, 28 | width: MediaQuery.of(context).size.width * 0.5, 29 | color: Colors.white, 30 | ), 31 | ], 32 | ), 33 | ) 34 | ], 35 | ), 36 | ), 37 | ); 38 | } 39 | 40 | Widget customListLoadingShimmer(BuildContext context, 41 | {String loadingMessage, int listLength = 1}) { 42 | return Container( 43 | alignment: Alignment.center, 44 | width: MediaQuery.of(context).size.width * 0.8, 45 | margin: const EdgeInsets.symmetric(vertical: 10), 46 | child: Column( 47 | mainAxisAlignment: MainAxisAlignment.center, 48 | crossAxisAlignment: CrossAxisAlignment.center, 49 | children: [ 50 | loadingMessage == null 51 | ? Container() 52 | : Padding( 53 | padding: const EdgeInsets.only(bottom: 10), 54 | child: Text( 55 | loadingMessage, 56 | style: TextStyle(fontSize: 16, color: Colors.grey), 57 | ), 58 | ), 59 | for (var i = 0; i < listLength; i++) listItemShimmer(context) 60 | ], 61 | ), 62 | ); 63 | } 64 | -------------------------------------------------------------------------------- /lib/utils/network.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'dart:convert'; 3 | import 'dart:io'; 4 | import 'package:http/http.dart' as http; 5 | import 'package:http_parser/http_parser.dart'; 6 | import 'package:path/path.dart'; 7 | import 'common.dart'; 8 | 9 | class NetworkUtil { 10 | // next three lines makes this class a Singleton 11 | static NetworkUtil _instance = new NetworkUtil.internal(); 12 | NetworkUtil.internal(); 13 | factory NetworkUtil() => _instance; 14 | 15 | final JsonDecoder _decoder = new JsonDecoder(); 16 | 17 | Future get(String url, {Map headers}) { 18 | if (!isValidUrl(url)) { 19 | return Future.error("Invalid API URL"); 20 | } 21 | return http.get(url, headers: headers).then((http.Response response) { 22 | final String res = response.body; 23 | final int statusCode = response.statusCode; 24 | 25 | if (statusCode < 200 || statusCode > 400 || json == null) { 26 | throw new Exception("Error while fetching data"); 27 | } 28 | return _decoder.convert(res); 29 | }); 30 | } 31 | 32 | Future post(String url, {Map headers, body, encoding}) { 33 | if (!isValidUrl(url)) { 34 | return Future.error("Invalid API URL"); 35 | } 36 | return http 37 | .post(url, 38 | body: json.encode(body), headers: headers, encoding: encoding) 39 | .then((http.Response response) { 40 | final res = response.body; 41 | final int statusCode = response.statusCode; 42 | if (statusCode == 401) { 43 | throw new Exception(statusCode.toString() + "Invalid Credentials"); 44 | } else if (statusCode == 400) { 45 | final resData = json.decode(res); 46 | var validationErrorData = resData['validationErrors']; 47 | List> errors = []; 48 | validationErrorData.entries.forEach(((err) => errors.add(err.value))); 49 | if (errors.isNotEmpty) { 50 | throw Exception( 51 | statusCode.toString() + " " + errors[0].values.toString()); 52 | } 53 | } else if (statusCode < 200 || statusCode > 400 || json == null) { 54 | throw Exception(statusCode.toString() + "Error while fetching data"); 55 | } 56 | return _decoder.convert(res); 57 | }); 58 | } 59 | 60 | Future upload(String url, File img) async { 61 | if (!isValidUrl(url)) { 62 | return Future.error("Invalid API URL"); 63 | } 64 | 65 | try { 66 | var streamed = new http.ByteStream(Stream.castFrom(img.openRead())); 67 | var length = await img.length(); 68 | 69 | var uri = Uri.parse(url); 70 | 71 | var request = new http.MultipartRequest("POST", uri); 72 | var multipartFile = new http.MultipartFile('image', streamed, length, 73 | filename: basename(img.path), 74 | contentType: new MediaType('image', 'jpeg')); 75 | 76 | request.files.add(multipartFile); 77 | var response = await request.send(); 78 | final int statusCode = response.statusCode; 79 | if (statusCode == 401) { 80 | throw new Exception(statusCode.toString() + "Invalid Credentials"); 81 | } else if (statusCode < 200 || statusCode > 400 || json == null) { 82 | throw new Exception( 83 | statusCode.toString() + "Error while fetching data"); 84 | } 85 | var data = await response.stream.bytesToString(); 86 | return {"err": null, "data": data}; 87 | } catch (err) { 88 | return {"err": err, "data": null}; 89 | } 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /lib/utils/rest_ds.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'dart:io'; 3 | 4 | import 'package:openemr/models/patient.dart'; 5 | import 'package:openemr/utils/network.dart'; 6 | import 'package:openemr/models/user.dart'; 7 | import 'package:openemr/const/strings.dart'; 8 | 9 | class RestDatasource { 10 | NetworkUtil _netUtil = new NetworkUtil(); 11 | 12 | Future login(String username, String password, String url) { 13 | url = url == null ? "" : url; 14 | return _netUtil.post(url + loginendpoint, body: { 15 | "grant_type": "password", 16 | "username": username, 17 | "password": password, 18 | "scope": "default" 19 | }).then((dynamic res) { 20 | if (res == null) throw new Exception("Invalid Login Credentials"); 21 | res['username'] = username; 22 | res['baseUrl'] = url; 23 | res['password'] = password; 24 | return new User.map(res); 25 | }); 26 | } 27 | 28 | Future> getPatientList(baseUrl, token) { 29 | Map headers = {"Authorization": token}; 30 | return _netUtil 31 | .get(baseUrl + patientendpoint, headers: headers) 32 | .then((dynamic res) { 33 | if (res == null) throw new Exception("Error fetching data"); 34 | var patientList = new List.empty(growable: true); 35 | if (res["data"] != null) { 36 | res = res["data"]; 37 | } 38 | res.forEach((patient) => {patientList.add(Patient.map(patient))}); 39 | return patientList; 40 | }); 41 | } 42 | 43 | Future addPatient({ 44 | baseUrl, 45 | token, 46 | title, 47 | fname, 48 | mname, 49 | lname, 50 | dob, 51 | sex, 52 | street, 53 | postalcode, 54 | city, 55 | state, 56 | countrycode, 57 | phonecontact, 58 | race, 59 | ethnicity, 60 | }) { 61 | Map headers = {"Authorization": token}; 62 | return _netUtil.post(baseUrl + addpatientendpoint, headers: headers, body: { 63 | "title": title, 64 | "fname": fname, 65 | "mname": mname, 66 | "lname": lname, 67 | "DOB": dob, 68 | "sex": sex, 69 | "street": street, 70 | "postal_code": postalcode, 71 | "city": city, 72 | "state": state, 73 | "country_code": countrycode, 74 | "phone_contact": phonecontact, 75 | "race": race, 76 | "ethnicity": ethnicity, 77 | }).then((dynamic res) { 78 | if (res["data"] != null) { 79 | res = res["data"]; 80 | } 81 | return res; 82 | }); 83 | } 84 | 85 | Future textRecognition(File img, ip) { 86 | return _netUtil 87 | .upload("https://" + ip + ":8086/ocrImage", img) 88 | .then((dynamic res) { 89 | return res; 90 | }); 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /lib/utils/system_padding.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class SystemPadding extends StatelessWidget { 4 | final Widget child; 5 | 6 | SystemPadding({Key key, this.child}) : super(key: key); 7 | 8 | @override 9 | Widget build(BuildContext context) { 10 | var mediaQuery = MediaQuery.of(context); 11 | return new AnimatedContainer( 12 | padding: mediaQuery.viewInsets, 13 | duration: const Duration(milliseconds: 300), 14 | child: child); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /lib/utils/websocket.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | import 'dart:math'; 3 | import 'dart:convert'; 4 | import 'dart:async'; 5 | 6 | typedef void OnMessageCallback(dynamic msg); 7 | typedef void OnCloseCallback(int code, String reason); 8 | typedef void OnOpenCallback(); 9 | 10 | class SimpleWebSocket { 11 | String _url; 12 | var _socket; 13 | OnOpenCallback onOpen; 14 | OnMessageCallback onMessage; 15 | OnCloseCallback onClose; 16 | SimpleWebSocket(this._url); 17 | 18 | connect() async { 19 | try { 20 | //_socket = await WebSocket.connect(_url); 21 | _socket = await _connectForSelfSignedCert(_url); 22 | this?.onOpen(); 23 | _socket.listen((data) { 24 | this?.onMessage(data); 25 | }, onDone: () { 26 | this?.onClose(_socket.closeCode, _socket.closeReason); 27 | }); 28 | } catch (e) { 29 | this.onClose(500, e.toString()); 30 | } 31 | } 32 | 33 | send(data) { 34 | if (_socket != null) { 35 | _socket.add(data); 36 | print('send: $data'); 37 | } 38 | } 39 | 40 | close() { 41 | if (_socket != null) _socket.close(); 42 | } 43 | 44 | Future _connectForSelfSignedCert(url) async { 45 | try { 46 | Random r = new Random(); 47 | String key = base64.encode(List.generate(8, (_) => r.nextInt(255))); 48 | HttpClient client = HttpClient(context: SecurityContext()); 49 | client.badCertificateCallback = 50 | (X509Certificate cert, String host, int port) { 51 | print( 52 | 'SimpleWebSocket: Allow self-signed certificate => $host:$port. '); 53 | return true; 54 | }; 55 | 56 | HttpClientRequest request = 57 | await client.getUrl(Uri.parse(url)); // form the correct url here 58 | request.headers.add('Connection', 'Upgrade'); 59 | request.headers.add('Upgrade', 'websocket'); 60 | request.headers.add( 61 | 'Sec-WebSocket-Version', '13'); // insert the correct version here 62 | request.headers.add('Sec-WebSocket-Key', key.toLowerCase()); 63 | 64 | HttpClientResponse response = await request.close(); 65 | // ignore: close_sinks 66 | Socket socket = await response.detachSocket(); 67 | var webSocket = WebSocket.fromUpgradedSocket( 68 | socket, 69 | protocol: 'signaling', 70 | serverSide: false, 71 | ); 72 | 73 | return webSocket; 74 | } catch (e) { 75 | throw e; 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: openemr 2 | description: A new Flutter project. 3 | 4 | version: 2.2+5 5 | 6 | environment: 7 | sdk: '>=2.3.0 <3.0.0' 8 | 9 | dependencies: 10 | json_store: ^1.1.0 11 | flutter: 12 | sdk: flutter 13 | getwidget: ^1.2.4 14 | webview_flutter: ^0.3.19+6 15 | cupertino_icons: ^0.1.2 16 | charts_flutter: ^0.9.0 17 | wakelock: ^0.1.4+1 18 | image_picker: ^0.6.7+22 19 | dash_chat: ^1.1.8 20 | camera: ^0.6.4+3 21 | barcode_scan: ^3.0.1 22 | shared_preferences: ^0.5.8 23 | firebase_storage: ^3.1.6 24 | cloud_firestore: ^0.13.7 25 | firebase_auth: ^0.16.1 26 | flutter_webrtc: ^0.2.8 27 | google_sign_in: ^4.4.0 28 | flutter_auth_buttons: ^0.8.0 29 | firebase_ml_vision: ^0.9.6+2 30 | http: ^0.12.2 31 | modal_progress_hud: ^0.1.3 32 | 33 | dev_dependencies: 34 | flutter_test: 35 | sdk: flutter 36 | 37 | flutter: 38 | uses-material-design: true 39 | assets: 40 | - lib/assets/images/image.png 41 | - lib/assets/images/image1.png 42 | - lib/assets/images/red.png 43 | - lib/assets/images/purple.png 44 | - lib/assets/images/pink.png 45 | - lib/assets/images/orange.png 46 | - lib/assets/images/image2.png 47 | - lib/assets/images/img.png 48 | - lib/assets/images/img1.png 49 | - lib/assets/images/img2.png 50 | - lib/assets/images/card.png 51 | - lib/assets/images/card1.png 52 | - lib/assets/images/card2.png 53 | - lib/assets/images/card3.png 54 | - lib/assets/images/card5.png 55 | - lib/assets/images/card4.png 56 | - lib/assets/images/gflogo.png 57 | - lib/assets/images/avatar1.png 58 | - lib/assets/images/avatar2.png 59 | - lib/assets/images/avatar3.png 60 | - lib/assets/images/avatar4.png 61 | - lib/assets/images/avatar5.png 62 | - lib/assets/images/avatar6.png 63 | - lib/assets/images/avatar7.png 64 | - lib/assets/images/avatar8.png 65 | - lib/assets/images/avatar9.png 66 | - lib/assets/images/avatar10.png 67 | - lib/assets/images/avatar11.png 68 | - lib/assets/images/avatar12.png 69 | - lib/assets/images/firebase.png 70 | - lib/assets/gif/success1.gif 71 | - lib/assets/icons/gflogo.png 72 | - lib/assets/images/logo.png 73 | 74 | fonts: 75 | - family: GFFontIcons 76 | fonts: 77 | - asset: lib/assets/fonts/gfFontIcon.ttf 78 | weight: 400 79 | 80 | - family: GFFontIcons2 81 | fonts: 82 | - asset: lib/assets/fonts/gfFontIcons2.ttf 83 | weight: 400 84 | 85 | - family: GFSocialFonts 86 | fonts: 87 | - asset: lib/assets/fonts/gfSocialFonts.ttf 88 | weight: 400 89 | 90 | - family: GFIconFonts 91 | fonts: 92 | - asset: lib/assets/fonts/gfIconFonts.ttf 93 | weight: 400 94 | 95 | - family: GFIcons 96 | fonts: 97 | - asset: lib/assets/fonts/loader.ttf 98 | weight: 400 -------------------------------------------------------------------------------- /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:openemr/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(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 | --------------------------------------------------------------------------------