├── .github ├── FUNDING.yml └── workflows │ └── main.yml ├── .gitignore ├── .metadata ├── LICENCE.md ├── README.md ├── analysis_options.yaml ├── android ├── .gitignore ├── app │ ├── build.gradle │ ├── google-services.json │ └── src │ │ ├── debug │ │ └── AndroidManifest.xml │ │ ├── main │ │ ├── AndroidManifest.xml │ │ ├── ic_launcher-playstore.png │ │ ├── java │ │ │ └── com │ │ │ │ └── groovinchip │ │ │ │ └── flutter │ │ │ │ └── callmanager │ │ │ │ └── MainActivity.java │ │ ├── launcher_icon-web.png │ │ └── res │ │ │ ├── drawable-hdpi │ │ │ ├── ic_notification.png │ │ │ └── ic_stat_phone_in_talk.png │ │ │ ├── drawable-mdpi │ │ │ ├── ic_notification.png │ │ │ └── ic_stat_phone_in_talk.png │ │ │ ├── drawable-v21 │ │ │ └── launch_background.xml │ │ │ ├── drawable-xhdpi │ │ │ ├── ic_notification.png │ │ │ └── ic_stat_phone_in_talk.png │ │ │ ├── drawable-xxhdpi │ │ │ ├── ic_notification.png │ │ │ └── ic_stat_phone_in_talk.png │ │ │ ├── drawable-xxxhdpi │ │ │ └── ic_stat_phone_in_talk.png │ │ │ ├── drawable │ │ │ ├── ic_launcher_foreground.xml │ │ │ └── launch_background.xml │ │ │ ├── mipmap-anydpi-v26 │ │ │ ├── ic_launcher.xml │ │ │ ├── ic_launcher_round.xml │ │ │ ├── launcher_icon.xml │ │ │ └── launcher_icon_round.xml │ │ │ ├── mipmap-hdpi │ │ │ ├── ic_launcher.png │ │ │ ├── ic_launcher_foreground.png │ │ │ ├── ic_launcher_round.png │ │ │ ├── launcher_icon.png │ │ │ └── launcher_icon_round.png │ │ │ ├── mipmap-mdpi │ │ │ ├── ic_launcher.png │ │ │ ├── ic_launcher_foreground.png │ │ │ ├── ic_launcher_round.png │ │ │ ├── launcher_icon.png │ │ │ └── launcher_icon_round.png │ │ │ ├── mipmap-xhdpi │ │ │ ├── ic_launcher.png │ │ │ ├── ic_launcher_foreground.png │ │ │ ├── ic_launcher_round.png │ │ │ ├── launcher_icon.png │ │ │ └── launcher_icon_round.png │ │ │ ├── mipmap-xxhdpi │ │ │ ├── ic_launcher.png │ │ │ ├── ic_launcher_foreground.png │ │ │ ├── ic_launcher_round.png │ │ │ ├── launcher_icon.png │ │ │ └── launcher_icon_round.png │ │ │ ├── mipmap-xxxhdpi │ │ │ ├── ic_launcher.png │ │ │ ├── ic_launcher_foreground.png │ │ │ ├── ic_launcher_round.png │ │ │ ├── launcher_icon.png │ │ │ └── launcher_icon_round.png │ │ │ ├── values-night │ │ │ └── styles.xml │ │ │ └── values │ │ │ ├── colors.xml │ │ │ ├── ic_launcher_background.xml │ │ │ └── styles.xml │ │ └── profile │ │ └── AndroidManifest.xml ├── build.gradle ├── gradle.properties ├── gradle │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── settings.gradle └── settings_aar.gradle ├── assets ├── glogo.png └── icon │ └── call_manager_app_icon.png ├── fonts ├── SourceSansPro-Black.ttf ├── SourceSansPro-BlackItalic.ttf ├── SourceSansPro-Bold.ttf ├── SourceSansPro-BoldItalic.ttf ├── SourceSansPro-ExtraLight.ttf ├── SourceSansPro-ExtraLightItalic.ttf ├── SourceSansPro-Italic.ttf ├── SourceSansPro-Light.ttf ├── SourceSansPro-LightItalic.ttf ├── SourceSansPro-Regular.ttf ├── SourceSansPro-SemiBold.ttf └── SourceSansPro-SemiBoldItalic.ttf ├── ios ├── .gitignore ├── Flutter │ ├── AppFrameworkInfo.plist │ ├── Debug.xcconfig │ └── Release.xcconfig ├── Podfile ├── Podfile.lock ├── Runner.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ ├── IDEWorkspaceChecks.plist │ │ │ └── WorkspaceSettings.xcsettings │ └── xcshareddata │ │ └── xcschemes │ │ └── Runner.xcscheme ├── Runner.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ ├── IDEWorkspaceChecks.plist │ │ └── WorkspaceSettings.xcsettings └── Runner │ ├── AppDelegate.swift │ ├── Assets.xcassets │ ├── AppIcon.appiconset │ │ ├── Contents.json │ │ ├── icon_1024@1x.png │ │ ├── icon_20@1x.png │ │ ├── icon_20@2x-1.png │ │ ├── icon_20@2x.png │ │ ├── icon_20@3x.png │ │ ├── icon_29@1x-1.png │ │ ├── icon_29@1x.png │ │ ├── icon_29@2x-1.png │ │ ├── icon_29@2x.png │ │ ├── icon_29@3x.png │ │ ├── icon_40@1x.png │ │ ├── icon_40@2x-1.png │ │ ├── icon_40@2x.png │ │ ├── icon_40@3x.png │ │ ├── icon_60@2x.png │ │ ├── icon_60@3x.png │ │ ├── icon_76@1x.png │ │ ├── icon_76@2x.png │ │ └── icon_83.5@2x.png │ ├── Contents.json │ └── LaunchImage.imageset │ │ ├── Contents.json │ │ ├── LaunchImage.png │ │ ├── LaunchImage@2x.png │ │ ├── LaunchImage@3x.png │ │ └── README.md │ ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard │ ├── GoogleService-Info.plist │ ├── Info.plist │ ├── Runner-Bridging-Header.h │ └── Runner.entitlements ├── lib ├── app.dart ├── data_models │ └── call.dart ├── firebase │ ├── fb_type_aliases.dart │ ├── firebase.dart │ ├── firebase_auth_extensions.dart │ ├── firebase_mixin.dart │ └── firestore_extensions.dart ├── generated_plugin_registrant.dart ├── main.dart ├── provided.dart ├── screens │ ├── edit_call_screen.dart │ ├── home_screen.dart │ ├── login_screen.dart │ └── new_call_screen.dart ├── services │ ├── contacts_utility.dart │ ├── notifications_service.dart │ ├── phone_utility.dart │ └── prefs_service.dart ├── theme │ ├── app_colors.dart │ └── app_themes.dart ├── utils │ ├── extensions.dart │ └── pass_notification.dart └── widgets │ ├── call_avatar.dart │ ├── call_card.dart │ ├── calls_view.dart │ ├── clear_button.dart │ ├── contact_avatar.dart │ ├── contact_tile.dart │ ├── dialogs │ ├── complete_call_dialog.dart │ ├── delete_all_dialog.dart │ ├── delete_call_dialog.dart │ ├── log_out_dialog.dart │ ├── mark_incomplete_dialog.dart │ └── theme_switcher_dialog.dart │ ├── menu_bottom_sheet.dart │ ├── multiple_phone_numbers_sheet.dart │ ├── schedule_notification_sheet.dart │ ├── theme_icon.dart │ └── user_account_avatar.dart ├── macos ├── .gitignore ├── Flutter │ ├── Flutter-Debug.xcconfig │ ├── Flutter-Release.xcconfig │ └── GeneratedPluginRegistrant.swift ├── Podfile ├── Runner.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ └── IDEWorkspaceChecks.plist │ └── xcshareddata │ │ └── xcschemes │ │ └── Runner.xcscheme ├── Runner.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist └── Runner │ ├── AppDelegate.swift │ ├── Assets.xcassets │ └── AppIcon.appiconset │ │ ├── Contents.json │ │ ├── app_icon_1024.png │ │ ├── app_icon_128.png │ │ ├── app_icon_16.png │ │ ├── app_icon_256.png │ │ ├── app_icon_32.png │ │ ├── app_icon_512.png │ │ └── app_icon_64.png │ ├── Base.lproj │ └── MainMenu.xib │ ├── Configs │ ├── AppInfo.xcconfig │ ├── Debug.xcconfig │ ├── Release.xcconfig │ └── Warnings.xcconfig │ ├── DebugProfile.entitlements │ ├── Info.plist │ ├── MainFlutterWindow.swift │ └── Release.entitlements ├── privacy policy.txt ├── pubspec.lock ├── pubspec.yaml ├── test └── widget_test.dart └── web ├── favicon.png ├── icons ├── Icon-192.png └── Icon-512.png ├── index.html └── manifest.json /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: [GroovinChip] 4 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: Call Manager CI 2 | on: 3 | push: 4 | branches: [ master ] 5 | pull_request: 6 | branches: [ master ] 7 | jobs: 8 | build: 9 | name: Build APK 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: base64-to-file 13 | id: write_file 14 | uses: timheuer/base64-to-file@v1 15 | with: 16 | fileName: 'call_manager_key.jks' 17 | encodedString: ${{ secrets.SIGNING_KEY }} 18 | - uses: actions/checkout@v2 19 | - uses: actions/setup-java@v1 20 | with: 21 | java-version: '12.x' 22 | env: 23 | SIGNING_KEY: ${{ secrets.SIGNING_KEY }} 24 | - uses: subosito/flutter-action@v1 25 | with: 26 | flutter-version: '2.0.3' 27 | fileLocation: ${{ steps.write_file.outputs.filePath }} 28 | - run: flutter pub get 29 | - run: flutter analyze 30 | # - run: flutter build apk --split-per-abi 31 | # env: 32 | # KEY_STORE_PASSWORD: ${{ secrets.KEY_STORE_PASSWORD }} 33 | # KEY_PASSWORD: ${{ secrets.KEY_PASSWORD }} 34 | # ALIAS: ${{ secrets.ALIAS }} 35 | # KEY_PATH: ${{ steps.write_file.outputs.filePath }} 36 | # - name: Create a Release APK 37 | # uses: ncipollo/release-action@v1 38 | # with: 39 | # artifacts: "build/app/outputs/flutter-apk/*.apk" 40 | # token: ${{ secrets.TOKEN }} -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .dart_tool/ 3 | 4 | .packages 5 | .pub/ 6 | .idea/ 7 | 8 | build/ 9 | 10 | .flutter-plugins 11 | .flutter-plugins-dependencies 12 | *android/app/call_manager_key.jks 13 | android/key.properties 14 | *.iml 15 | /metrics 16 | .vscode/launch.json 17 | detective_connect.txt 18 | 19 | *.patch 20 | .vscode/launch.json 21 | -------------------------------------------------------------------------------- /.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: f9bb4289e9fd861d70ae78bcc3a042ef1b35cc9d 8 | channel: beta 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Call Manager 2 | 3 | Call Manager is a cross-platform application for scheduling and keeping track of phone calls. Think of it as a "to-do" list, dedicated to phone calls. 4 | 5 | Features: 6 | - Add phone calls to your list as Call Cards. Call Cards have one-tap actions for accomplishing tasks for that call, like: 7 | - Call 8 | - Send SMS 9 | - Edit Call 10 | - Set Notification Reminder 11 | - Delete Call 12 | - Clean, simple interface 13 | - Light/dark/system themes 14 | 15 | Call Manager is available on the Google Play Store and the Apple App Store. It is compatible with Apple Silicon, and a dedicated macOS version will be coming later this year. You can also download the app right from the Releases section of this repository. 16 | 17 | Get it on Google Play 18 | 19 | Download on the App Store 20 | 21 | Google Play and the Google Play logo are trademarks of Google LLC. 22 | 23 | ## Contribute 24 | If you would like to contribute to this application: 25 | 1. Fork the repository 26 | 2. Clone the repository to your machine 27 | 3. Create an app in the Firebase console, add your SHA1 key, and add the google-services.json file to your android/app directory 28 | 4. Make your changes 29 | 5. Open a pull request 30 | -------------------------------------------------------------------------------- /analysis_options.yaml: -------------------------------------------------------------------------------- 1 | include: package:flutter_lints/flutter.yaml 2 | 3 | linter: 4 | rules: 5 | - prefer_single_quotes 6 | - unnecessary_new 7 | - prefer_contains 8 | - package_names 9 | - non_constant_identifier_names 10 | - library_prefixes 11 | - library_names 12 | - implementation_imports 13 | - file_names 14 | - camel_case_extensions 15 | - camel_case_types 16 | - avoid_empty_else 17 | - avoid_classes_with_only_static_members 18 | - always_declare_return_types 19 | - always_require_non_null_named_parameters 20 | 21 | analyzer: 22 | exclude: 23 | - ios/** 24 | - macos/** 25 | plugins: 26 | - dart_code_metrics 27 | 28 | dart_code_metrics: 29 | anti-patterns: 30 | - long-method 31 | - long-parameter-list 32 | metrics: 33 | cyclomatic-complexity: 20 34 | lines-of-executable-code: 50 35 | number-of-arguments: 4 36 | maximum-nesting: 5 37 | metrics-exclude: 38 | - test/** 39 | rules: 40 | - newline-before-return 41 | - no-boolean-literal-compare 42 | - prefer-trailing-comma 43 | - no-equal-then-else 44 | - potential-null-dereference 45 | - member-ordering: 46 | alphabetize: true 47 | order: 48 | - constructors 49 | - public_fields 50 | - public_setters 51 | - private_fields 52 | -------------------------------------------------------------------------------- /android/.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | *.class 3 | .gradle 4 | /local.properties 5 | /.idea/workspace.xml 6 | /.idea/libraries 7 | .DS_Store 8 | /build 9 | /captures 10 | GeneratedPluginRegistrant.java 11 | -------------------------------------------------------------------------------- /android/app/build.gradle: -------------------------------------------------------------------------------- 1 | def localProperties = new Properties() 2 | def localPropertiesFile = rootProject.file('local.properties') 3 | if (localPropertiesFile.exists()) { 4 | localPropertiesFile.withReader('UTF-8') { reader -> 5 | localProperties.load(reader) 6 | } 7 | } 8 | 9 | def flutterRoot = localProperties.getProperty('flutter.sdk') 10 | if (flutterRoot == null) { 11 | throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") 12 | } 13 | 14 | def flutterVersionCode = localProperties.getProperty('flutter.versionCode') 15 | if (flutterVersionCode == null) { 16 | flutterVersionCode = '1' 17 | } 18 | 19 | def flutterVersionName = localProperties.getProperty('flutter.versionName') 20 | if (flutterVersionName == null) { 21 | flutterVersionName = '1.0' 22 | } 23 | 24 | apply plugin: 'com.android.application' 25 | apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" 26 | 27 | def keystoreProperties = new Properties() 28 | def keystorePropertiesFile = rootProject.file('key.properties') 29 | if (keystorePropertiesFile.exists()) { 30 | keystoreProperties.load(new FileInputStream(keystorePropertiesFile)) 31 | } else { 32 | keystoreProperties.setProperty('storePassword', System.getenv('KEY_STORE_PASSWORD')); 33 | keystoreProperties.setProperty('keyPassword', System.getenv('KEY_PASSWORD')); 34 | keystoreProperties.setProperty('keyAlias', System.getenv('ALIAS')); 35 | keystoreProperties.setProperty('storeFile', System.getenv('KEY_PATH')); 36 | } 37 | 38 | android { 39 | compileSdkVersion 31 40 | 41 | lintOptions { 42 | disable 'InvalidPackage' 43 | } 44 | 45 | defaultConfig { 46 | applicationId "com.groovinchip.flutter.callmanager" 47 | minSdkVersion 21 48 | targetSdkVersion 31 49 | versionCode flutterVersionCode.toInteger() 50 | versionName flutterVersionName 51 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 52 | } 53 | 54 | signingConfigs { 55 | release { 56 | keyAlias keystoreProperties['keyAlias'] 57 | keyPassword keystoreProperties['keyPassword'] 58 | storeFile keystoreProperties['storeFile'] ? file(keystoreProperties['storeFile']) : null 59 | storePassword keystoreProperties['storePassword'] 60 | } 61 | } 62 | 63 | 64 | buildTypes { 65 | release { 66 | signingConfig signingConfigs.release 67 | } 68 | } 69 | } 70 | 71 | flutter { 72 | source '../..' 73 | } 74 | 75 | dependencies { 76 | testImplementation 'junit:junit:4.12' 77 | androidTestImplementation 'androidx.test:runner:1.1.0' 78 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.0' 79 | } 80 | 81 | apply plugin: 'com.google.gms.google-services' 82 | -------------------------------------------------------------------------------- /android/app/google-services.json: -------------------------------------------------------------------------------- 1 | { 2 | "project_info": { 3 | "project_number": "1053316160376", 4 | "firebase_url": "https://call-manager-flutter-db.firebaseio.com", 5 | "project_id": "call-manager-flutter-db", 6 | "storage_bucket": "call-manager-flutter-db.appspot.com" 7 | }, 8 | "client": [ 9 | { 10 | "client_info": { 11 | "mobilesdk_app_id": "1:1053316160376:android:9e3c10e86de5057f", 12 | "android_client_info": { 13 | "package_name": "com.groovinchip.flutter.callmanager" 14 | } 15 | }, 16 | "oauth_client": [ 17 | { 18 | "client_id": "1053316160376-5juugktuaas98do08thao9iplqmr5fn9.apps.googleusercontent.com", 19 | "client_type": 1, 20 | "android_info": { 21 | "package_name": "com.groovinchip.flutter.callmanager", 22 | "certificate_hash": "11743c6d989da7c5fc95a69f9f8851ef57c3c40b" 23 | } 24 | }, 25 | { 26 | "client_id": "1053316160376-a5ophi33ioqa0sp0efc1g9g1p3dug62c.apps.googleusercontent.com", 27 | "client_type": 1, 28 | "android_info": { 29 | "package_name": "com.groovinchip.flutter.callmanager", 30 | "certificate_hash": "bd512282d3192eb10c311691a907423947cff8fc" 31 | } 32 | }, 33 | { 34 | "client_id": "1053316160376-e77pbuh7scqba2fgae9fi0r5dt0nerkv.apps.googleusercontent.com", 35 | "client_type": 1, 36 | "android_info": { 37 | "package_name": "com.groovinchip.flutter.callmanager", 38 | "certificate_hash": "902c3abbcc1f45630ea31837eb0492c17f730b4c" 39 | } 40 | }, 41 | { 42 | "client_id": "1053316160376-kefg5rq4moi87ppeoh3orf264ap2a2id.apps.googleusercontent.com", 43 | "client_type": 1, 44 | "android_info": { 45 | "package_name": "com.groovinchip.flutter.callmanager", 46 | "certificate_hash": "59ed53cd18e0211dfcb13e67785c58464fc8ea2a" 47 | } 48 | }, 49 | { 50 | "client_id": "1053316160376-llj2t9oec98dl6mfs7rfdc3ll3hqravv.apps.googleusercontent.com", 51 | "client_type": 1, 52 | "android_info": { 53 | "package_name": "com.groovinchip.flutter.callmanager", 54 | "certificate_hash": "2428f1f3f6060ea4c36607835d31be2c9429b2c1" 55 | } 56 | }, 57 | { 58 | "client_id": "1053316160376-lta90e93askpe441ela6neshdl352j2k.apps.googleusercontent.com", 59 | "client_type": 1, 60 | "android_info": { 61 | "package_name": "com.groovinchip.flutter.callmanager", 62 | "certificate_hash": "3583c159206c49e1c0499a37c83a15ec3df645a1" 63 | } 64 | }, 65 | { 66 | "client_id": "1053316160376-uc75rivms55uvv7pa54eb4qgp6vpcr07.apps.googleusercontent.com", 67 | "client_type": 1, 68 | "android_info": { 69 | "package_name": "com.groovinchip.flutter.callmanager", 70 | "certificate_hash": "ffae666c77cb4019976e48a903cc3c235875f9f6" 71 | } 72 | }, 73 | { 74 | "client_id": "1053316160376-oe9l5ae2aigre7b7i4a5uqbsbn3scmgg.apps.googleusercontent.com", 75 | "client_type": 3 76 | } 77 | ], 78 | "api_key": [ 79 | { 80 | "current_key": "AIzaSyDs2Vch4b2plINC3gqZ3Ksf1GAtquIlvnA" 81 | } 82 | ], 83 | "services": { 84 | "appinvite_service": { 85 | "other_platform_oauth_client": [ 86 | { 87 | "client_id": "1053316160376-oe9l5ae2aigre7b7i4a5uqbsbn3scmgg.apps.googleusercontent.com", 88 | "client_type": 3 89 | }, 90 | { 91 | "client_id": "1053316160376-117rp70a5ps0s6nk98th54trgsacoaim.apps.googleusercontent.com", 92 | "client_type": 2, 93 | "ios_info": { 94 | "bundle_id": "dev.groovinchip.callmanager" 95 | } 96 | } 97 | ] 98 | } 99 | } 100 | } 101 | ], 102 | "configuration_version": "1" 103 | } -------------------------------------------------------------------------------- /android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 20 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 32 | 35 | 43 | 44 | 45 | 46 | 47 | 48 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /android/app/src/main/ic_launcher-playstore.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GroovinChip/call_manager/abf78a0ee52784b7dc352cf0cbbb9428fbe6d2fb/android/app/src/main/ic_launcher-playstore.png -------------------------------------------------------------------------------- /android/app/src/main/java/com/groovinchip/flutter/callmanager/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.groovinchip.flutter.callmanager; 2 | 3 | import io.flutter.embedding.android.FlutterActivity; 4 | 5 | public class MainActivity extends FlutterActivity { 6 | 7 | } 8 | -------------------------------------------------------------------------------- /android/app/src/main/launcher_icon-web.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GroovinChip/call_manager/abf78a0ee52784b7dc352cf0cbbb9428fbe6d2fb/android/app/src/main/launcher_icon-web.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-hdpi/ic_notification.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GroovinChip/call_manager/abf78a0ee52784b7dc352cf0cbbb9428fbe6d2fb/android/app/src/main/res/drawable-hdpi/ic_notification.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-hdpi/ic_stat_phone_in_talk.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GroovinChip/call_manager/abf78a0ee52784b7dc352cf0cbbb9428fbe6d2fb/android/app/src/main/res/drawable-hdpi/ic_stat_phone_in_talk.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-mdpi/ic_notification.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GroovinChip/call_manager/abf78a0ee52784b7dc352cf0cbbb9428fbe6d2fb/android/app/src/main/res/drawable-mdpi/ic_notification.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-mdpi/ic_stat_phone_in_talk.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GroovinChip/call_manager/abf78a0ee52784b7dc352cf0cbbb9428fbe6d2fb/android/app/src/main/res/drawable-mdpi/ic_stat_phone_in_talk.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-v21/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-xhdpi/ic_notification.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GroovinChip/call_manager/abf78a0ee52784b7dc352cf0cbbb9428fbe6d2fb/android/app/src/main/res/drawable-xhdpi/ic_notification.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-xhdpi/ic_stat_phone_in_talk.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GroovinChip/call_manager/abf78a0ee52784b7dc352cf0cbbb9428fbe6d2fb/android/app/src/main/res/drawable-xhdpi/ic_stat_phone_in_talk.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-xxhdpi/ic_notification.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GroovinChip/call_manager/abf78a0ee52784b7dc352cf0cbbb9428fbe6d2fb/android/app/src/main/res/drawable-xxhdpi/ic_notification.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-xxhdpi/ic_stat_phone_in_talk.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GroovinChip/call_manager/abf78a0ee52784b7dc352cf0cbbb9428fbe6d2fb/android/app/src/main/res/drawable-xxhdpi/ic_stat_phone_in_talk.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-xxxhdpi/ic_stat_phone_in_talk.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GroovinChip/call_manager/abf78a0ee52784b7dc352cf0cbbb9428fbe6d2fb/android/app/src/main/res/drawable-xxxhdpi/ic_stat_phone_in_talk.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 9 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-anydpi-v26/launcher_icon.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-anydpi-v26/launcher_icon_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GroovinChip/call_manager/abf78a0ee52784b7dc352cf0cbbb9428fbe6d2fb/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GroovinChip/call_manager/abf78a0ee52784b7dc352cf0cbbb9428fbe6d2fb/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GroovinChip/call_manager/abf78a0ee52784b7dc352cf0cbbb9428fbe6d2fb/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-hdpi/launcher_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GroovinChip/call_manager/abf78a0ee52784b7dc352cf0cbbb9428fbe6d2fb/android/app/src/main/res/mipmap-hdpi/launcher_icon.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-hdpi/launcher_icon_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GroovinChip/call_manager/abf78a0ee52784b7dc352cf0cbbb9428fbe6d2fb/android/app/src/main/res/mipmap-hdpi/launcher_icon_round.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GroovinChip/call_manager/abf78a0ee52784b7dc352cf0cbbb9428fbe6d2fb/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GroovinChip/call_manager/abf78a0ee52784b7dc352cf0cbbb9428fbe6d2fb/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GroovinChip/call_manager/abf78a0ee52784b7dc352cf0cbbb9428fbe6d2fb/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-mdpi/launcher_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GroovinChip/call_manager/abf78a0ee52784b7dc352cf0cbbb9428fbe6d2fb/android/app/src/main/res/mipmap-mdpi/launcher_icon.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-mdpi/launcher_icon_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GroovinChip/call_manager/abf78a0ee52784b7dc352cf0cbbb9428fbe6d2fb/android/app/src/main/res/mipmap-mdpi/launcher_icon_round.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GroovinChip/call_manager/abf78a0ee52784b7dc352cf0cbbb9428fbe6d2fb/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GroovinChip/call_manager/abf78a0ee52784b7dc352cf0cbbb9428fbe6d2fb/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GroovinChip/call_manager/abf78a0ee52784b7dc352cf0cbbb9428fbe6d2fb/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xhdpi/launcher_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GroovinChip/call_manager/abf78a0ee52784b7dc352cf0cbbb9428fbe6d2fb/android/app/src/main/res/mipmap-xhdpi/launcher_icon.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xhdpi/launcher_icon_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GroovinChip/call_manager/abf78a0ee52784b7dc352cf0cbbb9428fbe6d2fb/android/app/src/main/res/mipmap-xhdpi/launcher_icon_round.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GroovinChip/call_manager/abf78a0ee52784b7dc352cf0cbbb9428fbe6d2fb/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GroovinChip/call_manager/abf78a0ee52784b7dc352cf0cbbb9428fbe6d2fb/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GroovinChip/call_manager/abf78a0ee52784b7dc352cf0cbbb9428fbe6d2fb/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxhdpi/launcher_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GroovinChip/call_manager/abf78a0ee52784b7dc352cf0cbbb9428fbe6d2fb/android/app/src/main/res/mipmap-xxhdpi/launcher_icon.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxhdpi/launcher_icon_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GroovinChip/call_manager/abf78a0ee52784b7dc352cf0cbbb9428fbe6d2fb/android/app/src/main/res/mipmap-xxhdpi/launcher_icon_round.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GroovinChip/call_manager/abf78a0ee52784b7dc352cf0cbbb9428fbe6d2fb/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GroovinChip/call_manager/abf78a0ee52784b7dc352cf0cbbb9428fbe6d2fb/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GroovinChip/call_manager/abf78a0ee52784b7dc352cf0cbbb9428fbe6d2fb/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxxhdpi/launcher_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GroovinChip/call_manager/abf78a0ee52784b7dc352cf0cbbb9428fbe6d2fb/android/app/src/main/res/mipmap-xxxhdpi/launcher_icon.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxxhdpi/launcher_icon_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GroovinChip/call_manager/abf78a0ee52784b7dc352cf0cbbb9428fbe6d2fb/android/app/src/main/res/mipmap-xxxhdpi/launcher_icon_round.png -------------------------------------------------------------------------------- /android/app/src/main/res/values-night/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /android/app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #2962ff 4 | -------------------------------------------------------------------------------- /android/app/src/main/res/values/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #2962FF 4 | -------------------------------------------------------------------------------- /android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | -------------------------------------------------------------------------------- /android/app/src/profile/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | repositories { 3 | google() 4 | mavenCentral() 5 | } 6 | 7 | dependencies { 8 | classpath 'com.android.tools.build:gradle:4.2.2' 9 | //noinspection GradleDependency 10 | classpath 'com.google.gms:google-services:4.3.8' 11 | } 12 | } 13 | 14 | allprojects { 15 | repositories { 16 | google() 17 | mavenCentral() 18 | } 19 | } 20 | 21 | rootProject.buildDir = '../build' 22 | subprojects { 23 | project.buildDir = "${rootProject.buildDir}/${project.name}" 24 | } 25 | subprojects { 26 | project.evaluationDependsOn(':app') 27 | } 28 | subprojects { 29 | project.configurations.all { 30 | resolutionStrategy.eachDependency { details -> 31 | if (details.requested.group == 'com.android.support' 32 | && !details.requested.name.contains('multidex') ) { 33 | details.useVersion "26.1.0" 34 | } 35 | } 36 | } 37 | } 38 | 39 | task clean(type: Delete) { 40 | delete rootProject.buildDir 41 | } 42 | -------------------------------------------------------------------------------- /android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx1536M 2 | android.useAndroidX=true 3 | android.enableJetifier=true -------------------------------------------------------------------------------- /android/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GroovinChip/call_manager/abf78a0ee52784b7dc352cf0cbbb9428fbe6d2fb/android/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Fri Jun 23 08:50:38 CEST 2017 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-6.7.1-all.zip 7 | -------------------------------------------------------------------------------- /android/gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 10 | DEFAULT_JVM_OPTS="" 11 | 12 | APP_NAME="Gradle" 13 | APP_BASE_NAME=`basename "$0"` 14 | 15 | # Use the maximum available, or set MAX_FD != -1 to use that value. 16 | MAX_FD="maximum" 17 | 18 | warn ( ) { 19 | echo "$*" 20 | } 21 | 22 | die ( ) { 23 | echo 24 | echo "$*" 25 | echo 26 | exit 1 27 | } 28 | 29 | # OS specific support (must be 'true' or 'false'). 30 | cygwin=false 31 | msys=false 32 | darwin=false 33 | case "`uname`" in 34 | CYGWIN* ) 35 | cygwin=true 36 | ;; 37 | Darwin* ) 38 | darwin=true 39 | ;; 40 | MINGW* ) 41 | msys=true 42 | ;; 43 | esac 44 | 45 | # Attempt to set APP_HOME 46 | # Resolve links: $0 may be a link 47 | PRG="$0" 48 | # Need this for relative symlinks. 49 | while [ -h "$PRG" ] ; do 50 | ls=`ls -ld "$PRG"` 51 | link=`expr "$ls" : '.*-> \(.*\)$'` 52 | if expr "$link" : '/.*' > /dev/null; then 53 | PRG="$link" 54 | else 55 | PRG=`dirname "$PRG"`"/$link" 56 | fi 57 | done 58 | SAVED="`pwd`" 59 | cd "`dirname \"$PRG\"`/" >/dev/null 60 | APP_HOME="`pwd -P`" 61 | cd "$SAVED" >/dev/null 62 | 63 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 64 | 65 | # Determine the Java command to use to start the JVM. 66 | if [ -n "$JAVA_HOME" ] ; then 67 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 68 | # IBM's JDK on AIX uses strange locations for the executables 69 | JAVACMD="$JAVA_HOME/jre/sh/java" 70 | else 71 | JAVACMD="$JAVA_HOME/bin/java" 72 | fi 73 | if [ ! -x "$JAVACMD" ] ; then 74 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 75 | 76 | Please set the JAVA_HOME variable in your environment to match the 77 | location of your Java installation." 78 | fi 79 | else 80 | JAVACMD="java" 81 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 82 | 83 | Please set the JAVA_HOME variable in your environment to match the 84 | location of your Java installation." 85 | fi 86 | 87 | # Increase the maximum file descriptors if we can. 88 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then 89 | MAX_FD_LIMIT=`ulimit -H -n` 90 | if [ $? -eq 0 ] ; then 91 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 92 | MAX_FD="$MAX_FD_LIMIT" 93 | fi 94 | ulimit -n $MAX_FD 95 | if [ $? -ne 0 ] ; then 96 | warn "Could not set maximum file descriptor limit: $MAX_FD" 97 | fi 98 | else 99 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 100 | fi 101 | fi 102 | 103 | # For Darwin, add options to specify how the application appears in the dock 104 | if $darwin; then 105 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 106 | fi 107 | 108 | # For Cygwin, switch paths to Windows format before running java 109 | if $cygwin ; then 110 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 111 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 112 | JAVACMD=`cygpath --unix "$JAVACMD"` 113 | 114 | # We build the pattern for arguments to be converted via cygpath 115 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 116 | SEP="" 117 | for dir in $ROOTDIRSRAW ; do 118 | ROOTDIRS="$ROOTDIRS$SEP$dir" 119 | SEP="|" 120 | done 121 | OURCYGPATTERN="(^($ROOTDIRS))" 122 | # Add a user-defined pattern to the cygpath arguments 123 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 124 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 125 | fi 126 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 127 | i=0 128 | for arg in "$@" ; do 129 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 130 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 131 | 132 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 133 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 134 | else 135 | eval `echo args$i`="\"$arg\"" 136 | fi 137 | i=$((i+1)) 138 | done 139 | case $i in 140 | (0) set -- ;; 141 | (1) set -- "$args0" ;; 142 | (2) set -- "$args0" "$args1" ;; 143 | (3) set -- "$args0" "$args1" "$args2" ;; 144 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 145 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 146 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 147 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 148 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 149 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 150 | esac 151 | fi 152 | 153 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 154 | function splitJvmOpts() { 155 | JVM_OPTS=("$@") 156 | } 157 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 158 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 159 | 160 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 161 | -------------------------------------------------------------------------------- /android/gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 12 | set DEFAULT_JVM_OPTS= 13 | 14 | set DIRNAME=%~dp0 15 | if "%DIRNAME%" == "" set DIRNAME=. 16 | set APP_BASE_NAME=%~n0 17 | set APP_HOME=%DIRNAME% 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windowz variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /android/settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | 3 | def flutterProjectRoot = rootProject.projectDir.parentFile.toPath() 4 | 5 | def plugins = new Properties() 6 | def pluginsFile = new File(flutterProjectRoot.toFile(), '.flutter-plugins') 7 | if (pluginsFile.exists()) { 8 | pluginsFile.withReader('UTF-8') { reader -> plugins.load(reader) } 9 | } 10 | 11 | plugins.each { name, path -> 12 | def pluginDirectory = flutterProjectRoot.resolve(path).resolve('android').toFile() 13 | include ":$name" 14 | project(":$name").projectDir = pluginDirectory 15 | } 16 | -------------------------------------------------------------------------------- /android/settings_aar.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | -------------------------------------------------------------------------------- /assets/glogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GroovinChip/call_manager/abf78a0ee52784b7dc352cf0cbbb9428fbe6d2fb/assets/glogo.png -------------------------------------------------------------------------------- /assets/icon/call_manager_app_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GroovinChip/call_manager/abf78a0ee52784b7dc352cf0cbbb9428fbe6d2fb/assets/icon/call_manager_app_icon.png -------------------------------------------------------------------------------- /fonts/SourceSansPro-Black.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GroovinChip/call_manager/abf78a0ee52784b7dc352cf0cbbb9428fbe6d2fb/fonts/SourceSansPro-Black.ttf -------------------------------------------------------------------------------- /fonts/SourceSansPro-BlackItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GroovinChip/call_manager/abf78a0ee52784b7dc352cf0cbbb9428fbe6d2fb/fonts/SourceSansPro-BlackItalic.ttf -------------------------------------------------------------------------------- /fonts/SourceSansPro-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GroovinChip/call_manager/abf78a0ee52784b7dc352cf0cbbb9428fbe6d2fb/fonts/SourceSansPro-Bold.ttf -------------------------------------------------------------------------------- /fonts/SourceSansPro-BoldItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GroovinChip/call_manager/abf78a0ee52784b7dc352cf0cbbb9428fbe6d2fb/fonts/SourceSansPro-BoldItalic.ttf -------------------------------------------------------------------------------- /fonts/SourceSansPro-ExtraLight.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GroovinChip/call_manager/abf78a0ee52784b7dc352cf0cbbb9428fbe6d2fb/fonts/SourceSansPro-ExtraLight.ttf -------------------------------------------------------------------------------- /fonts/SourceSansPro-ExtraLightItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GroovinChip/call_manager/abf78a0ee52784b7dc352cf0cbbb9428fbe6d2fb/fonts/SourceSansPro-ExtraLightItalic.ttf -------------------------------------------------------------------------------- /fonts/SourceSansPro-Italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GroovinChip/call_manager/abf78a0ee52784b7dc352cf0cbbb9428fbe6d2fb/fonts/SourceSansPro-Italic.ttf -------------------------------------------------------------------------------- /fonts/SourceSansPro-Light.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GroovinChip/call_manager/abf78a0ee52784b7dc352cf0cbbb9428fbe6d2fb/fonts/SourceSansPro-Light.ttf -------------------------------------------------------------------------------- /fonts/SourceSansPro-LightItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GroovinChip/call_manager/abf78a0ee52784b7dc352cf0cbbb9428fbe6d2fb/fonts/SourceSansPro-LightItalic.ttf -------------------------------------------------------------------------------- /fonts/SourceSansPro-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GroovinChip/call_manager/abf78a0ee52784b7dc352cf0cbbb9428fbe6d2fb/fonts/SourceSansPro-Regular.ttf -------------------------------------------------------------------------------- /fonts/SourceSansPro-SemiBold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GroovinChip/call_manager/abf78a0ee52784b7dc352cf0cbbb9428fbe6d2fb/fonts/SourceSansPro-SemiBold.ttf -------------------------------------------------------------------------------- /fonts/SourceSansPro-SemiBoldItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GroovinChip/call_manager/abf78a0ee52784b7dc352cf0cbbb9428fbe6d2fb/fonts/SourceSansPro-SemiBoldItalic.ttf -------------------------------------------------------------------------------- /ios/.gitignore: -------------------------------------------------------------------------------- 1 | *.mode1v3 2 | *.mode2v3 3 | *.moved-aside 4 | *.pbxuser 5 | *.perspectivev3 6 | **/*sync/ 7 | .sconsign.dblite 8 | .tags* 9 | **/.vagrant/ 10 | **/DerivedData/ 11 | Icon? 12 | **/Pods/ 13 | **/.symlinks/ 14 | profile 15 | xcuserdata 16 | **/.generated/ 17 | Flutter/App.framework 18 | Flutter/Flutter.framework 19 | Flutter/Flutter.podspec 20 | Flutter/Generated.xcconfig 21 | Flutter/app.flx 22 | Flutter/app.zip 23 | Flutter/flutter_assets/ 24 | Flutter/flutter_export_environment.sh 25 | ServiceDefinitions.json 26 | Runner/GeneratedPluginRegistrant.* 27 | 28 | # Exceptions to above rules. 29 | !default.mode1v3 30 | !default.mode2v3 31 | !default.pbxuser 32 | !default.perspectivev3 33 | -------------------------------------------------------------------------------- /ios/Flutter/AppFrameworkInfo.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | App 9 | CFBundleIdentifier 10 | io.flutter.flutter.app 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | App 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1.0 23 | MinimumOSVersion 24 | 9.0 25 | 26 | 27 | -------------------------------------------------------------------------------- /ios/Flutter/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /ios/Flutter/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /ios/Podfile: -------------------------------------------------------------------------------- 1 | # Uncomment this line to define a global platform for your project 2 | platform :ios, '12.0' 3 | 4 | # CocoaPods analytics sends network stats synchronously affecting flutter build latency. 5 | ENV['COCOAPODS_DISABLE_STATS'] = 'true' 6 | 7 | project 'Runner', { 8 | 'Debug' => :debug, 9 | 'Profile' => :release, 10 | 'Release' => :release, 11 | } 12 | 13 | def flutter_root 14 | generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__) 15 | unless File.exist?(generated_xcode_build_settings_path) 16 | raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first" 17 | end 18 | 19 | File.foreach(generated_xcode_build_settings_path) do |line| 20 | matches = line.match(/FLUTTER_ROOT\=(.*)/) 21 | return matches[1].strip if matches 22 | end 23 | raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get" 24 | end 25 | 26 | require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) 27 | 28 | flutter_ios_podfile_setup 29 | 30 | target 'Runner' do 31 | use_frameworks! 32 | use_modular_headers! 33 | 34 | flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) 35 | pod 'FirebaseFirestore', :git => 'https://github.com/invertase/firestore-ios-sdk-frameworks.git', :tag => '8.7.0' 36 | end 37 | 38 | #post_install do |installer| 39 | # installer.pods_project.targets.each do |target| 40 | # flutter_additional_ios_build_settings(target) 41 | # end 42 | #end 43 | 44 | post_install do |installer| 45 | installer.pods_project.targets.each do |target| 46 | flutter_additional_ios_build_settings(target) 47 | target.build_configurations.each do |config| 48 | # Here are some configurations automatically generated by flutter 49 | 50 | # You can remove unused permissions here 51 | # for more infomation: https://github.com/BaseflowIT/flutter-permission-handler/blob/develop/permission_handler/ios/Classes/PermissionHandlerEnums.h 52 | # e.g. when you don't need camera permission, just add 'PERMISSION_CAMERA=0' 53 | config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] ||= [ 54 | '$(inherited)', 55 | 56 | ## dart: PermissionGroup.calendar 57 | 'PERMISSION_EVENTS=0', 58 | 59 | ## dart: PermissionGroup.reminders 60 | 'PERMISSION_REMINDERS=0', 61 | 62 | ## dart: PermissionGroup.contacts 63 | 'PERMISSION_CONTACTS=1', 64 | 65 | ## dart: PermissionGroup.camera 66 | 'PERMISSION_CAMERA=0', 67 | 68 | ## dart: PermissionGroup.microphone 69 | 'PERMISSION_MICROPHONE=0', 70 | 71 | ## dart: PermissionGroup.speech 72 | 'PERMISSION_SPEECH_RECOGNIZER=0', 73 | 74 | ## dart: PermissionGroup.photos 75 | 'PERMISSION_PHOTOS=0', 76 | 77 | ## dart: [PermissionGroup.location, PermissionGroup.locationAlways, PermissionGroup.locationWhenInUse] 78 | 'PERMISSION_LOCATION=0', 79 | 80 | ## dart: PermissionGroup.notification 81 | # 'PERMISSION_NOTIFICATIONS=0', 82 | 83 | ## dart: PermissionGroup.mediaLibrary 84 | 'PERMISSION_MEDIA_LIBRARY=0', 85 | 86 | ## dart: PermissionGroup.sensors 87 | 'PERMISSION_SENSORS=0', 88 | 89 | ## dart: PermissionGroup.bluetooth 90 | 'PERMISSION_BLUETOOTH=0' 91 | ] 92 | 93 | end 94 | end 95 | end 96 | -------------------------------------------------------------------------------- /ios/Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - AppAuth (1.4.0): 3 | - AppAuth/Core (= 1.4.0) 4 | - AppAuth/ExternalUserAgent (= 1.4.0) 5 | - AppAuth/Core (1.4.0) 6 | - AppAuth/ExternalUserAgent (1.4.0) 7 | - cloud_firestore (2.5.3): 8 | - Firebase/Firestore (= 8.7.0) 9 | - firebase_core 10 | - Flutter 11 | - contacts_service (0.2.2): 12 | - Flutter 13 | - device_info_plus (0.0.1): 14 | - Flutter 15 | - direct_dialer (0.0.1): 16 | - Flutter 17 | - Firebase/Auth (8.7.0): 18 | - Firebase/CoreOnly 19 | - FirebaseAuth (~> 8.7.0) 20 | - Firebase/CoreOnly (8.7.0): 21 | - FirebaseCore (= 8.7.0) 22 | - Firebase/Firestore (8.7.0): 23 | - Firebase/CoreOnly 24 | - FirebaseFirestore (~> 8.7.0) 25 | - firebase_auth (3.1.3): 26 | - Firebase/Auth (= 8.7.0) 27 | - firebase_core 28 | - Flutter 29 | - firebase_core (1.7.0): 30 | - Firebase/CoreOnly (= 8.7.0) 31 | - Flutter 32 | - FirebaseAuth (8.7.0): 33 | - FirebaseCore (~> 8.0) 34 | - GoogleUtilities/AppDelegateSwizzler (~> 7.4) 35 | - GoogleUtilities/Environment (~> 7.4) 36 | - GTMSessionFetcher/Core (~> 1.5) 37 | - FirebaseCore (8.7.0): 38 | - FirebaseCoreDiagnostics (~> 8.0) 39 | - GoogleUtilities/Environment (~> 7.4) 40 | - GoogleUtilities/Logger (~> 7.4) 41 | - FirebaseCoreDiagnostics (8.8.0): 42 | - GoogleDataTransport (~> 9.0) 43 | - GoogleUtilities/Environment (~> 7.4) 44 | - GoogleUtilities/Logger (~> 7.4) 45 | - nanopb (~> 2.30908.0) 46 | - FirebaseFirestore (8.7.0): 47 | - FirebaseFirestore/AutodetectLeveldb (= 8.7.0) 48 | - FirebaseFirestore/AutodetectLeveldb (8.7.0): 49 | - FirebaseFirestore/Base 50 | - FirebaseFirestore/WithLeveldb 51 | - FirebaseFirestore/Base (8.7.0) 52 | - FirebaseFirestore/WithLeveldb (8.7.0): 53 | - FirebaseFirestore/Base 54 | - Flutter (1.0.0) 55 | - flutter_keyboard_visibility (0.0.1): 56 | - Flutter 57 | - flutter_local_notifications (0.0.1): 58 | - Flutter 59 | - google_sign_in (0.0.1): 60 | - Flutter 61 | - GoogleSignIn (~> 5.0) 62 | - GoogleDataTransport (9.1.0): 63 | - GoogleUtilities/Environment (~> 7.2) 64 | - nanopb (~> 2.30908.0) 65 | - PromisesObjC (< 3.0, >= 1.2) 66 | - GoogleSignIn (5.0.2): 67 | - AppAuth (~> 1.2) 68 | - GTMAppAuth (~> 1.0) 69 | - GTMSessionFetcher/Core (~> 1.1) 70 | - GoogleUtilities/AppDelegateSwizzler (7.5.2): 71 | - GoogleUtilities/Environment 72 | - GoogleUtilities/Logger 73 | - GoogleUtilities/Network 74 | - GoogleUtilities/Environment (7.5.2): 75 | - PromisesObjC (< 3.0, >= 1.2) 76 | - GoogleUtilities/Logger (7.5.2): 77 | - GoogleUtilities/Environment 78 | - GoogleUtilities/Network (7.5.2): 79 | - GoogleUtilities/Logger 80 | - "GoogleUtilities/NSData+zlib" 81 | - GoogleUtilities/Reachability 82 | - "GoogleUtilities/NSData+zlib (7.5.2)" 83 | - GoogleUtilities/Reachability (7.5.2): 84 | - GoogleUtilities/Logger 85 | - GTMAppAuth (1.2.2): 86 | - AppAuth/Core (~> 1.4) 87 | - GTMSessionFetcher/Core (~> 1.5) 88 | - GTMSessionFetcher/Core (1.7.0) 89 | - nanopb (2.30908.0): 90 | - nanopb/decode (= 2.30908.0) 91 | - nanopb/encode (= 2.30908.0) 92 | - nanopb/decode (2.30908.0) 93 | - nanopb/encode (2.30908.0) 94 | - package_info (0.0.1): 95 | - Flutter 96 | - path_provider (0.0.1): 97 | - Flutter 98 | - "permission_handler (5.1.0+2)": 99 | - Flutter 100 | - PromisesObjC (2.0.0) 101 | - shared_preferences (0.0.1): 102 | - Flutter 103 | - sign_in_with_apple (0.0.1): 104 | - Flutter 105 | - url_launcher (0.0.1): 106 | - Flutter 107 | 108 | DEPENDENCIES: 109 | - cloud_firestore (from `.symlinks/plugins/cloud_firestore/ios`) 110 | - contacts_service (from `.symlinks/plugins/contacts_service/ios`) 111 | - device_info_plus (from `.symlinks/plugins/device_info_plus/ios`) 112 | - direct_dialer (from `.symlinks/plugins/direct_dialer/ios`) 113 | - firebase_auth (from `.symlinks/plugins/firebase_auth/ios`) 114 | - firebase_core (from `.symlinks/plugins/firebase_core/ios`) 115 | - FirebaseFirestore (from `https://github.com/invertase/firestore-ios-sdk-frameworks.git`, tag `8.7.0`) 116 | - Flutter (from `Flutter`) 117 | - flutter_keyboard_visibility (from `.symlinks/plugins/flutter_keyboard_visibility/ios`) 118 | - flutter_local_notifications (from `.symlinks/plugins/flutter_local_notifications/ios`) 119 | - google_sign_in (from `.symlinks/plugins/google_sign_in/ios`) 120 | - package_info (from `.symlinks/plugins/package_info/ios`) 121 | - path_provider (from `.symlinks/plugins/path_provider/ios`) 122 | - permission_handler (from `.symlinks/plugins/permission_handler/ios`) 123 | - shared_preferences (from `.symlinks/plugins/shared_preferences/ios`) 124 | - sign_in_with_apple (from `.symlinks/plugins/sign_in_with_apple/ios`) 125 | - url_launcher (from `.symlinks/plugins/url_launcher/ios`) 126 | 127 | SPEC REPOS: 128 | trunk: 129 | - AppAuth 130 | - Firebase 131 | - FirebaseAuth 132 | - FirebaseCore 133 | - FirebaseCoreDiagnostics 134 | - GoogleDataTransport 135 | - GoogleSignIn 136 | - GoogleUtilities 137 | - GTMAppAuth 138 | - GTMSessionFetcher 139 | - nanopb 140 | - PromisesObjC 141 | 142 | EXTERNAL SOURCES: 143 | cloud_firestore: 144 | :path: ".symlinks/plugins/cloud_firestore/ios" 145 | contacts_service: 146 | :path: ".symlinks/plugins/contacts_service/ios" 147 | device_info_plus: 148 | :path: ".symlinks/plugins/device_info_plus/ios" 149 | direct_dialer: 150 | :path: ".symlinks/plugins/direct_dialer/ios" 151 | firebase_auth: 152 | :path: ".symlinks/plugins/firebase_auth/ios" 153 | firebase_core: 154 | :path: ".symlinks/plugins/firebase_core/ios" 155 | FirebaseFirestore: 156 | :git: https://github.com/invertase/firestore-ios-sdk-frameworks.git 157 | :tag: 8.7.0 158 | Flutter: 159 | :path: Flutter 160 | flutter_keyboard_visibility: 161 | :path: ".symlinks/plugins/flutter_keyboard_visibility/ios" 162 | flutter_local_notifications: 163 | :path: ".symlinks/plugins/flutter_local_notifications/ios" 164 | google_sign_in: 165 | :path: ".symlinks/plugins/google_sign_in/ios" 166 | package_info: 167 | :path: ".symlinks/plugins/package_info/ios" 168 | path_provider: 169 | :path: ".symlinks/plugins/path_provider/ios" 170 | permission_handler: 171 | :path: ".symlinks/plugins/permission_handler/ios" 172 | shared_preferences: 173 | :path: ".symlinks/plugins/shared_preferences/ios" 174 | sign_in_with_apple: 175 | :path: ".symlinks/plugins/sign_in_with_apple/ios" 176 | url_launcher: 177 | :path: ".symlinks/plugins/url_launcher/ios" 178 | 179 | CHECKOUT OPTIONS: 180 | FirebaseFirestore: 181 | :git: https://github.com/invertase/firestore-ios-sdk-frameworks.git 182 | :tag: 8.7.0 183 | 184 | SPEC CHECKSUMS: 185 | AppAuth: 31bcec809a638d7bd2f86ea8a52bd45f6e81e7c7 186 | cloud_firestore: a09fdd53707eadea99f93ca5ad8e95e4c1ab1108 187 | contacts_service: 849e1f84281804c8bfbec1b4c3eedcb23c5d3eca 188 | device_info_plus: e5c5da33f982a436e103237c0c85f9031142abed 189 | direct_dialer: 47a81737464ef2bb231e3bda4d3ac6ec6ff8bb07 190 | Firebase: bc9325d5ee2041524bac78a5213d0e530c651309 191 | firebase_auth: 25f34550b0023cbc3824731240608cd0aa515ca2 192 | firebase_core: f5ac1f2726a2bd0468cea0161eddeea3d83d7e3e 193 | FirebaseAuth: 2e7d029977648c67a5d51a263d4cbab76d34cf12 194 | FirebaseCore: f4804c1d3f4bbbefc88904d15653038f2c99ddf7 195 | FirebaseCoreDiagnostics: fe77f42da6329d6d83d21fd9d621a6b704413bfc 196 | FirebaseFirestore: 0f0d98ab906dbf65c82d070dd1a0c53da48d74dd 197 | Flutter: 50d75fe2f02b26cc09d224853bb45737f8b3214a 198 | flutter_keyboard_visibility: 0339d06371254c3eb25eeb90ba8d17dca8f9c069 199 | flutter_local_notifications: 0c0b1ae97e741e1521e4c1629a459d04b9aec743 200 | google_sign_in: c5cecea71f3be43282263550556e311c4cc03582 201 | GoogleDataTransport: 85fd18ff3019bb85d3f2c551d04c481dedf71fc9 202 | GoogleSignIn: 7137d297ddc022a7e0aa4619c86d72c909fa7213 203 | GoogleUtilities: 8de2a97a17e15b6b98e38e8770e2d129a57c0040 204 | GTMAppAuth: ad5c2b70b9a8689e1a04033c9369c4915bfcbe89 205 | GTMSessionFetcher: 43748f93435c2aa068b1cbe39655aaf600652e91 206 | nanopb: a0ba3315591a9ae0a16a309ee504766e90db0c96 207 | package_info: 873975fc26034f0b863a300ad47e7f1ac6c7ec62 208 | path_provider: abfe2b5c733d04e238b0d8691db0cfd63a27a93c 209 | permission_handler: ccb20a9fad0ee9b1314a52b70b76b473c5f8dab0 210 | PromisesObjC: 68159ce6952d93e17b2dfe273b8c40907db5ba58 211 | shared_preferences: 5033afbb22d372e15aff8ff766df9021b845f273 212 | sign_in_with_apple: f3bf75217ea4c2c8b91823f225d70230119b8440 213 | url_launcher: b6e016d912f04be9f5bf6e8e82dc599b7ba59649 214 | 215 | PODFILE CHECKSUM: 031d74e1deeb1bc9167e287d75f54fde7bde5f5f 216 | 217 | COCOAPODS: 1.11.2 218 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 39 | 40 | 41 | 42 | 43 | 44 | 54 | 56 | 62 | 63 | 64 | 65 | 66 | 67 | 73 | 75 | 81 | 82 | 83 | 84 | 86 | 87 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Runner/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import Flutter 3 | 4 | @UIApplicationMain 5 | @objc class AppDelegate: FlutterAppDelegate { 6 | override func application( 7 | _ application: UIApplication, 8 | didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? 9 | ) -> Bool { 10 | if #available(iOS 10.0, *) { 11 | UNUserNotificationCenter.current().delegate = self as? UNUserNotificationCenterDelegate 12 | } 13 | GeneratedPluginRegistrant.register(with: self) 14 | return super.application(application, didFinishLaunchingWithOptions: launchOptions) 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "icon_20@2x.png", 5 | "idiom" : "iphone", 6 | "scale" : "2x", 7 | "size" : "20x20" 8 | }, 9 | { 10 | "filename" : "icon_20@3x.png", 11 | "idiom" : "iphone", 12 | "scale" : "3x", 13 | "size" : "20x20" 14 | }, 15 | { 16 | "filename" : "icon_29@1x.png", 17 | "idiom" : "iphone", 18 | "scale" : "1x", 19 | "size" : "29x29" 20 | }, 21 | { 22 | "filename" : "icon_29@2x.png", 23 | "idiom" : "iphone", 24 | "scale" : "2x", 25 | "size" : "29x29" 26 | }, 27 | { 28 | "filename" : "icon_29@3x.png", 29 | "idiom" : "iphone", 30 | "scale" : "3x", 31 | "size" : "29x29" 32 | }, 33 | { 34 | "filename" : "icon_40@2x.png", 35 | "idiom" : "iphone", 36 | "scale" : "2x", 37 | "size" : "40x40" 38 | }, 39 | { 40 | "filename" : "icon_40@3x.png", 41 | "idiom" : "iphone", 42 | "scale" : "3x", 43 | "size" : "40x40" 44 | }, 45 | { 46 | "filename" : "icon_60@2x.png", 47 | "idiom" : "iphone", 48 | "scale" : "2x", 49 | "size" : "60x60" 50 | }, 51 | { 52 | "filename" : "icon_60@3x.png", 53 | "idiom" : "iphone", 54 | "scale" : "3x", 55 | "size" : "60x60" 56 | }, 57 | { 58 | "filename" : "icon_20@1x.png", 59 | "idiom" : "ipad", 60 | "scale" : "1x", 61 | "size" : "20x20" 62 | }, 63 | { 64 | "filename" : "icon_20@2x-1.png", 65 | "idiom" : "ipad", 66 | "scale" : "2x", 67 | "size" : "20x20" 68 | }, 69 | { 70 | "filename" : "icon_29@1x-1.png", 71 | "idiom" : "ipad", 72 | "scale" : "1x", 73 | "size" : "29x29" 74 | }, 75 | { 76 | "filename" : "icon_29@2x-1.png", 77 | "idiom" : "ipad", 78 | "scale" : "2x", 79 | "size" : "29x29" 80 | }, 81 | { 82 | "filename" : "icon_40@1x.png", 83 | "idiom" : "ipad", 84 | "scale" : "1x", 85 | "size" : "40x40" 86 | }, 87 | { 88 | "filename" : "icon_40@2x-1.png", 89 | "idiom" : "ipad", 90 | "scale" : "2x", 91 | "size" : "40x40" 92 | }, 93 | { 94 | "filename" : "icon_76@1x.png", 95 | "idiom" : "ipad", 96 | "scale" : "1x", 97 | "size" : "76x76" 98 | }, 99 | { 100 | "filename" : "icon_76@2x.png", 101 | "idiom" : "ipad", 102 | "scale" : "2x", 103 | "size" : "76x76" 104 | }, 105 | { 106 | "filename" : "icon_83.5@2x.png", 107 | "idiom" : "ipad", 108 | "scale" : "2x", 109 | "size" : "83.5x83.5" 110 | }, 111 | { 112 | "filename" : "icon_1024@1x.png", 113 | "idiom" : "ios-marketing", 114 | "scale" : "1x", 115 | "size" : "1024x1024" 116 | } 117 | ], 118 | "info" : { 119 | "author" : "xcode", 120 | "version" : 1 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/icon_1024@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GroovinChip/call_manager/abf78a0ee52784b7dc352cf0cbbb9428fbe6d2fb/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon_1024@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/icon_20@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GroovinChip/call_manager/abf78a0ee52784b7dc352cf0cbbb9428fbe6d2fb/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon_20@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/icon_20@2x-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GroovinChip/call_manager/abf78a0ee52784b7dc352cf0cbbb9428fbe6d2fb/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon_20@2x-1.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/icon_20@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GroovinChip/call_manager/abf78a0ee52784b7dc352cf0cbbb9428fbe6d2fb/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon_20@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/icon_20@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GroovinChip/call_manager/abf78a0ee52784b7dc352cf0cbbb9428fbe6d2fb/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon_20@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/icon_29@1x-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GroovinChip/call_manager/abf78a0ee52784b7dc352cf0cbbb9428fbe6d2fb/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon_29@1x-1.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/icon_29@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GroovinChip/call_manager/abf78a0ee52784b7dc352cf0cbbb9428fbe6d2fb/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon_29@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/icon_29@2x-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GroovinChip/call_manager/abf78a0ee52784b7dc352cf0cbbb9428fbe6d2fb/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon_29@2x-1.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/icon_29@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GroovinChip/call_manager/abf78a0ee52784b7dc352cf0cbbb9428fbe6d2fb/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon_29@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/icon_29@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GroovinChip/call_manager/abf78a0ee52784b7dc352cf0cbbb9428fbe6d2fb/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon_29@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/icon_40@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GroovinChip/call_manager/abf78a0ee52784b7dc352cf0cbbb9428fbe6d2fb/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon_40@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/icon_40@2x-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GroovinChip/call_manager/abf78a0ee52784b7dc352cf0cbbb9428fbe6d2fb/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon_40@2x-1.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/icon_40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GroovinChip/call_manager/abf78a0ee52784b7dc352cf0cbbb9428fbe6d2fb/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon_40@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/icon_40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GroovinChip/call_manager/abf78a0ee52784b7dc352cf0cbbb9428fbe6d2fb/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon_40@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/icon_60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GroovinChip/call_manager/abf78a0ee52784b7dc352cf0cbbb9428fbe6d2fb/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon_60@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/icon_60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GroovinChip/call_manager/abf78a0ee52784b7dc352cf0cbbb9428fbe6d2fb/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon_60@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/icon_76@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GroovinChip/call_manager/abf78a0ee52784b7dc352cf0cbbb9428fbe6d2fb/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon_76@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/icon_76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GroovinChip/call_manager/abf78a0ee52784b7dc352cf0cbbb9428fbe6d2fb/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon_76@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/icon_83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GroovinChip/call_manager/abf78a0ee52784b7dc352cf0cbbb9428fbe6d2fb/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon_83.5@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /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/GroovinChip/call_manager/abf78a0ee52784b7dc352cf0cbbb9428fbe6d2fb/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GroovinChip/call_manager/abf78a0ee52784b7dc352cf0cbbb9428fbe6d2fb/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GroovinChip/call_manager/abf78a0ee52784b7dc352cf0cbbb9428fbe6d2fb/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/GoogleService-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CLIENT_ID 6 | 1053316160376-117rp70a5ps0s6nk98th54trgsacoaim.apps.googleusercontent.com 7 | REVERSED_CLIENT_ID 8 | com.googleusercontent.apps.1053316160376-117rp70a5ps0s6nk98th54trgsacoaim 9 | ANDROID_CLIENT_ID 10 | 1053316160376-5juugktuaas98do08thao9iplqmr5fn9.apps.googleusercontent.com 11 | API_KEY 12 | AIzaSyDHw5ZUSRFFr506s5tDwjRBXNVauqsZ854 13 | GCM_SENDER_ID 14 | 1053316160376 15 | PLIST_VERSION 16 | 1 17 | BUNDLE_ID 18 | dev.groovinchip.callmanager 19 | PROJECT_ID 20 | call-manager-flutter-db 21 | STORAGE_BUCKET 22 | call-manager-flutter-db.appspot.com 23 | IS_ADS_ENABLED 24 | 25 | IS_ANALYTICS_ENABLED 26 | 27 | IS_APPINVITE_ENABLED 28 | 29 | IS_GCM_ENABLED 30 | 31 | IS_SIGNIN_ENABLED 32 | 33 | GOOGLE_APP_ID 34 | 1:1053316160376:ios:3f44ce63a1c0a5fba4868d 35 | DATABASE_URL 36 | https://call-manager-flutter-db.firebaseio.com 37 | 38 | -------------------------------------------------------------------------------- /ios/Runner/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleDisplayName 8 | Call Manager 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | call_manager 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | $(FLUTTER_BUILD_NAME) 21 | CFBundleSignature 22 | ???? 23 | CFBundleURLTypes 24 | 25 | 26 | CFBundleTypeRole 27 | Editor 28 | CFBundleURLSchemes 29 | 30 | com.googleusercontent.apps.1053316160376-117rp70a5ps0s6nk98th54trgsacoaim 31 | 32 | 33 | 34 | CFBundleVersion 35 | $(FLUTTER_BUILD_NUMBER) 36 | LSRequiresIPhoneOS 37 | 38 | NSContactsUsageDescription 39 | Call Manager can autocomplete your contacts when you add a new call, if you allow this permission. 40 | PermissionGroupNotification 41 | Call Manager Pro would like to send you notifications 42 | UILaunchStoryboardName 43 | LaunchScreen 44 | UIMainStoryboardFile 45 | Main 46 | UISupportedInterfaceOrientations 47 | 48 | UIInterfaceOrientationPortrait 49 | UIInterfaceOrientationLandscapeLeft 50 | UIInterfaceOrientationLandscapeRight 51 | 52 | UISupportedInterfaceOrientations~ipad 53 | 54 | UIInterfaceOrientationPortrait 55 | UIInterfaceOrientationPortraitUpsideDown 56 | UIInterfaceOrientationLandscapeLeft 57 | UIInterfaceOrientationLandscapeRight 58 | 59 | UIViewControllerBasedStatusBarAppearance 60 | 61 | 62 | 63 | -------------------------------------------------------------------------------- /ios/Runner/Runner-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | #import "GeneratedPluginRegistrant.h" 2 | -------------------------------------------------------------------------------- /ios/Runner/Runner.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | aps-environment 6 | development 7 | com.apple.developer.applesignin 8 | 9 | Default 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /lib/app.dart: -------------------------------------------------------------------------------- 1 | import 'package:call_manager/firebase/firebase.dart'; 2 | import 'package:call_manager/screens/home_screen.dart'; 3 | import 'package:call_manager/screens/login_screen.dart'; 4 | import 'package:call_manager/services/contacts_utility.dart'; 5 | import 'package:call_manager/services/notifications_service.dart'; 6 | import 'package:call_manager/services/phone_utility.dart'; 7 | import 'package:call_manager/services/prefs_service.dart'; 8 | import 'package:call_manager/theme/app_colors.dart'; 9 | import 'package:call_manager/theme/app_themes.dart'; 10 | import 'package:firebase_auth/firebase_auth.dart'; 11 | import 'package:flutter/material.dart'; 12 | import 'package:provider/provider.dart'; 13 | import 'package:wiredash/wiredash.dart'; 14 | 15 | class CallManagerApp extends StatefulWidget { 16 | const CallManagerApp({ 17 | Key? key, 18 | required this.contactsUtility, 19 | required this.notificationService, 20 | required this.phoneUtility, 21 | required this.prefsService, 22 | }) : super(key: key); 23 | 24 | final ContactsUtility contactsUtility; 25 | final NotificationService notificationService; 26 | final PhoneUtility phoneUtility; 27 | final PrefsService prefsService; 28 | 29 | @override 30 | _CallManagerAppState createState() => _CallManagerAppState(); 31 | } 32 | 33 | class _CallManagerAppState extends State with FirebaseMixin { 34 | final _navigatorKey = GlobalKey(); 35 | 36 | @override 37 | void initState() { 38 | super.initState(); 39 | _onAuthStateChange(); 40 | } 41 | 42 | void _onAuthStateChange() { 43 | auth.authStateChanges().listen((User? user) { 44 | if (user != null) { 45 | firestore.initStorageForUser(currentUser!.uid); 46 | firestore.recordLoginDate(currentUser!.uid); 47 | } 48 | }); 49 | } 50 | 51 | Route? _onGenerateRoute(RouteSettings settings) { 52 | switch (settings.name) { 53 | case HomeScreen.routeName: 54 | return HomeScreen.route(settings: settings); 55 | case LoginScreen.routeName: 56 | return LoginScreen.route(settings: settings); 57 | default: 58 | return null; 59 | } 60 | } 61 | 62 | @override 63 | Widget build(BuildContext context) { 64 | return MultiProvider( 65 | providers: [ 66 | Provider.value(value: widget.prefsService), 67 | Provider.value(value: widget.contactsUtility), 68 | Provider.value(value: widget.phoneUtility), 69 | Provider.value(value: widget.notificationService), 70 | ], 71 | child: StreamBuilder( 72 | stream: widget.prefsService.preferencesSubject, 73 | initialData: widget.prefsService.preferencesSubject.value, 74 | builder: (context, snapshot) { 75 | return Wiredash( 76 | projectId: 'call-manager-bk2ikve', 77 | secret: '6p356wjo9kyupuj9se49pd0q2e41xa1x4m68nnky0hvkeva8', 78 | navigatorKey: _navigatorKey, 79 | options: WiredashOptionsData( 80 | praiseButton: false, 81 | ), 82 | theme: WiredashThemeData( 83 | primaryColor: AppColors.primaryColor, 84 | //primaryBackgroundColor: AppColors.primaryColor, 85 | backgroundColor: AppColors.primaryColor, 86 | brightness: snapshot.data!.brightness, 87 | ), 88 | child: MaterialApp( 89 | navigatorKey: _navigatorKey, 90 | title: 'Call Manager', 91 | theme: AppThemes.lightTheme(), 92 | darkTheme: AppThemes.darkTheme(), 93 | themeMode: snapshot.data!.themeMode, 94 | initialRoute: currentUser != null 95 | ? HomeScreen.routeName 96 | : LoginScreen.routeName, 97 | onGenerateInitialRoutes: ((String initialRoute) => [ 98 | _onGenerateRoute(RouteSettings(name: initialRoute))!, 99 | ]), 100 | onGenerateRoute: _onGenerateRoute, 101 | debugShowCheckedModeBanner: false, 102 | ), 103 | ); 104 | }, 105 | ), 106 | ); 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /lib/data_models/call.dart: -------------------------------------------------------------------------------- 1 | class Call { 2 | Call({ 3 | this.avatar, 4 | this.description, 5 | this.id, 6 | required this.name, 7 | required this.phoneNumber, 8 | this.reminderDate, 9 | this.reminderTime, 10 | this.timeCreated, 11 | this.lastEdited, 12 | this.completedAt, 13 | }); 14 | 15 | factory Call.fromJsonWithDocId(Map json, String docId) { 16 | DateTime? timestamp; 17 | DateTime? _lastEdited; 18 | if (json['TimeCreated'] != 'null' && json['TimeCreated'] != null) { 19 | timestamp = DateTime.parse(json['TimeCreated']); 20 | } 21 | 22 | if (json['LastEdited'] != 'null' && json['LastEdited'] != null) { 23 | _lastEdited = DateTime.parse(json['LastEdited']); 24 | } 25 | 26 | return Call( 27 | avatar: json['Avatar'] ?? '', 28 | description: json['Description'], 29 | id: docId, 30 | name: json['Name'], 31 | phoneNumber: json['PhoneNumber'], 32 | reminderDate: json['ReminderDate'], 33 | reminderTime: json['ReminderTime'], 34 | timeCreated: timestamp, 35 | lastEdited: _lastEdited, 36 | completedAt: json['CompletedAt'], 37 | ); 38 | } 39 | 40 | String? avatar; 41 | String? completedAt; 42 | String? description; 43 | String? id; 44 | DateTime? lastEdited; 45 | String? name; 46 | String? phoneNumber; 47 | String? reminderDate; 48 | String? reminderTime; 49 | DateTime? timeCreated; 50 | 51 | Map toJson() { 52 | return { 53 | 'Avatar': avatar, 54 | 'Description': description, 55 | 'Name': name, 56 | 'PhoneNumber': phoneNumber, 57 | 'ReminderDate': reminderDate?.toString() ?? '', 58 | 'ReminderTime': reminderTime?.toString() ?? '', 59 | 'TimeCreated': timeCreated.toString(), 60 | 'LastEdited': lastEdited.toString(), 61 | 'CompletedAt': completedAt ?? '', 62 | }; 63 | } 64 | } 65 | 66 | extension CallX on Call { 67 | bool get hasAvatar => avatar != null && avatar!.isNotEmpty; 68 | bool get hasDescription => description != null && description!.isNotEmpty; 69 | bool get isNotCompleted => completedAt == null || completedAt!.isEmpty; 70 | } 71 | -------------------------------------------------------------------------------- /lib/firebase/fb_type_aliases.dart: -------------------------------------------------------------------------------- 1 | import 'firebase.dart'; 2 | 3 | typedef FirestoreCollection = CollectionReference>; 4 | typedef FirestoreDocument = QuerySnapshot>; -------------------------------------------------------------------------------- /lib/firebase/firebase.dart: -------------------------------------------------------------------------------- 1 | export 'package:cloud_firestore/cloud_firestore.dart'; 2 | export 'package:firebase_auth/firebase_auth.dart'; 3 | export 'package:firebase_core/firebase_core.dart'; 4 | 5 | export 'fb_type_aliases.dart'; 6 | export 'firebase_auth_extensions.dart'; 7 | export 'firebase_mixin.dart'; 8 | export 'firestore_extensions.dart'; -------------------------------------------------------------------------------- /lib/firebase/firebase_auth_extensions.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'dart:convert'; 3 | import 'dart:math'; 4 | 5 | import 'package:crypto/crypto.dart'; 6 | import 'package:firebase_auth/firebase_auth.dart'; 7 | import 'package:google_sign_in/google_sign_in.dart'; 8 | import 'package:sign_in_with_apple/sign_in_with_apple.dart'; 9 | 10 | /// Generates a cryptographically secure random nonce, to be included in a 11 | /// credential request. 12 | String generateNonce([int length = 32]) { 13 | const charset = 14 | '0123456789ABCDEFGHIJKLMNOPQRSTUVXYZabcdefghijklmnopqrstuvwxyz-._'; 15 | final random = Random.secure(); 16 | 17 | return List.generate(length, (_) => charset[random.nextInt(charset.length)]) 18 | .join(); 19 | } 20 | 21 | /// Returns the sha256 hash of [input] in hex notation. 22 | String sha256ofString(String input) { 23 | final bytes = utf8.encode(input); 24 | final digest = sha256.convert(bytes); 25 | 26 | return digest.toString(); 27 | } 28 | 29 | extension FirebaseAuthX on FirebaseAuth { 30 | /// Sign in with Apple. 31 | /// 32 | /// This handles the displaying of the Sign in with Apple dialog and 33 | /// finalizing with Firebase Auth. 34 | Future signInWithApple() async { 35 | // To prevent replay attacks with the credential returned from Apple, we 36 | // include a nonce in the credential request. When signing in in with 37 | // Firebase, the nonce in the id token returned by Apple, is expected to 38 | // match the sha256 hash of `rawNonce`. 39 | final rawNonce = generateNonce(); 40 | final nonce = sha256ofString(rawNonce); 41 | 42 | // Request credential for the currently signed in Apple account. 43 | final appleCredential = await SignInWithApple.getAppleIDCredential( 44 | scopes: [ 45 | AppleIDAuthorizationScopes.email, 46 | AppleIDAuthorizationScopes.fullName, 47 | ], 48 | nonce: nonce, 49 | ); 50 | 51 | // Create an `OAuthCredential` from the credential returned by Apple. 52 | final oauthCredential = OAuthProvider('apple.com').credential( 53 | idToken: appleCredential.identityToken, 54 | rawNonce: rawNonce, 55 | ); 56 | 57 | await signInWithCredential(oauthCredential); 58 | } 59 | 60 | /// Sign in with Google. 61 | /// 62 | /// This handles the displaying of the Google SignIn dialog and 63 | /// finalizing with Firebase Auth. 64 | Future signInWithGoogle() async { 65 | final googleUser = await GoogleSignIn().signIn(); 66 | final googleAuth = await googleUser!.authentication; 67 | 68 | final googleAuthCredential = GoogleAuthProvider.credential( 69 | accessToken: googleAuth.accessToken, 70 | idToken: googleAuth.idToken, 71 | ); 72 | 73 | await signInWithCredential(googleAuthCredential); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /lib/firebase/firebase_mixin.dart: -------------------------------------------------------------------------------- 1 | import 'firebase.dart'; 2 | 3 | mixin FirebaseMixin { 4 | FirebaseAuth get auth => FirebaseAuth.instance; 5 | FirebaseFirestore get firestore => FirebaseFirestore.instance; 6 | User? get currentUser => FirebaseAuth.instance.currentUser; 7 | } 8 | -------------------------------------------------------------------------------- /lib/firebase/firestore_extensions.dart: -------------------------------------------------------------------------------- 1 | import 'package:call_manager/data_models/call.dart'; 2 | import 'package:call_manager/firebase/fb_type_aliases.dart'; 3 | import 'package:cloud_firestore/cloud_firestore.dart'; 4 | import 'package:firebase_auth/firebase_auth.dart'; 5 | 6 | extension FirestoreX on FirebaseFirestore { 7 | FirestoreCollection get users => collection('Users'); 8 | FirestoreCollection get upcomingCalls => 9 | users.doc(FirebaseAuth.instance.currentUser!.uid).collection('Calls'); 10 | FirestoreCollection get completedCalls => users 11 | .doc(FirebaseAuth.instance.currentUser!.uid) 12 | .collection('CompletedCalls'); 13 | 14 | /// Marks a call as complete and moves it to [completedCalls] 15 | Future completeCall(Call call) async { 16 | final completedAt = DateTime.now().toString(); 17 | call.completedAt = completedAt; 18 | await completedCalls.doc(call.id).set(call.toJson()); 19 | await upcomingCalls.doc(call.id).delete(); 20 | } 21 | 22 | /// Marks a call as incomplete and moves it to [upcomingCalls] 23 | Future incompleteCall(Call call) async { 24 | call.completedAt = null; 25 | await upcomingCalls.doc(call.id).set(call.toJson()); 26 | await completedCalls.doc(call.id).delete(); 27 | } 28 | 29 | /// Deletes one call 30 | Future deleteCall(Call call) async { 31 | if (!call.isNotCompleted) { 32 | await completedCalls.doc(call.id).delete(); 33 | } else { 34 | await upcomingCalls.doc(call.id).delete(); 35 | } 36 | } 37 | 38 | /// Deletes all calls 39 | Future deleteAllCalls() async { 40 | final _upcomingCalls = await upcomingCalls.get(); 41 | final _completedCalls = await completedCalls.get(); 42 | if (_upcomingCalls.docs.isEmpty && _completedCalls.docs.isEmpty) { 43 | return false; 44 | } 45 | if (_upcomingCalls.docs.isNotEmpty) { 46 | for (int i = 0; i < _upcomingCalls.docs.length; i++) { 47 | _upcomingCalls.docs[i].reference.delete(); 48 | } 49 | } 50 | if (_completedCalls.docs.isNotEmpty) { 51 | for (int i = 0; i < _completedCalls.docs.length; i++) { 52 | _completedCalls.docs[i].reference.delete(); 53 | } 54 | } 55 | } 56 | 57 | void initStorageForUser(String uid) { 58 | if (users.doc(uid).path.isEmpty) { 59 | users.doc(uid).set({}); 60 | } 61 | } 62 | 63 | void recordLoginDate(String uid) { 64 | users.doc(uid).update({ 65 | 'last login date': DateTime.now().toIso8601String(), 66 | }); 67 | } 68 | 69 | void recordLoginWithGoogle(String uid) { 70 | users.doc(uid).update({ 71 | 'last login with Google': DateTime.now().toIso8601String(), 72 | }); 73 | } 74 | 75 | void recordLoginWithApple(String uid) { 76 | users.doc(uid).update({ 77 | 'last login with Apple': DateTime.now().toIso8601String(), 78 | }); 79 | } 80 | 81 | void recordLogout(String uid) { 82 | users.doc(uid).update({ 83 | 'last logout date': DateTime.now().toIso8601String(), 84 | }); 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /lib/generated_plugin_registrant.dart: -------------------------------------------------------------------------------- 1 | // 2 | // Generated file. Do not edit. 3 | // 4 | 5 | // ignore_for_file: directives_ordering 6 | // ignore_for_file: lines_longer_than_80_chars 7 | 8 | import 'package:cloud_firestore_web/cloud_firestore_web.dart'; 9 | import 'package:device_info_plus_web/device_info_plus_web.dart'; 10 | import 'package:firebase_auth_web/firebase_auth_web.dart'; 11 | import 'package:firebase_core_web/firebase_core_web.dart'; 12 | import 'package:flutter_keyboard_visibility_web/flutter_keyboard_visibility_web.dart'; 13 | import 'package:google_sign_in_web/google_sign_in_web.dart'; 14 | import 'package:shared_preferences_web/shared_preferences_web.dart'; 15 | import 'package:url_launcher_web/url_launcher_web.dart'; 16 | 17 | import 'package:flutter_web_plugins/flutter_web_plugins.dart'; 18 | 19 | // ignore: public_member_api_docs 20 | void registerPlugins(Registrar registrar) { 21 | FirebaseFirestoreWeb.registerWith(registrar); 22 | DeviceInfoPlusPlugin.registerWith(registrar); 23 | FirebaseAuthWeb.registerWith(registrar); 24 | FirebaseCoreWeb.registerWith(registrar); 25 | FlutterKeyboardVisibilityPlugin.registerWith(registrar); 26 | GoogleSignInPlugin.registerWith(registrar); 27 | SharedPreferencesPlugin.registerWith(registrar); 28 | UrlLauncherPlugin.registerWith(registrar); 29 | registrar.registerMessageHandler(); 30 | } 31 | -------------------------------------------------------------------------------- /lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'package:call_manager/app.dart'; 2 | import 'package:call_manager/services/contacts_utility.dart'; 3 | import 'package:call_manager/services/notifications_service.dart'; 4 | import 'package:call_manager/services/phone_utility.dart'; 5 | import 'package:call_manager/services/prefs_service.dart'; 6 | import 'package:call_manager/utils/pass_notification.dart'; 7 | import 'package:firebase_core/firebase_core.dart'; 8 | import 'package:flutter/material.dart'; 9 | 10 | Future main() async { 11 | WidgetsFlutterBinding.ensureInitialized(); 12 | 13 | await Firebase.initializeApp(); 14 | 15 | final prefsService = await PrefsService.init(); 16 | final contactsUtility = await ContactsUtility.init(); 17 | final phoneUtility = await PhoneUtility.init(); 18 | final notificationService = await NotificationService.init(); 19 | 20 | // launch app 21 | runApp( 22 | PassNotification( 23 | notificationService.notificationsPlugin, 24 | child: CallManagerApp( 25 | prefsService: prefsService, 26 | contactsUtility: contactsUtility, 27 | phoneUtility: phoneUtility, 28 | notificationService: notificationService, 29 | ), 30 | ), 31 | ); 32 | } 33 | -------------------------------------------------------------------------------- /lib/provided.dart: -------------------------------------------------------------------------------- 1 | import 'package:call_manager/services/contacts_utility.dart'; 2 | import 'package:call_manager/services/notifications_service.dart'; 3 | import 'package:call_manager/services/phone_utility.dart'; 4 | import 'package:call_manager/services/prefs_service.dart'; 5 | import 'package:flutter/material.dart'; 6 | import 'package:provider/provider.dart'; 7 | 8 | mixin Provided on State { 9 | PrefsService? _prefsService; 10 | ContactsUtility? _contactsUtility; 11 | PhoneUtility? _phoneUtility; 12 | NotificationService? _notificationService; 13 | 14 | PrefsService get prefsService => 15 | _prefsService ??= Provider.of(context, listen: false); 16 | ContactsUtility get contactsUtility => 17 | _contactsUtility ??= Provider.of(context, listen: false); 18 | PhoneUtility get phoneUtility => 19 | _phoneUtility ??= Provider.of(context, listen: false); 20 | NotificationService get notificationService => _notificationService ??= 21 | Provider.of(context, listen: false); 22 | } 23 | -------------------------------------------------------------------------------- /lib/screens/edit_call_screen.dart: -------------------------------------------------------------------------------- 1 | import 'package:bluejay/bluejay.dart'; 2 | import 'package:call_manager/data_models/call.dart'; 3 | import 'package:call_manager/firebase/firebase.dart'; 4 | import 'package:call_manager/provided.dart'; 5 | import 'package:call_manager/utils/extensions.dart'; 6 | import 'package:call_manager/widgets/clear_button.dart'; 7 | import 'package:call_manager/widgets/contact_avatar.dart'; 8 | import 'package:call_manager/widgets/multiple_phone_numbers_sheet.dart'; 9 | import 'package:contacts_service/contacts_service.dart'; 10 | import 'package:flutter/material.dart'; 11 | import 'package:flutter/services.dart'; 12 | import 'package:flutter_typeahead/flutter_typeahead.dart'; 13 | 14 | class EditCallScreen extends StatefulWidget { 15 | const EditCallScreen({ 16 | Key? key, 17 | required this.call, 18 | }) : super(key: key); 19 | 20 | final Call call; 21 | 22 | @override 23 | _EditCallScreenState createState() => _EditCallScreenState(); 24 | } 25 | 26 | class _EditCallScreenState extends State 27 | with FirebaseMixin, Provided { 28 | Contact? selectedContact; 29 | final _formKey = GlobalKey(); 30 | 31 | @override 32 | // ignore: long-method, code-metrics 33 | Widget build(BuildContext context) { 34 | final theme = Theme.of(context); 35 | 36 | return Scaffold( 37 | backgroundColor: theme.canvasColor, 38 | appBar: AppBar( 39 | automaticallyImplyLeading: false, 40 | title: const Text('Edit Call'), 41 | ), 42 | body: SingleChildScrollView( 43 | child: Padding( 44 | padding: const EdgeInsets.all(16.0), 45 | child: Form( 46 | key: _formKey, 47 | child: Column( 48 | children: [ 49 | TextEditingControllerBuilder( 50 | text: widget.call.name!, 51 | builder: (_, controller) { 52 | return TypeAheadFormField( 53 | suggestionsCallback: 54 | contactsUtility.searchContactsWithQuery, 55 | itemBuilder: (context, dynamic contact) { 56 | return ListTile( 57 | leading: ContactAvatar(contact: contact), 58 | title: Text(contact.displayName), 59 | ); 60 | }, 61 | transitionBuilder: (context, suggestionsBox, controller) { 62 | return suggestionsBox; 63 | }, 64 | onSuggestionSelected: (dynamic contact) { 65 | selectedContact = contact; 66 | controller.text = selectedContact!.displayName!; 67 | if (selectedContact!.phones!.length > 1) { 68 | showModalBottomSheet( 69 | context: context, 70 | shape: RoundedRectangleBorder( 71 | borderRadius: BorderRadius.circular(12), 72 | ), 73 | builder: (_) => MultiplePhoneNumbersSheet( 74 | selectedContact: selectedContact, 75 | ), 76 | ).then((value) => widget.call.phoneNumber = value); 77 | } else { 78 | widget.call.phoneNumber = 79 | selectedContact!.phones!.first.value!; 80 | } 81 | }, 82 | validator: (value) => value == null || value.isEmpty 83 | ? 'This field is required' 84 | : null, 85 | onSaved: (contactName) { 86 | controller.text = contactName!; 87 | widget.call.name = contactName; 88 | }, 89 | textFieldConfiguration: TextFieldConfiguration( 90 | textCapitalization: TextCapitalization.words, 91 | controller: controller, 92 | keyboardType: TextInputType.text, 93 | maxLines: 1, 94 | decoration: InputDecoration( 95 | prefixIcon: Icon( 96 | Icons.person_outline, 97 | color: theme.iconTheme.color, 98 | ), 99 | suffixIcon: ClearButton( 100 | onPressed: () { 101 | controller.clear(); 102 | widget.call.name = controller.text; 103 | }, 104 | ), 105 | labelText: 'Name', 106 | ), 107 | ), 108 | ); 109 | }, 110 | ), 111 | const SizedBox(height: 16.0), 112 | TextEditingControllerBuilder( 113 | text: widget.call.phoneNumber!, 114 | builder: (_, controller) { 115 | return TextFormField( 116 | keyboardType: TextInputType.phone, 117 | maxLines: 1, 118 | autofocus: false, 119 | controller: controller, 120 | onChanged: (value) => widget.call.phoneNumber = value, 121 | validator: (value) => value == null || value.isEmpty 122 | ? 'This field is required' 123 | : null, 124 | decoration: InputDecoration( 125 | prefixIcon: Icon( 126 | Icons.phone_outlined, 127 | color: theme.iconTheme.color, 128 | ), 129 | suffixIcon: ClearButton( 130 | onPressed: () { 131 | controller.clear(); 132 | widget.call.phoneNumber = controller.text; 133 | }, 134 | ), 135 | labelText: 'Phone number', 136 | ), 137 | ); 138 | }, 139 | ), 140 | const SizedBox(height: 16.0), 141 | TextEditingControllerBuilder( 142 | text: widget.call.description ?? '', 143 | builder: (_, controller) { 144 | return TextFormField( 145 | keyboardType: TextInputType.multiline, 146 | textCapitalization: TextCapitalization.sentences, 147 | maxLines: 2, 148 | autofocus: false, 149 | controller: controller, 150 | onChanged: (value) => widget.call.description = value, 151 | decoration: InputDecoration( 152 | labelText: 'Description', 153 | prefixIcon: Icon( 154 | Icons.comment_outlined, 155 | color: theme.iconTheme.color, 156 | ), 157 | suffixIcon: ClearButton( 158 | onPressed: () { 159 | controller.clear(); 160 | widget.call.description = controller.text; 161 | }, 162 | ), 163 | ), 164 | ); 165 | }, 166 | ), 167 | ], 168 | ), 169 | ), 170 | ), 171 | ), 172 | floatingActionButton: !MediaQuery.of(context).keyboardOpen 173 | ? FloatingActionButton.extended( 174 | highlightElevation: 2.0, 175 | onPressed: () { 176 | _formKey.currentState!.save(); 177 | if (_formKey.currentState!.validate()) { 178 | if (selectedContact != null) { 179 | widget.call.avatar = selectedContact?.avatar != null 180 | ? String.fromCharCodes(selectedContact!.avatar!) 181 | : ''; 182 | } 183 | 184 | widget.call.lastEdited = DateTime.now(); 185 | 186 | firestore.upcomingCalls 187 | .doc(widget.call.id) 188 | .update(widget.call.toJson()); 189 | 190 | Navigator.of(context).pop(); 191 | } 192 | }, 193 | tooltip: 'Save', 194 | elevation: 2.0, 195 | icon: const Icon(Icons.save), 196 | label: const Text('SAVE'), 197 | ) 198 | : null, 199 | floatingActionButtonLocation: FloatingActionButtonLocation.centerDocked, 200 | bottomNavigationBar: BottomAppBar( 201 | //hasNotch: false, 202 | child: Row( 203 | children: const [ 204 | SizedBox(width: 8.0), 205 | CloseButton(), 206 | ], 207 | ), 208 | ), 209 | ); 210 | } 211 | } 212 | -------------------------------------------------------------------------------- /lib/screens/home_screen.dart: -------------------------------------------------------------------------------- 1 | import 'package:call_manager/provided.dart'; 2 | import 'package:call_manager/screens/new_call_screen.dart'; 3 | import 'package:call_manager/theme/app_themes.dart'; 4 | import 'package:call_manager/widgets/calls_view.dart'; 5 | import 'package:call_manager/widgets/menu_bottom_sheet.dart'; 6 | import 'package:flutter/material.dart'; 7 | import 'package:flutter/services.dart'; 8 | import 'package:permission_handler/permission_handler.dart'; 9 | 10 | class HomeScreen extends StatefulWidget { 11 | const HomeScreen({Key? key}) : super(key: key); 12 | 13 | static const routeName = '/homeScreen'; 14 | 15 | static Route route({ 16 | RouteSettings settings = const RouteSettings(name: routeName), 17 | }) { 18 | return MaterialPageRoute( 19 | settings: settings, 20 | builder: (BuildContext context) { 21 | return const HomeScreen(); 22 | }, 23 | ); 24 | } 25 | 26 | @override 27 | _HomeScreenState createState() => _HomeScreenState(); 28 | } 29 | 30 | class _HomeScreenState extends State 31 | with Provided, SingleTickerProviderStateMixin { 32 | late final tabController = TabController(length: 2, vsync: this); 33 | @override 34 | void initState() { 35 | super.initState(); 36 | _checkPermissions(); 37 | } 38 | 39 | /// Checks for contacts and phone permissions and requests them if they 40 | /// are not yet given. 41 | Future _checkPermissions() async { 42 | await [ 43 | Permission.phone, 44 | Permission.contacts, 45 | ].request(); 46 | } 47 | 48 | @override 49 | // ignore: long-method 50 | Widget build(BuildContext context) { 51 | SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge); 52 | 53 | return AnnotatedRegion( 54 | value: AppThemes.themedSystemNavigationBar(context), 55 | child: Scaffold( 56 | appBar: AppBar( 57 | title: const Text('Call Manager'), 58 | ), 59 | body: const CallsView(), 60 | floatingActionButton: FloatingActionButton.extended( 61 | icon: const Icon(Icons.add), 62 | elevation: 2.0, 63 | label: const Text('NEW CALL'), 64 | onPressed: () => Navigator.of(context).push( 65 | MaterialPageRoute( 66 | builder: (_) => const NewCallScreen(), 67 | ), 68 | ), 69 | ), 70 | floatingActionButtonLocation: FloatingActionButtonLocation.centerDocked, 71 | bottomNavigationBar: BottomAppBar( 72 | child: Row( 73 | children: [ 74 | const SizedBox(width: 8.0), 75 | IconButton( 76 | icon: const Icon(Icons.menu), 77 | onPressed: () { 78 | showModalBottomSheet( 79 | context: context, 80 | shape: const RoundedRectangleBorder( 81 | borderRadius: BorderRadius.vertical( 82 | top: Radius.circular(8.0), 83 | ), 84 | ), 85 | builder: (_) => const MenuBottomSheet(), 86 | ); 87 | }, 88 | ), 89 | ], 90 | ), 91 | ), 92 | ), 93 | ); 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /lib/screens/login_screen.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | import 'package:call_manager/firebase/firebase.dart'; 4 | import 'package:call_manager/screens/home_screen.dart'; 5 | import 'package:call_manager/theme/app_themes.dart'; 6 | import 'package:flutter/material.dart'; 7 | import 'package:flutter/services.dart'; 8 | import 'package:material_design_icons_flutter/material_design_icons_flutter.dart'; 9 | 10 | class LoginScreen extends StatefulWidget { 11 | const LoginScreen({Key? key}) : super(key: key); 12 | 13 | static const routeName = '/login'; 14 | 15 | static Route route({ 16 | RouteSettings settings = const RouteSettings(name: routeName), 17 | }) { 18 | return MaterialPageRoute( 19 | settings: settings, 20 | builder: (BuildContext context) { 21 | return const LoginScreen(); 22 | }, 23 | ); 24 | } 25 | 26 | @override 27 | LoginScreenState createState() => LoginScreenState(); 28 | } 29 | 30 | class LoginScreenState extends State 31 | with FirebaseMixin, SingleTickerProviderStateMixin { 32 | late Animation animation; 33 | late AnimationController controller; 34 | 35 | @override 36 | void initState() { 37 | super.initState(); 38 | controller = AnimationController( 39 | duration: const Duration(milliseconds: 1500), 40 | vsync: this, 41 | ); 42 | animation = CurvedAnimation( 43 | parent: controller, 44 | curve: Curves.easeIn, 45 | ); 46 | controller.forward(); 47 | } 48 | 49 | @override 50 | void dispose() { 51 | controller.dispose(); 52 | super.dispose(); 53 | } 54 | 55 | @override 56 | // ignore: long-method 57 | Widget build(BuildContext context) { 58 | final theme = Theme.of(context); 59 | 60 | SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge); 61 | 62 | return AnnotatedRegion( 63 | value: AppThemes.themedSystemNavigationBar(context), 64 | child: Scaffold( 65 | body: SafeArea( 66 | child: Center( 67 | child: FadeTransition( 68 | opacity: animation, 69 | child: Column( 70 | mainAxisSize: MainAxisSize.min, 71 | children: [ 72 | const Text( 73 | 'Call Manager', 74 | style: TextStyle( 75 | fontSize: 48.0, 76 | fontWeight: FontWeight.bold, 77 | ), 78 | ), 79 | const SizedBox(height: 25.0), 80 | const Text( 81 | 'Your Phone Call Organizer', 82 | style: TextStyle( 83 | fontWeight: FontWeight.bold, 84 | fontSize: 16.0, 85 | ), 86 | ), 87 | const SizedBox(height: 50.0), 88 | Image.asset( 89 | 'assets/icon/call_manager_app_icon.png', 90 | width: 92.0, 91 | height: 92.0, 92 | ), 93 | const SizedBox(height: 50.0), 94 | if (currentUser != null) 95 | const Center(child: CircularProgressIndicator()) 96 | else ...[ 97 | if (Platform.isIOS) ...[ 98 | ElevatedButton.icon( 99 | style: ElevatedButton.styleFrom( 100 | primary: theme.canvasColor, 101 | onPrimary: theme.colorScheme.onSurface, 102 | elevation: 2.0, 103 | ), 104 | icon: const Icon(MdiIcons.apple), 105 | label: const Text( 106 | 'Sign in with Apple', 107 | ), 108 | onPressed: () async => 109 | await auth.signInWithApple().then((value) { 110 | if (auth.currentUser != null) { 111 | firestore.recordLoginWithApple(currentUser!.uid); 112 | Navigator.of(context).pushAndRemoveUntil( 113 | HomeScreen.route(), 114 | (route) => false, 115 | ); 116 | } 117 | }), 118 | ), 119 | ], 120 | ElevatedButton.icon( 121 | style: ElevatedButton.styleFrom( 122 | primary: theme.canvasColor, 123 | onPrimary: theme.colorScheme.onSurface, 124 | elevation: 2.0, 125 | ), 126 | icon: Image.asset( 127 | 'assets/glogo.png', 128 | width: 32.0, 129 | height: 32.0, 130 | ), 131 | label: const Text( 132 | 'Sign in with Google', 133 | ), 134 | onPressed: () async => 135 | await auth.signInWithGoogle().then((value) { 136 | if (auth.currentUser != null) { 137 | firestore.recordLoginWithGoogle(currentUser!.uid); 138 | Navigator.of(context).pushAndRemoveUntil( 139 | HomeScreen.route(), 140 | (route) => false, 141 | ); 142 | } 143 | }), 144 | ), 145 | ], 146 | ], 147 | ), 148 | ), 149 | ), 150 | ), 151 | ), 152 | ); 153 | } 154 | } 155 | -------------------------------------------------------------------------------- /lib/screens/new_call_screen.dart: -------------------------------------------------------------------------------- 1 | import 'package:bluejay/bluejay.dart'; 2 | import 'package:call_manager/data_models/call.dart'; 3 | import 'package:call_manager/firebase/firebase.dart'; 4 | import 'package:call_manager/provided.dart'; 5 | import 'package:call_manager/utils/extensions.dart'; 6 | import 'package:call_manager/widgets/clear_button.dart'; 7 | import 'package:call_manager/widgets/contact_tile.dart'; 8 | import 'package:call_manager/widgets/multiple_phone_numbers_sheet.dart'; 9 | import 'package:contacts_service/contacts_service.dart'; 10 | import 'package:datetime_picker_formfield/datetime_picker_formfield.dart'; 11 | import 'package:flutter/material.dart'; 12 | import 'package:flutter/services.dart'; 13 | import 'package:flutter_typeahead/flutter_typeahead.dart'; 14 | import 'package:intl/intl.dart'; 15 | 16 | // Add New Call Screen 17 | class NewCallScreen extends StatefulWidget { 18 | const NewCallScreen({Key? key}) : super(key: key); 19 | 20 | @override 21 | _NewCallScreenState createState() => _NewCallScreenState(); 22 | } 23 | 24 | class _NewCallScreenState extends State 25 | with FirebaseMixin, Provided { 26 | // Contact Picker stuff 27 | late Call call = Call( 28 | name: '', 29 | phoneNumber: '', 30 | ); 31 | Iterable? contacts; 32 | final dateFormat = DateFormat('EEEE, MMMM d, yyyy'); 33 | final formKey = GlobalKey(); 34 | final phoneFieldController = TextEditingController(); 35 | DateTime? reminderDate; 36 | TimeOfDay? reminderTime; 37 | Contact? selectedContact; 38 | final timeFormat = DateFormat('h:mm a'); 39 | 40 | Future saveCall() async { 41 | formKey.currentState!.save(); 42 | if (formKey.currentState!.validate()) { 43 | formKey.currentState!.save(); 44 | call.timeCreated = DateTime.now(); 45 | 46 | if (reminderDate != null && reminderTime != null) { 47 | call.reminderDate = reminderDate.toString(); 48 | call.reminderTime = reminderTime.toString(); 49 | final scheduledNotificationDateTime = DateTime( 50 | reminderDate!.year, 51 | reminderDate!.month, 52 | reminderDate!.day, 53 | reminderTime!.hour, 54 | reminderTime!.minute, 55 | ); 56 | 57 | await notificationService.scheduleNotification( 58 | call, 59 | scheduledNotificationDateTime, 60 | ); 61 | } 62 | 63 | firestore.upcomingCalls.add(call.toJson()); 64 | 65 | Navigator.of(context).pop(); 66 | } 67 | } 68 | 69 | /// Show sheet and set number 70 | void showMultiplePhoneNumbersSheet(BuildContext context) { 71 | showModalBottomSheet( 72 | context: context, 73 | shape: RoundedRectangleBorder( 74 | borderRadius: BorderRadius.circular(12), 75 | ), 76 | builder: (_) => MultiplePhoneNumbersSheet( 77 | selectedContact: selectedContact, 78 | ), 79 | ).then((value) { 80 | call.avatar = selectedContact?.avatar != null 81 | ? String.fromCharCodes(selectedContact!.avatar!) 82 | : ''; 83 | //call.name = value 84 | call.phoneNumber = value; 85 | phoneFieldController.text = value; 86 | }); 87 | } 88 | 89 | /// Set number. 90 | /// 91 | /// Called in case of single phone number. 92 | void setPhoneNumber() { 93 | call.avatar = selectedContact?.avatar != null 94 | ? String.fromCharCodes(selectedContact!.avatar!) 95 | : ''; 96 | if (selectedContact!.phones!.isEmpty) { 97 | call.phoneNumber = ''; 98 | } else { 99 | call.phoneNumber = selectedContact!.phones?.first.value!; 100 | final number = selectedContact!.phones?.first.value!; 101 | phoneFieldController.text = number!; 102 | } 103 | } 104 | 105 | @override 106 | // ignore: long-method 107 | Widget build(BuildContext context) { 108 | final theme = Theme.of(context); 109 | 110 | return Scaffold( 111 | backgroundColor: theme.canvasColor, 112 | appBar: AppBar( 113 | automaticallyImplyLeading: false, 114 | title: const Text('New Call'), 115 | ), 116 | body: Form( 117 | key: formKey, 118 | child: SingleChildScrollView( 119 | child: Padding( 120 | padding: const EdgeInsets.all(16.0), 121 | child: Column( 122 | children: [ 123 | TextEditingControllerBuilder( 124 | text: call.name ?? '', 125 | builder: (_, controller) { 126 | return TypeAheadFormField( 127 | suggestionsCallback: 128 | contactsUtility.searchContactsWithQuery, 129 | itemBuilder: (context, dynamic contact) => 130 | ContactTile(contact: contact), 131 | transitionBuilder: (context, suggestionsBox, controller) { 132 | return suggestionsBox; 133 | }, 134 | onSuggestionSelected: (dynamic contact) { 135 | selectedContact = contact; 136 | controller.text = selectedContact!.displayName!; 137 | if (selectedContact!.phones!.length > 1) { 138 | showMultiplePhoneNumbersSheet(context); 139 | } else { 140 | setPhoneNumber(); 141 | } 142 | }, 143 | validator: (input) => input == null || input == '' 144 | ? 'This field is required' 145 | : null, 146 | onSaved: (contactName) => call.name = contactName!, 147 | textFieldConfiguration: TextFieldConfiguration( 148 | textCapitalization: TextCapitalization.words, 149 | controller: controller, 150 | keyboardType: TextInputType.text, 151 | maxLines: 1, 152 | decoration: InputDecoration( 153 | prefixIcon: Icon( 154 | Icons.person_outline, 155 | color: theme.iconTheme.color, 156 | ), 157 | labelText: 'Name*', 158 | suffixIcon: ClearButton( 159 | onPressed: () => controller.clear(), 160 | ), 161 | ), 162 | ), 163 | ); 164 | }, 165 | ), 166 | const SizedBox(height: 16.0), 167 | TextEditingControllerBuilder( 168 | text: phoneFieldController.text, 169 | builder: (_, controller) { 170 | return TextFormField( 171 | validator: (input) => input == null || input == '' 172 | ? 'This field is required' 173 | : null, 174 | //onSaved: (input) => controller.text = input!, 175 | keyboardType: TextInputType.phone, 176 | maxLines: 1, 177 | autofocus: false, 178 | controller: phoneFieldController, 179 | onChanged: (value) => call.phoneNumber = value, 180 | decoration: InputDecoration( 181 | prefixIcon: Icon( 182 | Icons.phone_outlined, 183 | color: theme.iconTheme.color, 184 | ), 185 | labelText: 'Phone Number*', 186 | suffixIcon: ClearButton( 187 | onPressed: () => controller.clear(), 188 | ), 189 | ), 190 | ); 191 | }, 192 | ), 193 | const SizedBox(height: 16.0), 194 | TextEditingControllerBuilder( 195 | text: call.description ?? '', 196 | builder: (_, controller) { 197 | return TextFormField( 198 | keyboardType: TextInputType.multiline, 199 | textCapitalization: TextCapitalization.sentences, 200 | maxLines: 2, 201 | autofocus: false, 202 | controller: controller, 203 | onChanged: (value) => call.description = value, 204 | decoration: InputDecoration( 205 | labelText: 'Description', 206 | prefixIcon: Icon( 207 | Icons.comment_outlined, 208 | color: theme.iconTheme.color, 209 | ), 210 | suffixIcon: ClearButton( 211 | onPressed: () => controller.clear(), 212 | ), 213 | ), 214 | ); 215 | }, 216 | ), 217 | const SizedBox(height: 16.0), 218 | TextEditingControllerBuilder( 219 | text: '', 220 | builder: (_, controller) { 221 | return DateTimeField( 222 | format: dateFormat, 223 | onShowPicker: (context, currentValue) { 224 | return showDatePicker( 225 | context: context, 226 | initialDate: DateTime.now(), 227 | firstDate: DateTime.now(), 228 | lastDate: DateTime(DateTime.now().year + 1), 229 | ); 230 | }, 231 | onChanged: (date) => reminderDate = date, 232 | controller: controller, 233 | decoration: InputDecoration( 234 | prefixIcon: Icon( 235 | Icons.today, 236 | color: theme.iconTheme.color, 237 | ), 238 | labelText: 'Reminder Date', 239 | ), 240 | ); 241 | }, 242 | ), 243 | const SizedBox(height: 16.0), 244 | TextEditingControllerBuilder( 245 | text: '', 246 | builder: (_, controller) { 247 | return DateTimeField( 248 | format: timeFormat, 249 | onChanged: (timeOfDay) => 250 | reminderTime = TimeOfDay.fromDateTime(timeOfDay!), 251 | onShowPicker: (context, currentValue) async { 252 | final time = await showTimePicker( 253 | context: context, 254 | initialTime: TimeOfDay.fromDateTime( 255 | currentValue ?? DateTime.now(), 256 | ), 257 | ); 258 | 259 | return DateTimeField.convert(time); 260 | }, 261 | controller: controller, 262 | decoration: InputDecoration( 263 | labelText: 'Reminder Time', 264 | prefixIcon: Icon( 265 | Icons.access_time, 266 | color: theme.iconTheme.color, 267 | ), 268 | ), 269 | ); 270 | }, 271 | ), 272 | ], 273 | ), 274 | ), 275 | ), 276 | ), 277 | floatingActionButton: !MediaQuery.of(context).keyboardOpen 278 | ? FloatingActionButton.extended( 279 | onPressed: saveCall, 280 | tooltip: 'Save', 281 | elevation: 2.0, 282 | icon: const Icon(Icons.save), 283 | label: const Text('SAVE'), 284 | ) 285 | : null, 286 | floatingActionButtonLocation: FloatingActionButtonLocation.centerDocked, 287 | bottomNavigationBar: BottomAppBar( 288 | //hasNotch: false, 289 | child: Row( 290 | children: const [ 291 | SizedBox(width: 8.0), 292 | CloseButton(), 293 | ], 294 | ), 295 | ), 296 | ); 297 | } 298 | } 299 | -------------------------------------------------------------------------------- /lib/services/contacts_utility.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:contacts_service/contacts_service.dart'; 4 | import 'package:permission_handler/permission_handler.dart'; 5 | import 'package:rxdart/rxdart.dart'; 6 | 7 | class ContactsUtility { 8 | ContactsUtility._(); 9 | 10 | static Future init() async { 11 | final service = ContactsUtility._(); 12 | await service._init(); 13 | 14 | return service; 15 | } 16 | 17 | // Get initial permission state, add to stream. There will always be 18 | // an initial permission state this way. 19 | Future _init() async { 20 | var status = await Permission.contacts.status; 21 | contactsPermissionSubject.add(status); 22 | if (status.isGranted) { 23 | getContacts(); 24 | } else { 25 | //requestPermission(); 26 | } 27 | } 28 | 29 | // Used to determine whether to show a TypeAheadFormField or a TextFormField 30 | // for NewCallScreen and EditCallScreen 31 | Iterable? contacts; 32 | final contactsPermissionSubject = BehaviorSubject(); 33 | PermissionStatus? get permissionStatus => contactsPermissionSubject.value; 34 | 35 | // Used to actually ask the user for permission 36 | void requestPermission() { 37 | Permission.contacts.status.then((status) { 38 | if (status.isDenied) { 39 | Permission.contacts.request().then((value) { 40 | if (value.isGranted) { 41 | contactsPermissionSubject.add(value); 42 | getContacts(); 43 | } 44 | }); 45 | } 46 | }); 47 | } 48 | 49 | void getContacts() { 50 | ContactsService.getContacts().then((value) { 51 | contacts = value; 52 | }); 53 | } 54 | 55 | FutureOr searchContactsWithQuery(query) { 56 | if (contacts != null) { 57 | return contacts! 58 | .where((contact) => 59 | contact.displayName != null && 60 | contact.displayName!.toLowerCase().contains(query.toLowerCase())) 61 | .toList(); 62 | } 63 | 64 | return []; 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /lib/services/notifications_service.dart: -------------------------------------------------------------------------------- 1 | import 'dart:developer'; 2 | 3 | import 'package:call_manager/data_models/call.dart'; 4 | import 'package:direct_dialer/direct_dialer.dart'; 5 | import 'package:flutter_local_notifications/flutter_local_notifications.dart'; 6 | import 'package:timezone/data/latest.dart' as tz; 7 | import 'package:timezone/timezone.dart' as tz; 8 | 9 | class NotificationService { 10 | NotificationService._(); 11 | 12 | static Future init() async { 13 | final service = NotificationService._(); 14 | await service._init(); 15 | 16 | return service; 17 | } 18 | 19 | Future _init() async { 20 | tz.initializeTimeZones(); 21 | notificationsPlugin = FlutterLocalNotificationsPlugin(); 22 | const androidInitializationSettings = 23 | AndroidInitializationSettings('ic_stat_phone_in_talk'); 24 | const iosInitializationSettings = IOSInitializationSettings( 25 | requestAlertPermission: true, 26 | requestBadgePermission: true, 27 | requestSoundPermission: true, 28 | ); 29 | notificationsPlugin!.initialize( 30 | const InitializationSettings( 31 | android: androidInitializationSettings, 32 | iOS: iosInitializationSettings, 33 | ), 34 | onSelectNotification: (String? payload) async { 35 | if (payload != null) { 36 | log('notification payload: ' + payload, name: 'Call Manager'); 37 | final dialer = await DirectDialer.instance; 38 | await dialer.dial(payload); 39 | } 40 | //await CallNumber.callNumber(payload); 41 | }, 42 | ); 43 | } 44 | 45 | FlutterLocalNotificationsPlugin? notificationsPlugin; 46 | 47 | static const _androidPlatformChannelSpecifics = AndroidNotificationDetails( 48 | '1', 49 | 'Call Reminders', 50 | channelDescription: 51 | 'Call Manager sends reminders about your calls through this channel.', 52 | ); 53 | 54 | static const _iosPlatformChannelSpecifics = IOSNotificationDetails( 55 | presentAlert: true, 56 | presentBadge: true, 57 | presentSound: true, 58 | ); 59 | 60 | static const _platformChannelSpecifics = NotificationDetails( 61 | android: _androidPlatformChannelSpecifics, 62 | iOS: _iosPlatformChannelSpecifics, 63 | ); 64 | 65 | Future scheduleNotification(Call call, DateTime scheduledDate) async { 66 | await notificationsPlugin!.zonedSchedule( 67 | 0, 68 | 'Reminder: call ${call.name}', 69 | 'Tap to call ${call.name}', 70 | tz.TZDateTime.from(scheduledDate, tz.local), 71 | _platformChannelSpecifics, 72 | uiLocalNotificationDateInterpretation: 73 | UILocalNotificationDateInterpretation.wallClockTime, 74 | androidAllowWhileIdle: true, 75 | ); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /lib/services/phone_utility.dart: -------------------------------------------------------------------------------- 1 | import 'package:direct_dialer/direct_dialer.dart'; 2 | import 'package:flutter/cupertino.dart'; 3 | import 'package:permission_handler/permission_handler.dart'; 4 | import 'package:url_launcher/url_launcher.dart' as url_launcher; 5 | 6 | /// Responsible for handling various phone-related functionality. 7 | /// 8 | /// Handles permissions, making phone calls, and launching the SMS app. 9 | class PhoneUtility { 10 | PhoneUtility._(); 11 | 12 | static Future init() async { 13 | final service = PhoneUtility._(); 14 | await service._init(); 15 | 16 | return service; 17 | } 18 | 19 | Future _init() async { 20 | phonePermissionStatus = await Permission.phone.status; 21 | } 22 | 23 | PermissionStatus? phonePermissionStatus; 24 | 25 | /// 26 | void requestPhonePermission() { 27 | Permission.phone.request().then((status) { 28 | phonePermissionStatus = status; 29 | }); 30 | } 31 | 32 | /// 33 | Future callNumber(String phoneNumber) async { 34 | final dialer = await DirectDialer.instance; 35 | await dialer.dial(phoneNumber); 36 | } 37 | 38 | /// 39 | void sendSms(String? phoneNumber) async { 40 | final url = 'sms:$phoneNumber'; 41 | try { 42 | if (await url_launcher.canLaunch(url)) { 43 | await url_launcher.launch(url); 44 | } 45 | } catch (e) { 46 | debugPrint('Error sending SMS: $e'); 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /lib/services/prefs_service.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:rxdart/rxdart.dart'; 3 | import 'package:shared_preferences/shared_preferences.dart'; 4 | 5 | class PrefsService { 6 | PrefsService._(); 7 | 8 | static Future init() async { 9 | final service = PrefsService._(); 10 | await service._init(); 11 | 12 | return service; 13 | } 14 | 15 | Future _init() async { 16 | preferences = await SharedPreferences.getInstance(); 17 | readThemeModePref(); 18 | } 19 | 20 | ThemeMode? get currentThemeMode => preferencesSubject.value.themeMode; 21 | late SharedPreferences preferences; 22 | final preferencesSubject = BehaviorSubject.seeded( 23 | Preferences( 24 | brightness: Brightness.light, 25 | themeMode: ThemeMode.system, 26 | ), 27 | ); 28 | 29 | Future setThemeModePref(ThemeMode themeMode) async { 30 | await preferences.setString('themeModePref', themeMode.toString()); 31 | preferencesSubject.add( 32 | Preferences( 33 | themeMode: themeMode, 34 | brightness: preferencesSubject.value.brightness, 35 | ), 36 | ); 37 | } 38 | 39 | Future setBrightnessPref(Brightness brightness) async { 40 | await preferences.setString('brightness', brightness.toString()); 41 | preferencesSubject.add( 42 | Preferences( 43 | themeMode: preferencesSubject.value.themeMode, 44 | brightness: brightness, 45 | ), 46 | ); 47 | } 48 | 49 | void readThemeModePref() { 50 | String tm = 51 | preferences.get('themeModePref') as String? ?? 'ThemeMode.system'; 52 | ThemeMode themeMode = 53 | ThemeMode.values.firstWhere((element) => element.toString() == tm); 54 | 55 | preferencesSubject.add( 56 | Preferences( 57 | themeMode: themeMode, 58 | brightness: preferencesSubject.value.brightness, 59 | ), 60 | ); 61 | } 62 | 63 | void readBrightnessPref() { 64 | String b = preferences.getString('brightness') ?? 'Brightness.system'; 65 | final _brightness = 66 | Brightness.values.firstWhere((element) => element.toString() == b); 67 | preferencesSubject.add( 68 | Preferences( 69 | themeMode: preferencesSubject.value.themeMode, 70 | brightness: _brightness, 71 | ), 72 | ); 73 | } 74 | 75 | void close() { 76 | preferencesSubject.close(); 77 | } 78 | } 79 | 80 | class Preferences { 81 | Preferences({ 82 | required this.themeMode, 83 | required this.brightness, 84 | }); 85 | 86 | final Brightness brightness; 87 | final ThemeMode themeMode; 88 | } 89 | -------------------------------------------------------------------------------- /lib/theme/app_colors.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | // ignore_for_file: member-ordering 4 | // ignore: avoid_classes_with_only_static_members 5 | class AppColors { 6 | static const Color primaryColor = Color(0xff2962ff); 7 | static const Color accentColor = Color(0xff5784FF); 8 | static const Color cardColorLight = Color(0xffD1DDFF); 9 | static const Color cardColorDark = Color(0xff283C87); 10 | static Color canvasColorDark = Colors.grey.shade900; 11 | static Color? bottomAppColorDark = Color.lerp( 12 | Colors.grey.shade900, 13 | Colors.grey.shade800, 14 | .10, 15 | ); 16 | static Color outlinedButtonColorLight = Colors.grey.shade800; 17 | static Color iconColorLight = Colors.grey.shade800; 18 | static Color iconColorDark = Colors.white; 19 | static Color dividerColorLight = Colors.grey.shade800; 20 | } 21 | -------------------------------------------------------------------------------- /lib/theme/app_themes.dart: -------------------------------------------------------------------------------- 1 | import 'package:call_manager/theme/app_colors.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:flutter/services.dart'; 4 | import 'package:google_fonts/google_fonts.dart'; 5 | 6 | // ignore: avoid_classes_with_only_static_members 7 | class AppThemes { 8 | static ThemeData lightTheme() { 9 | return ThemeData( 10 | brightness: Brightness.light, 11 | primaryColor: AppColors.primaryColor, 12 | accentColor: AppColors.accentColor, 13 | textTheme: GoogleFonts.sourceSansProTextTheme( 14 | ThemeData.light().textTheme, 15 | ), 16 | appBarTheme: AppBarTheme( 17 | elevation: 0, 18 | centerTitle: true, 19 | backgroundColor: ThemeData.light().canvasColor, 20 | //foregroundColor: Colors.black, 21 | titleTextStyle: const TextStyle( 22 | color: Colors.black, 23 | fontSize: 20, 24 | fontWeight: FontWeight.bold, 25 | ), 26 | ), 27 | cardColor: AppColors.cardColorLight, 28 | buttonTheme: const ButtonThemeData( 29 | buttonColor: AppColors.primaryColor, 30 | ), 31 | outlinedButtonTheme: OutlinedButtonThemeData( 32 | style: OutlinedButton.styleFrom( 33 | side: BorderSide( 34 | color: AppColors.outlinedButtonColorLight, 35 | ), 36 | shape: const StadiumBorder(), 37 | primary: AppColors.outlinedButtonColorLight, 38 | ), 39 | ), 40 | iconTheme: IconThemeData( 41 | color: AppColors.iconColorLight, 42 | ), 43 | inputDecorationTheme: InputDecorationTheme( 44 | border: OutlineInputBorder( 45 | borderRadius: BorderRadius.circular(12), 46 | ), 47 | ), 48 | floatingActionButtonTheme: const FloatingActionButtonThemeData( 49 | backgroundColor: AppColors.primaryColor, 50 | foregroundColor: Colors.white, 51 | ), 52 | timePickerTheme: TimePickerThemeData( 53 | backgroundColor: ThemeData.light().canvasColor, 54 | ), 55 | dividerColor: AppColors.dividerColorLight, 56 | visualDensity: VisualDensity.adaptivePlatformDensity, 57 | ); 58 | } 59 | 60 | // ignore: long-method 61 | static ThemeData darkTheme() { 62 | return ThemeData( 63 | brightness: Brightness.dark, 64 | canvasColor: AppColors.canvasColorDark, 65 | primaryColor: AppColors.primaryColor, 66 | accentColor: AppColors.accentColor, 67 | textTheme: GoogleFonts.sourceSansProTextTheme( 68 | ThemeData.dark().textTheme, 69 | ), 70 | appBarTheme: AppBarTheme( 71 | elevation: 0, 72 | centerTitle: true, 73 | backgroundColor: AppColors.canvasColorDark, 74 | titleTextStyle: const TextStyle( 75 | color: Colors.white, 76 | fontSize: 20, 77 | fontWeight: FontWeight.bold, 78 | ), 79 | ), 80 | buttonTheme: const ButtonThemeData( 81 | buttonColor: AppColors.primaryColor, 82 | ), 83 | outlinedButtonTheme: OutlinedButtonThemeData( 84 | style: OutlinedButton.styleFrom( 85 | side: const BorderSide( 86 | color: Colors.white, 87 | ), 88 | shape: const StadiumBorder(), 89 | primary: Colors.white, 90 | ), 91 | ), 92 | cardColor: AppColors.cardColorDark, 93 | floatingActionButtonTheme: const FloatingActionButtonThemeData( 94 | backgroundColor: AppColors.primaryColor, 95 | foregroundColor: Colors.white, 96 | ), 97 | iconTheme: IconThemeData( 98 | color: AppColors.iconColorDark, 99 | ), 100 | dividerColor: Colors.white, 101 | bottomAppBarColor: AppColors.bottomAppColorDark, 102 | inputDecorationTheme: InputDecorationTheme( 103 | border: OutlineInputBorder( 104 | borderRadius: BorderRadius.circular(12), 105 | ), 106 | ), 107 | timePickerTheme: TimePickerThemeData( 108 | backgroundColor: AppColors.canvasColorDark, 109 | hourMinuteColor: Colors.grey.shade800, 110 | //dayPeriodColor: Colors.grey.shade900, 111 | ), 112 | dialogBackgroundColor: AppColors.canvasColorDark, 113 | visualDensity: VisualDensity.adaptivePlatformDensity, 114 | ); 115 | } 116 | 117 | static bool isDarkTheme(BuildContext context) { 118 | if (Theme.of(context).brightness == Brightness.dark) { 119 | return true; 120 | } else { 121 | return false; 122 | } 123 | } 124 | 125 | // lifted from Mike's flex_color_scheme package, slightly modified for my use 126 | static SystemUiOverlayStyle themedSystemNavigationBar( 127 | BuildContext context, { 128 | 129 | /// Opacity value for the system navigation bar. 130 | /// 131 | /// Use and support for this opacity value is EXPERIMENTAL, it ONLY 132 | /// works on Android API 30 (=Android 11) or higher. For more information 133 | /// and complete example of how it can be used, please see: 134 | /// https://github.com/rydmike/sysnavbar 135 | double opacity = 1, 136 | 137 | /// Brightness used if context is null, mostly used for testing. 138 | Brightness nullContextBrightness = Brightness.light, 139 | 140 | /// Background used if context is null, mostly used for testing. If null, 141 | /// then black for dark brightness, and white for light brightness. 142 | Color? nullContextBackground, 143 | }) { 144 | // ignore: parameter_assignments 145 | if (opacity < 0) opacity = 0; 146 | // ignore: parameter_assignments 147 | if (opacity > 1) opacity = 1; 148 | 149 | final bool isDark = Theme.of(context).brightness == Brightness.dark; 150 | 151 | // If nullContextBackground is null use black for dark, and white for light. 152 | nullContextBackground ??= isDark ? Colors.black : Colors.white; 153 | 154 | final Color background = Theme.of(context).bottomAppBarColor; 155 | 156 | // The used system navigation bar divider colors below were tuned to 157 | // fit well with most color schemes and possible surface branding. 158 | // Using the theme divider color does not work as the system call does 159 | // not use the alpha channel value in the passed in color, default divider 160 | // color of the theme uses alpha, using it will thus not look good. 161 | // 162 | // A future modification could expose the divider color, but then you 163 | // could just as well just copy and use this overlay style directly in your 164 | // AnnotatedRegion if this does not produce the desired result. 165 | return SystemUiOverlayStyle( 166 | systemNavigationBarColor: background.withOpacity(opacity), 167 | //systemNavigationBarColor: Colors.transparent, 168 | systemNavigationBarDividerColor: Colors.transparent, 169 | systemNavigationBarIconBrightness: 170 | isDark ? Brightness.light : Brightness.dark, 171 | statusBarColor: Colors.transparent, 172 | statusBarBrightness: isDark ? Brightness.dark : Brightness.light, 173 | statusBarIconBrightness: isDark ? Brightness.light : Brightness.dark, 174 | ); 175 | } 176 | } 177 | -------------------------------------------------------------------------------- /lib/utils/extensions.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | extension MediaQueryX on MediaQueryData { 4 | bool get keyboardOpen => viewInsets.bottom != 0; 5 | } 6 | 7 | extension ThemeModeExtensions on ThemeMode { 8 | String format() { 9 | String themeModeDisplay; 10 | switch (this) { 11 | case ThemeMode.system: 12 | themeModeDisplay = 'System default'; 13 | break; 14 | case ThemeMode.light: 15 | themeModeDisplay = 'Light theme'; 16 | break; 17 | case ThemeMode.dark: 18 | themeModeDisplay = 'Dark theme'; 19 | break; 20 | } 21 | 22 | return themeModeDisplay; 23 | } 24 | } 25 | 26 | extension BuildContextX on BuildContext { 27 | bool get isDarkTheme => Theme.of(this).brightness == Brightness.dark; 28 | } 29 | -------------------------------------------------------------------------------- /lib/utils/pass_notification.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_local_notifications/flutter_local_notifications.dart'; 3 | 4 | /// This class is an InheritedWidget that passes along the 5 | /// notification plugin to the rest of the app. The purpose of this is so that 6 | /// when the notification is tapped, the payload from the notification runs. 7 | class PassNotification extends InheritedWidget { 8 | const PassNotification( 9 | this.instance, { 10 | Key? key, 11 | required Widget child, 12 | }) : super(key: key, child: child); 13 | 14 | final FlutterLocalNotificationsPlugin? instance; 15 | 16 | static FlutterLocalNotificationsPlugin? of(BuildContext context) { 17 | return context 18 | .dependOnInheritedWidgetOfExactType()! 19 | .instance; 20 | } 21 | 22 | @override 23 | bool updateShouldNotify(InheritedWidget oldWidget) { 24 | return false; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /lib/widgets/call_avatar.dart: -------------------------------------------------------------------------------- 1 | import 'dart:typed_data'; 2 | 3 | import 'package:call_manager/data_models/call.dart'; 4 | import 'package:flutter/material.dart'; 5 | 6 | class CallAvatar extends StatelessWidget { 7 | const CallAvatar({ 8 | Key? key, 9 | required this.call, 10 | }) : super(key: key); 11 | 12 | final Call call; 13 | 14 | @override 15 | Widget build(BuildContext context) { 16 | if (call.hasAvatar) { 17 | return ClipOval( 18 | child: CircleAvatar( 19 | child: Image.memory( 20 | Uint8List.fromList(call.avatar!.codeUnits), 21 | gaplessPlayback: true, 22 | ), 23 | ), 24 | ); 25 | } else { 26 | return CircleAvatar( 27 | child: const Icon( 28 | Icons.person_outline, 29 | color: Colors.white, 30 | ), 31 | backgroundColor: Theme.of(context).primaryColor, 32 | ); 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /lib/widgets/call_card.dart: -------------------------------------------------------------------------------- 1 | import 'package:call_manager/data_models/call.dart'; 2 | import 'package:call_manager/firebase/firebase.dart'; 3 | import 'package:call_manager/provided.dart'; 4 | import 'package:call_manager/screens/edit_call_screen.dart'; 5 | import 'package:call_manager/utils/extensions.dart'; 6 | import 'package:call_manager/widgets/call_avatar.dart'; 7 | import 'package:call_manager/widgets/dialogs/complete_call_dialog.dart'; 8 | import 'package:call_manager/widgets/dialogs/delete_call_dialog.dart'; 9 | import 'package:call_manager/widgets/dialogs/mark_incomplete_dialog.dart'; 10 | import 'package:call_manager/widgets/schedule_notification_sheet.dart'; 11 | import 'package:flutter/material.dart'; 12 | import 'package:groovin_widgets/groovin_widgets.dart'; 13 | 14 | class CallCard extends StatefulWidget { 15 | const CallCard({ 16 | Key? key, 17 | required this.call, 18 | }) : super(key: key); 19 | 20 | final Call call; 21 | 22 | @override 23 | CallCardState createState() { 24 | return CallCardState(); 25 | } 26 | } 27 | 28 | class CallCardState extends State with FirebaseMixin, Provided { 29 | bool isExpanded = false; 30 | 31 | @override 32 | // ignore: long-method 33 | Widget build(BuildContext context) { 34 | return Material( 35 | elevation: 2.0, 36 | color: Theme.of(context).cardColor, 37 | shape: const RoundedRectangleBorder( 38 | borderRadius: BorderRadius.all( 39 | Radius.circular(8.0), 40 | ), 41 | ), 42 | child: GroovinExpansionTile( 43 | leading: CallAvatar( 44 | call: widget.call, 45 | ), 46 | title: Text( 47 | widget.call.name!, 48 | style: TextStyle( 49 | color: context.isDarkTheme ? Colors.white : Colors.black, 50 | fontWeight: FontWeight.bold, 51 | ), 52 | ), 53 | subtitle: Text(widget.call.phoneNumber!), 54 | onExpansionChanged: (value) { 55 | setState(() => isExpanded = value); 56 | }, 57 | inkwellRadius: !isExpanded 58 | ? const BorderRadius.all(Radius.circular(8.0)) 59 | : const BorderRadius.only( 60 | topRight: Radius.circular(8.0), 61 | topLeft: Radius.circular(8.0), 62 | ), 63 | children: [ 64 | if (widget.call.description != null && 65 | widget.call.description!.isNotEmpty) ...[ 66 | Row( 67 | children: [ 68 | const SizedBox(width: 16), 69 | Text(widget.call.description!), 70 | ], 71 | ), 72 | const SizedBox(height: 4), 73 | ], 74 | Row( 75 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 76 | children: [ 77 | IconButton( 78 | icon: const Icon(Icons.delete_outline), 79 | onPressed: () { 80 | showDialog( 81 | context: context, 82 | builder: (_) => DeleteCallDialog( 83 | call: widget.call, 84 | ), 85 | ); 86 | }, 87 | tooltip: 'Complete', 88 | ), 89 | if (widget.call.isNotCompleted) ...[ 90 | IconButton( 91 | icon: const Icon(Icons.check), 92 | onPressed: () { 93 | showDialog( 94 | context: context, 95 | builder: (_) => CompleteCallDialog( 96 | call: widget.call, 97 | ), 98 | ); 99 | }, 100 | tooltip: 'Complete', 101 | ), 102 | ] else ...[ 103 | IconButton( 104 | icon: const Icon(Icons.check_circle), 105 | onPressed: () { 106 | showDialog( 107 | context: context, 108 | builder: (_) => MarkIncompleteDialog( 109 | call: widget.call, 110 | ), 111 | ); 112 | }, 113 | tooltip: 'Complete', 114 | ), 115 | ], 116 | IconButton( 117 | icon: const Icon(Icons.notifications_none), 118 | onPressed: () { 119 | showModalBottomSheet( 120 | context: context, 121 | shape: RoundedRectangleBorder( 122 | borderRadius: BorderRadius.circular(12), 123 | ), 124 | builder: (_) => ScheduleNotificationSheet( 125 | call: widget.call, 126 | ), 127 | ); 128 | }, 129 | tooltip: 'Set reminder', 130 | ), 131 | IconButton( 132 | icon: const Icon(Icons.edit_outlined), 133 | onPressed: () { 134 | Navigator.of(context).push( 135 | MaterialPageRoute( 136 | builder: (_) => EditCallScreen( 137 | call: widget.call, 138 | ), 139 | ), 140 | ); 141 | }, 142 | tooltip: 'Edit this call', 143 | ), 144 | IconButton( 145 | icon: const Icon(Icons.phone_outlined), 146 | onPressed: () async { 147 | await phoneUtility.callNumber(widget.call.phoneNumber!); 148 | }, 149 | tooltip: 'Call ${widget.call.name}', 150 | ), 151 | ], 152 | ), 153 | ], 154 | ), 155 | ); 156 | } 157 | } 158 | -------------------------------------------------------------------------------- /lib/widgets/calls_view.dart: -------------------------------------------------------------------------------- 1 | import 'package:call_manager/data_models/call.dart'; 2 | import 'package:call_manager/firebase/firebase.dart'; 3 | import 'package:call_manager/widgets/call_card.dart'; 4 | import 'package:flutter/material.dart'; 5 | import 'package:rxdart/rxdart.dart'; 6 | 7 | /// This widget represents the content on the main screen of the app 8 | class CallsView extends StatefulWidget { 9 | const CallsView({ 10 | Key? key, 11 | }) : super(key: key); 12 | 13 | @override 14 | _CallsViewState createState() => _CallsViewState(); 15 | } 16 | 17 | class _CallsViewState extends State with FirebaseMixin { 18 | @override 19 | // ignore: long-method 20 | Widget build(BuildContext context) { 21 | return StreamBuilder>( 22 | stream: CombineLatestStream.combine2( 23 | firestore.upcomingCalls.snapshots(), 24 | firestore.completedCalls.snapshots(), 25 | (a, b) => [ 26 | a as FirestoreDocument, 27 | b as FirestoreDocument, 28 | ], 29 | ), 30 | builder: (_, AsyncSnapshot> snapshot) { 31 | return MobileCallsView( 32 | snapshot: snapshot, 33 | ); 34 | }, 35 | ); 36 | } 37 | } 38 | 39 | class MobileCallsView extends StatefulWidget { 40 | const MobileCallsView({ 41 | Key? key, 42 | required this.snapshot, 43 | }) : super(key: key); 44 | 45 | final AsyncSnapshot> snapshot; 46 | 47 | @override 48 | _MobileCallsViewState createState() => _MobileCallsViewState(); 49 | } 50 | 51 | class _MobileCallsViewState extends State 52 | with SingleTickerProviderStateMixin { 53 | late final tabController = TabController(length: 2, vsync: this); 54 | 55 | @override 56 | Widget build(BuildContext context) { 57 | return Column( 58 | children: [ 59 | TabBar( 60 | controller: tabController, 61 | indicatorColor: Theme.of(context).indicatorColor.withOpacity(.40), 62 | labelColor: Theme.of(context).colorScheme.onSurface, 63 | tabs: const [ 64 | Tab( 65 | child: Text('Upcoming'), 66 | ), 67 | Tab( 68 | child: Text('Completed'), 69 | ), 70 | ], 71 | ), 72 | Expanded( 73 | child: TabBarView( 74 | controller: tabController, 75 | children: [ 76 | // ignore: unnecessary_null_comparison 77 | if (!widget.snapshot.hasData) ...[ 78 | const Center( 79 | child: CircularProgressIndicator(), 80 | ), 81 | ] else ...[ 82 | // Upcoming calls 83 | if (widget.snapshot.data!.first.docs.isNotEmpty) ...[ 84 | _CallsList( 85 | calls: widget.snapshot.data!.first.docs, 86 | ), 87 | ] else ...[ 88 | Center( 89 | child: Text( 90 | 'Tap "New Call" to get started!', 91 | style: Theme.of(context).textTheme.headline6, 92 | ), 93 | ), 94 | ], 95 | // Completed calls 96 | if (widget.snapshot.data!.last.docs.isNotEmpty) ...[ 97 | _CallsList( 98 | calls: widget.snapshot.data!.last.docs, 99 | ), 100 | ] else ...[ 101 | Center( 102 | child: Text( 103 | 'Nothing here!', 104 | style: Theme.of(context).textTheme.headline6, 105 | ), 106 | ), 107 | ], 108 | ], 109 | if (!widget.snapshot.hasData) ...[ 110 | const Center( 111 | child: CircularProgressIndicator(), 112 | ), 113 | ], 114 | ], 115 | ), 116 | ), 117 | ], 118 | ); 119 | } 120 | } 121 | 122 | class _CallsList extends StatelessWidget { 123 | const _CallsList({ 124 | Key? key, 125 | required this.calls, 126 | }) : super(key: key); 127 | 128 | final List calls; 129 | 130 | @override 131 | Widget build(BuildContext context) { 132 | return ListView.builder( 133 | itemCount: calls.length, 134 | itemBuilder: (context, index) { 135 | final call = Call.fromJsonWithDocId( 136 | calls[index].data() as Map, 137 | calls[index].id, 138 | ); 139 | 140 | return Padding( 141 | padding: const EdgeInsets.all(8.0), 142 | child: CallCard( 143 | call: call, 144 | ), 145 | ); 146 | }, 147 | ); 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /lib/widgets/clear_button.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class ClearButton extends StatelessWidget { 4 | const ClearButton({ 5 | Key? key, 6 | this.onPressed, 7 | }) : super(key: key); 8 | 9 | final VoidCallback? onPressed; 10 | 11 | @override 12 | Widget build(BuildContext context) { 13 | return IconButton( 14 | icon: Icon( 15 | Icons.close, 16 | color: Theme.of(context).iconTheme.color, 17 | ), 18 | onPressed: onPressed, 19 | ); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /lib/widgets/contact_avatar.dart: -------------------------------------------------------------------------------- 1 | import 'package:contacts_service/contacts_service.dart'; 2 | import 'package:flutter/material.dart'; 3 | 4 | class ContactAvatar extends StatelessWidget { 5 | const ContactAvatar({ 6 | Key? key, 7 | this.contact, 8 | }) : super(key: key); 9 | 10 | final Contact? contact; 11 | @override 12 | Widget build(BuildContext context) { 13 | if (contact!.avatar == null || contact!.avatar!.isEmpty) { 14 | return const CircleAvatar( 15 | child: Icon(Icons.person_outline), 16 | ); 17 | } else { 18 | return ClipOval( 19 | child: CircleAvatar( 20 | child: Image.memory( 21 | contact!.avatar!, 22 | gaplessPlayback: true, 23 | ), 24 | ), 25 | ); 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /lib/widgets/contact_tile.dart: -------------------------------------------------------------------------------- 1 | import 'package:call_manager/widgets/contact_avatar.dart'; 2 | import 'package:contacts_service/contacts_service.dart'; 3 | import 'package:flutter/material.dart'; 4 | 5 | class ContactTile extends StatelessWidget { 6 | const ContactTile({ 7 | Key? key, 8 | required this.contact, 9 | }) : super(key: key); 10 | 11 | final Contact contact; 12 | 13 | @override 14 | Widget build(BuildContext context) { 15 | return ListTile( 16 | leading: ContactAvatar(contact: contact), 17 | title: Text(contact.displayName!), 18 | ); 19 | } 20 | } 21 | 22 | -------------------------------------------------------------------------------- /lib/widgets/dialogs/complete_call_dialog.dart: -------------------------------------------------------------------------------- 1 | import 'package:call_manager/data_models/call.dart'; 2 | import 'package:call_manager/firebase/firebase.dart'; 3 | import 'package:flutter/material.dart'; 4 | 5 | class CompleteCallDialog extends StatefulWidget { 6 | const CompleteCallDialog({ 7 | Key? key, 8 | required this.call, 9 | }) : super(key: key); 10 | 11 | final Call call; 12 | @override 13 | _CompleteCallDialogState createState() => _CompleteCallDialogState(); 14 | } 15 | 16 | class _CompleteCallDialogState extends State 17 | with FirebaseMixin { 18 | @override 19 | Widget build(BuildContext context) { 20 | return AlertDialog( 21 | content: const Text('Mark this call as completed?'), 22 | actions: [ 23 | TextButton( 24 | child: const Text('CANCEL'), 25 | onPressed: () => Navigator.of(context).pop(), 26 | ), 27 | TextButton( 28 | child: const Text('YES'), 29 | onPressed: () { 30 | firestore.completeCall(widget.call); 31 | Navigator.of(context).pop(); 32 | }, 33 | ), 34 | ], 35 | ); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /lib/widgets/dialogs/delete_all_dialog.dart: -------------------------------------------------------------------------------- 1 | import 'package:call_manager/firebase/firebase.dart'; 2 | import 'package:flutter/material.dart'; 3 | 4 | class DeleteAllDialog extends StatefulWidget { 5 | const DeleteAllDialog({Key? key}) : super(key: key); 6 | 7 | @override 8 | _DeleteAllDialogState createState() => _DeleteAllDialogState(); 9 | } 10 | 11 | class _DeleteAllDialogState extends State with FirebaseMixin { 12 | @override 13 | Widget build(BuildContext context) { 14 | return AlertDialog( 15 | content: const Text( 16 | 'Are you sure you want to delete all calls? This cannot be undone.', 17 | ), 18 | actions: [ 19 | TextButton( 20 | onPressed: () => Navigator.of(context).pop(), 21 | child: const Text('CANCEL'), 22 | ), 23 | TextButton( 24 | onPressed: () async { 25 | Navigator.of(context).pop(); 26 | 27 | final result = await firestore.deleteAllCalls(); 28 | 29 | if (result == false) { 30 | final snackBar = SnackBar( 31 | content: const Text('There are no calls to delete'), 32 | action: SnackBarAction( 33 | label: 'Dismiss', 34 | // ignore: no-empty-block 35 | onPressed: () {}, 36 | ), 37 | duration: const Duration(seconds: 3), 38 | ); 39 | ScaffoldMessenger.of(context).showSnackBar(snackBar); 40 | } 41 | }, 42 | child: const Text('DELETE'), 43 | ), 44 | ], 45 | ); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /lib/widgets/dialogs/delete_call_dialog.dart: -------------------------------------------------------------------------------- 1 | import 'package:call_manager/data_models/call.dart'; 2 | import 'package:call_manager/firebase/firebase.dart'; 3 | import 'package:flutter/material.dart'; 4 | 5 | class DeleteCallDialog extends StatefulWidget { 6 | const DeleteCallDialog({ 7 | Key? key, 8 | required this.call, 9 | }) : super(key: key); 10 | 11 | final Call call; 12 | @override 13 | _DeleteCallDialogState createState() => _DeleteCallDialogState(); 14 | } 15 | 16 | class _DeleteCallDialogState extends State 17 | with FirebaseMixin { 18 | @override 19 | Widget build(BuildContext context) { 20 | return AlertDialog( 21 | content: const Text('Delete call?'), 22 | actions: [ 23 | TextButton( 24 | child: const Text('CANCEL'), 25 | onPressed: () => Navigator.of(context).pop(), 26 | ), 27 | TextButton( 28 | child: const Text('YES'), 29 | onPressed: () { 30 | firestore.deleteCall(widget.call); 31 | Navigator.of(context).pop(); 32 | }, 33 | ), 34 | ], 35 | ); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /lib/widgets/dialogs/log_out_dialog.dart: -------------------------------------------------------------------------------- 1 | import 'package:call_manager/firebase/firebase.dart'; 2 | import 'package:call_manager/screens/login_screen.dart'; 3 | import 'package:flutter/material.dart'; 4 | 5 | class LogOutDialog extends StatefulWidget { 6 | const LogOutDialog({Key? key}) : super(key: key); 7 | 8 | @override 9 | _LogOutDialogState createState() => _LogOutDialogState(); 10 | } 11 | 12 | class _LogOutDialogState extends State with FirebaseMixin { 13 | @override 14 | Widget build(BuildContext context) { 15 | return AlertDialog( 16 | //title: Text('Log Out'), 17 | content: const Text('Are you sure you want to log out?'), 18 | actions: [ 19 | TextButton( 20 | onPressed: () => Navigator.of(context).pop(), 21 | child: const Text('NO'), 22 | ), 23 | TextButton( 24 | onPressed: () async { 25 | firestore.recordLogout(currentUser!.uid); 26 | await auth.signOut(); 27 | Navigator.of(context).pushAndRemoveUntil( 28 | MaterialPageRoute( 29 | builder: (context) => const LoginScreen(), 30 | ), 31 | (route) => false, 32 | ); 33 | }, 34 | child: const Text('YES'), 35 | ), 36 | ], 37 | ); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /lib/widgets/dialogs/mark_incomplete_dialog.dart: -------------------------------------------------------------------------------- 1 | import 'package:call_manager/data_models/call.dart'; 2 | import 'package:call_manager/firebase/firebase.dart'; 3 | import 'package:flutter/material.dart'; 4 | 5 | class MarkIncompleteDialog extends StatefulWidget { 6 | const MarkIncompleteDialog({ 7 | Key? key, 8 | required this.call, 9 | }) : super(key: key); 10 | 11 | final Call call; 12 | @override 13 | _MarkIncompleteDialogState createState() => _MarkIncompleteDialogState(); 14 | } 15 | 16 | class _MarkIncompleteDialogState extends State 17 | with FirebaseMixin { 18 | @override 19 | Widget build(BuildContext context) { 20 | return AlertDialog( 21 | content: const Text('Mark this call as incomplete?'), 22 | actions: [ 23 | TextButton( 24 | child: const Text('CANCEL'), 25 | onPressed: () => Navigator.of(context).pop(), 26 | ), 27 | TextButton( 28 | child: const Text('YES'), 29 | onPressed: () { 30 | firestore.incompleteCall(widget.call); 31 | Navigator.of(context).pop(); 32 | }, 33 | ), 34 | ], 35 | ); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /lib/widgets/dialogs/theme_switcher_dialog.dart: -------------------------------------------------------------------------------- 1 | import 'package:call_manager/provided.dart'; 2 | import 'package:flutter/material.dart'; 3 | 4 | class ThemeSwitcherDialog extends StatefulWidget { 5 | const ThemeSwitcherDialog({Key? key}) : super(key: key); 6 | @override 7 | _ThemeSwitcherDialogState createState() => _ThemeSwitcherDialogState(); 8 | } 9 | 10 | class _ThemeSwitcherDialogState extends State 11 | with Provided { 12 | void _onThemeSelection(ThemeMode themeMode) { 13 | prefsService.setThemeModePref(themeMode); 14 | if (themeMode == ThemeMode.system && 15 | Theme.of(context).brightness == Brightness.light) { 16 | prefsService.setBrightnessPref(Brightness.light); 17 | } 18 | if (themeMode == ThemeMode.system && 19 | Theme.of(context).brightness == Brightness.dark) { 20 | prefsService.setBrightnessPref(Brightness.dark); 21 | } 22 | if (themeMode == ThemeMode.light) { 23 | prefsService.setBrightnessPref(Brightness.light); 24 | } 25 | 26 | if (themeMode == ThemeMode.dark) { 27 | prefsService.setBrightnessPref(Brightness.dark); 28 | } 29 | 30 | // ignore: no-empty-block 31 | setState(() {}); 32 | 33 | Navigator.of(context).pop(themeMode); 34 | } 35 | 36 | @override 37 | Widget build(BuildContext context) { 38 | return SimpleDialog( 39 | backgroundColor: Theme.of(context).canvasColor, 40 | title: const Text('Change app theme'), 41 | children: [ 42 | RadioListTile( 43 | title: const Text('System theme'), 44 | value: ThemeMode.system, 45 | selected: 46 | prefsService.currentThemeMode == ThemeMode.system ? true : false, 47 | activeColor: Theme.of(context).accentColor, 48 | groupValue: prefsService.currentThemeMode, 49 | onChanged: (value) => _onThemeSelection(value!), 50 | ), 51 | RadioListTile( 52 | title: const Text('Light theme'), 53 | value: ThemeMode.light, 54 | selected: 55 | prefsService.currentThemeMode == ThemeMode.light ? true : false, 56 | activeColor: Theme.of(context).accentColor, 57 | groupValue: prefsService.currentThemeMode, 58 | onChanged: (value) => _onThemeSelection(value!), 59 | ), 60 | RadioListTile( 61 | title: const Text('Dark theme'), 62 | value: ThemeMode.dark, 63 | selected: 64 | prefsService.currentThemeMode == ThemeMode.dark ? true : false, 65 | activeColor: Theme.of(context).accentColor, 66 | groupValue: prefsService.currentThemeMode, 67 | onChanged: (value) => _onThemeSelection(value!), 68 | ), 69 | ], 70 | ); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /lib/widgets/menu_bottom_sheet.dart: -------------------------------------------------------------------------------- 1 | import 'package:call_manager/firebase/firebase_mixin.dart'; 2 | import 'package:call_manager/provided.dart'; 3 | import 'package:call_manager/services/prefs_service.dart'; 4 | import 'package:call_manager/utils/extensions.dart'; 5 | import 'package:call_manager/widgets/dialogs/delete_all_dialog.dart'; 6 | import 'package:call_manager/widgets/dialogs/log_out_dialog.dart'; 7 | import 'package:call_manager/widgets/dialogs/theme_switcher_dialog.dart'; 8 | import 'package:call_manager/widgets/theme_icon.dart'; 9 | import 'package:call_manager/widgets/user_account_avatar.dart'; 10 | import 'package:flutter/material.dart'; 11 | import 'package:groovin_widgets/groovin_widgets.dart'; 12 | import 'package:material_design_icons_flutter/material_design_icons_flutter.dart'; 13 | import 'package:package_info/package_info.dart'; 14 | import 'package:url_launcher/url_launcher.dart'; 15 | import 'package:wiredash/wiredash.dart'; 16 | 17 | /// Represents the BottomSheet launched from the BottomAppBar 18 | /// on the HomeScreen widget 19 | class MenuBottomSheet extends StatefulWidget { 20 | const MenuBottomSheet({Key? key}) : super(key: key); 21 | 22 | @override 23 | _MenuBottomSheetState createState() => _MenuBottomSheetState(); 24 | } 25 | 26 | class _MenuBottomSheetState extends State 27 | with FirebaseMixin, Provided { 28 | // Set initial package info 29 | PackageInfo _packageInfo = PackageInfo( 30 | appName: 'Unknown', 31 | packageName: 'Unknown', 32 | version: 'Unknown', 33 | buildNumber: 'Unknown', 34 | ); 35 | 36 | @override 37 | void initState() { 38 | super.initState(); 39 | getPackageDetails(); 40 | } 41 | 42 | // Get and set the package details 43 | Future getPackageDetails() async { 44 | final packageInfo = await PackageInfo.fromPlatform(); 45 | setState(() => _packageInfo = packageInfo); 46 | } 47 | 48 | @override 49 | // ignore: long-method 50 | Widget build(BuildContext context) { 51 | final theme = Theme.of(context); 52 | 53 | return SafeArea( 54 | child: Column( 55 | mainAxisSize: MainAxisSize.min, 56 | children: [ 57 | Padding( 58 | padding: const EdgeInsets.only(top: 8.0), 59 | child: ModalDrawerHandle(), 60 | ), 61 | ListTile( 62 | leading: const UserAccountAvatar(), 63 | title: currentUser!.displayName != null 64 | ? Text(currentUser!.displayName ?? 'user') 65 | : Text(currentUser!.email!), 66 | subtitle: currentUser!.displayName != null 67 | ? Text(currentUser!.email ?? 'email') 68 | : null, 69 | trailing: TextButton( 70 | child: const Text('LOG OUT'), 71 | onPressed: () { 72 | showDialog( 73 | context: context, 74 | builder: (_) => const LogOutDialog(), 75 | ); 76 | }, 77 | ), 78 | ), 79 | const Divider( 80 | color: Colors.grey, 81 | height: 0.0, 82 | ), 83 | ListTile( 84 | title: const Text('Delete All Calls'), 85 | leading: Icon( 86 | MdiIcons.deleteSweepOutline, 87 | color: theme.brightness == Brightness.light 88 | ? Colors.black 89 | : Colors.white, 90 | ), 91 | onTap: () { 92 | showDialog( 93 | context: context, 94 | builder: (_) => const DeleteAllDialog(), 95 | ); 96 | }, 97 | ), 98 | StreamBuilder( 99 | stream: prefsService.preferencesSubject, 100 | initialData: prefsService.preferencesSubject.value, 101 | builder: (context, snapshot) { 102 | return ListTile( 103 | leading: const ThemeIcon(), 104 | title: const Text('Toggle app theme'), 105 | subtitle: Text( 106 | snapshot.data!.themeMode.format(), 107 | ), 108 | onTap: () => showDialog( 109 | context: context, 110 | builder: (_) => const ThemeSwitcherDialog(), 111 | ), 112 | ); 113 | }, 114 | ), 115 | const Divider( 116 | color: Colors.grey, 117 | height: 0.0, 118 | ), 119 | ListTile( 120 | leading: Icon( 121 | MdiIcons.github, 122 | color: theme.brightness == Brightness.light 123 | ? Colors.black 124 | : Colors.white, 125 | ), 126 | title: Text('Call Manager v${_packageInfo.version}'), 127 | subtitle: const Text('View source code'), 128 | onTap: () { 129 | launch('https:github.com/GroovinChip/CallManager'); 130 | }, 131 | ), 132 | ListTile( 133 | leading: const Icon(MdiIcons.thoughtBubbleOutline), 134 | title: const Text('Send Feedback'), 135 | onTap: () => Wiredash.of(context)! 136 | ..setBuildProperties( 137 | buildVersion: _packageInfo.version, 138 | buildNumber: _packageInfo.buildNumber, 139 | ) 140 | ..show(), 141 | ), 142 | ], 143 | ), 144 | ); 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /lib/widgets/multiple_phone_numbers_sheet.dart: -------------------------------------------------------------------------------- 1 | import 'package:contacts_service/contacts_service.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:groovin_widgets/groovin_widgets.dart'; 4 | 5 | class MultiplePhoneNumbersSheet extends StatelessWidget { 6 | const MultiplePhoneNumbersSheet({ 7 | required this.selectedContact, 8 | Key? key, 9 | }) : super(key: key); 10 | 11 | final Contact? selectedContact; 12 | 13 | @override 14 | Widget build(BuildContext context) { 15 | return SafeArea( 16 | child: Column( 17 | mainAxisSize: MainAxisSize.min, 18 | children: [ 19 | Padding( 20 | padding: const EdgeInsets.all(8), 21 | child: ModalDrawerHandle(), 22 | ), 23 | Row( 24 | mainAxisAlignment: MainAxisAlignment.center, 25 | children: const [ 26 | Text( 27 | 'Choose phone number', 28 | style: TextStyle( 29 | fontWeight: FontWeight.bold, 30 | fontSize: 16.0, 31 | ), 32 | ), 33 | ], 34 | ), 35 | ...List.generate(selectedContact!.phones!.length, (index) { 36 | List _phoneNumbers = selectedContact!.phones!.toList(); 37 | Icon phoneType; 38 | switch (_phoneNumbers[index].label) { 39 | case 'mobile': 40 | phoneType = const Icon(Icons.smartphone); 41 | break; 42 | case 'work': 43 | phoneType = const Icon(Icons.business); 44 | break; 45 | case 'home': 46 | phoneType = const Icon(Icons.home_outlined); 47 | break; 48 | default: 49 | phoneType = const Icon(Icons.phone_outlined); 50 | } 51 | 52 | return ListTile( 53 | leading: phoneType, 54 | title: Text(_phoneNumbers[index].value!), 55 | subtitle: Text(_phoneNumbers[index].label!), 56 | onTap: () => 57 | Navigator.of(context).pop(_phoneNumbers[index].value), 58 | ); 59 | }), 60 | ], 61 | ), 62 | ); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /lib/widgets/schedule_notification_sheet.dart: -------------------------------------------------------------------------------- 1 | import 'package:call_manager/data_models/call.dart'; 2 | import 'package:call_manager/firebase/firebase_mixin.dart'; 3 | import 'package:call_manager/provided.dart'; 4 | import 'package:datetime_picker_formfield/datetime_picker_formfield.dart'; 5 | import 'package:flutter/material.dart'; 6 | import 'package:groovin_widgets/groovin_widgets.dart'; 7 | import 'package:intl/intl.dart'; 8 | import 'package:material_design_icons_flutter/material_design_icons_flutter.dart'; 9 | 10 | class ScheduleNotificationSheet extends StatefulWidget { 11 | const ScheduleNotificationSheet({ 12 | Key? key, 13 | required this.call, 14 | }) : super(key: key); 15 | 16 | final Call call; 17 | 18 | @override 19 | _ScheduleNotificationSheetState createState() => 20 | _ScheduleNotificationSheetState(); 21 | } 22 | 23 | class _ScheduleNotificationSheetState extends State 24 | with FirebaseMixin, Provided { 25 | final dateFormat = DateFormat('EEEE, MMMM d, yyyy'); 26 | 27 | String? numberToCallOnNotificationTap; 28 | DateTime? reminderDate; 29 | late TimeOfDay reminderTime; 30 | 31 | final timeFormat = DateFormat('h:mm a'); 32 | 33 | Future scheduleNotificationReminder() async { 34 | var scheduledNotificationDateTime = DateTime( 35 | reminderDate!.year, 36 | reminderDate!.month, 37 | reminderDate!.day, 38 | reminderTime.hour, 39 | reminderTime.minute, 40 | ); 41 | 42 | await notificationService.scheduleNotification( 43 | widget.call, 44 | scheduledNotificationDateTime, 45 | ); 46 | 47 | Navigator.of(context).pop(); 48 | } 49 | 50 | @override 51 | // ignore: long-method 52 | Widget build(BuildContext context) { 53 | final theme = Theme.of(context); 54 | 55 | return SafeArea( 56 | child: Padding( 57 | padding: const EdgeInsets.fromLTRB(16, 8, 16, 0), 58 | child: Column( 59 | mainAxisSize: MainAxisSize.min, 60 | children: [ 61 | ModalDrawerHandle(), 62 | const SizedBox(height: 12.0), 63 | DateTimeField( 64 | format: dateFormat, 65 | onShowPicker: (context, currentValue) { 66 | return showDatePicker( 67 | context: context, 68 | initialDate: DateTime.now(), 69 | firstDate: DateTime.now(), 70 | lastDate: DateTime(DateTime.now().year + 1), 71 | ); 72 | }, 73 | onChanged: (date) => reminderDate = date, 74 | decoration: InputDecoration( 75 | prefixIcon: Icon( 76 | Icons.today, 77 | color: theme.iconTheme.color, 78 | ), 79 | labelText: 'Reminder Date', 80 | ), 81 | ), 82 | const SizedBox(height: 16.0), 83 | DateTimeField( 84 | format: timeFormat, 85 | enabled: true, 86 | onChanged: (timeOfDay) => 87 | reminderTime = TimeOfDay.fromDateTime(timeOfDay!), 88 | onShowPicker: (context, currentValue) async { 89 | final time = await showTimePicker( 90 | context: context, 91 | initialTime: 92 | TimeOfDay.fromDateTime(currentValue ?? DateTime.now()), 93 | ); 94 | 95 | return DateTimeField.convert(time); 96 | }, 97 | decoration: InputDecoration( 98 | labelText: 'Reminder Time', 99 | prefixIcon: Icon( 100 | Icons.access_time, 101 | color: theme.iconTheme.color, 102 | ), 103 | ), 104 | ), 105 | const SizedBox(height: 8), 106 | Row( 107 | children: [ 108 | Expanded( 109 | child: ElevatedButton.icon( 110 | icon: const Icon(MdiIcons.bellPlusOutline), 111 | label: const Text('Set Reminder'), 112 | style: ElevatedButton.styleFrom( 113 | primary: Theme.of(context).primaryColor, 114 | shape: RoundedRectangleBorder( 115 | borderRadius: BorderRadius.circular(12), 116 | ), 117 | ), 118 | onPressed: () { 119 | numberToCallOnNotificationTap = widget.call.phoneNumber; 120 | scheduleNotificationReminder(); 121 | }, 122 | ), 123 | ), 124 | ], 125 | ), 126 | ], 127 | ), 128 | ), 129 | ); 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /lib/widgets/theme_icon.dart: -------------------------------------------------------------------------------- 1 | import 'package:call_manager/theme/app_themes.dart'; 2 | import 'package:flutter/material.dart'; 3 | 4 | class ThemeIcon extends StatefulWidget { 5 | const ThemeIcon({Key? key}) : super(key: key); 6 | 7 | @override 8 | _ThemeIconState createState() => _ThemeIconState(); 9 | } 10 | 11 | class _ThemeIconState extends State { 12 | IconData? _themeIconData; 13 | 14 | @override 15 | void didChangeDependencies() { 16 | super.didChangeDependencies(); 17 | if (AppThemes.isDarkTheme(context)) { 18 | setState(() => _themeIconData = Icons.wb_sunny_outlined); 19 | } else { 20 | setState(() => _themeIconData = Icons.nightlight_round); 21 | } 22 | } 23 | 24 | @override 25 | Widget build(BuildContext context) { 26 | return Icon( 27 | _themeIconData, 28 | color: Theme.of(context).iconTheme.color, 29 | ); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /lib/widgets/user_account_avatar.dart: -------------------------------------------------------------------------------- 1 | import 'package:call_manager/firebase/firebase_mixin.dart'; 2 | import 'package:flutter/material.dart'; 3 | 4 | class UserAccountAvatar extends StatefulWidget { 5 | const UserAccountAvatar({Key? key}) : super(key: key); 6 | 7 | @override 8 | _UserAccountAvatarState createState() => _UserAccountAvatarState(); 9 | } 10 | 11 | class _UserAccountAvatarState extends State 12 | with FirebaseMixin { 13 | @override 14 | Widget build(BuildContext context) { 15 | if (currentUser == null || currentUser!.photoURL == null) { 16 | return CircleAvatar( 17 | child: const Icon( 18 | Icons.person_outline, 19 | color: Colors.white, 20 | ), 21 | backgroundColor: Theme.of(context).primaryColor, 22 | ); 23 | } 24 | 25 | return CircleAvatar( 26 | backgroundImage: NetworkImage(currentUser!.photoURL!), 27 | backgroundColor: Theme.of(context).primaryColor, 28 | ); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /macos/.gitignore: -------------------------------------------------------------------------------- 1 | # Flutter-related 2 | **/Flutter/ephemeral/ 3 | **/Pods/ 4 | 5 | # Xcode-related 6 | **/xcuserdata/ 7 | -------------------------------------------------------------------------------- /macos/Flutter/Flutter-Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" 2 | #include "ephemeral/Flutter-Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /macos/Flutter/Flutter-Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" 2 | #include "ephemeral/Flutter-Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /macos/Flutter/GeneratedPluginRegistrant.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Generated file. Do not edit. 3 | // 4 | 5 | import FlutterMacOS 6 | import Foundation 7 | 8 | import cloud_firestore 9 | import device_info_plus_macos 10 | import firebase_auth 11 | import firebase_core 12 | import flutter_local_notifications 13 | import package_info 14 | import path_provider_macos 15 | import shared_preferences_macos 16 | import sign_in_with_apple 17 | import url_launcher_macos 18 | 19 | func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { 20 | FLTFirebaseFirestorePlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseFirestorePlugin")) 21 | DeviceInfoPlusMacosPlugin.register(with: registry.registrar(forPlugin: "DeviceInfoPlusMacosPlugin")) 22 | FLTFirebaseAuthPlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseAuthPlugin")) 23 | FLTFirebaseCorePlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseCorePlugin")) 24 | FlutterLocalNotificationsPlugin.register(with: registry.registrar(forPlugin: "FlutterLocalNotificationsPlugin")) 25 | FLTPackageInfoPlugin.register(with: registry.registrar(forPlugin: "FLTPackageInfoPlugin")) 26 | PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) 27 | SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) 28 | SignInWithApplePlugin.register(with: registry.registrar(forPlugin: "SignInWithApplePlugin")) 29 | UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin")) 30 | } 31 | -------------------------------------------------------------------------------- /macos/Podfile: -------------------------------------------------------------------------------- 1 | platform :osx, '10.11' 2 | 3 | # CocoaPods analytics sends network stats synchronously affecting flutter build latency. 4 | ENV['COCOAPODS_DISABLE_STATS'] = 'true' 5 | 6 | project 'Runner', { 7 | 'Debug' => :debug, 8 | 'Profile' => :release, 9 | 'Release' => :release, 10 | } 11 | 12 | def flutter_root 13 | generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'ephemeral', 'Flutter-Generated.xcconfig'), __FILE__) 14 | unless File.exist?(generated_xcode_build_settings_path) 15 | raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure \"flutter pub get\" is executed first" 16 | end 17 | 18 | File.foreach(generated_xcode_build_settings_path) do |line| 19 | matches = line.match(/FLUTTER_ROOT\=(.*)/) 20 | return matches[1].strip if matches 21 | end 22 | raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Flutter-Generated.xcconfig, then run \"flutter pub get\"" 23 | end 24 | 25 | require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) 26 | 27 | flutter_macos_podfile_setup 28 | 29 | target 'Runner' do 30 | use_frameworks! 31 | use_modular_headers! 32 | 33 | flutter_install_all_macos_pods File.dirname(File.realpath(__FILE__)) 34 | end 35 | 36 | post_install do |installer| 37 | installer.pods_project.targets.each do |target| 38 | flutter_additional_macos_build_settings(target) 39 | end 40 | end 41 | -------------------------------------------------------------------------------- /macos/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 37 | 38 | 39 | 40 | 41 | 42 | 52 | 54 | 60 | 61 | 62 | 63 | 64 | 65 | 71 | 73 | 79 | 80 | 81 | 82 | 84 | 85 | 88 | 89 | 90 | -------------------------------------------------------------------------------- /macos/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /macos/Runner/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import Cocoa 2 | import FlutterMacOS 3 | 4 | @NSApplicationMain 5 | class AppDelegate: FlutterAppDelegate { 6 | override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { 7 | return true 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "16x16", 5 | "idiom" : "mac", 6 | "filename" : "app_icon_16.png", 7 | "scale" : "1x" 8 | }, 9 | { 10 | "size" : "16x16", 11 | "idiom" : "mac", 12 | "filename" : "app_icon_32.png", 13 | "scale" : "2x" 14 | }, 15 | { 16 | "size" : "32x32", 17 | "idiom" : "mac", 18 | "filename" : "app_icon_32.png", 19 | "scale" : "1x" 20 | }, 21 | { 22 | "size" : "32x32", 23 | "idiom" : "mac", 24 | "filename" : "app_icon_64.png", 25 | "scale" : "2x" 26 | }, 27 | { 28 | "size" : "128x128", 29 | "idiom" : "mac", 30 | "filename" : "app_icon_128.png", 31 | "scale" : "1x" 32 | }, 33 | { 34 | "size" : "128x128", 35 | "idiom" : "mac", 36 | "filename" : "app_icon_256.png", 37 | "scale" : "2x" 38 | }, 39 | { 40 | "size" : "256x256", 41 | "idiom" : "mac", 42 | "filename" : "app_icon_256.png", 43 | "scale" : "1x" 44 | }, 45 | { 46 | "size" : "256x256", 47 | "idiom" : "mac", 48 | "filename" : "app_icon_512.png", 49 | "scale" : "2x" 50 | }, 51 | { 52 | "size" : "512x512", 53 | "idiom" : "mac", 54 | "filename" : "app_icon_512.png", 55 | "scale" : "1x" 56 | }, 57 | { 58 | "size" : "512x512", 59 | "idiom" : "mac", 60 | "filename" : "app_icon_1024.png", 61 | "scale" : "2x" 62 | } 63 | ], 64 | "info" : { 65 | "version" : 1, 66 | "author" : "xcode" 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GroovinChip/call_manager/abf78a0ee52784b7dc352cf0cbbb9428fbe6d2fb/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GroovinChip/call_manager/abf78a0ee52784b7dc352cf0cbbb9428fbe6d2fb/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GroovinChip/call_manager/abf78a0ee52784b7dc352cf0cbbb9428fbe6d2fb/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GroovinChip/call_manager/abf78a0ee52784b7dc352cf0cbbb9428fbe6d2fb/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GroovinChip/call_manager/abf78a0ee52784b7dc352cf0cbbb9428fbe6d2fb/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GroovinChip/call_manager/abf78a0ee52784b7dc352cf0cbbb9428fbe6d2fb/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GroovinChip/call_manager/abf78a0ee52784b7dc352cf0cbbb9428fbe6d2fb/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png -------------------------------------------------------------------------------- /macos/Runner/Configs/AppInfo.xcconfig: -------------------------------------------------------------------------------- 1 | // Application-level settings for the Runner target. 2 | // 3 | // This may be replaced with something auto-generated from metadata (e.g., pubspec.yaml) in the 4 | // future. If not, the values below would default to using the project name when this becomes a 5 | // 'flutter create' template. 6 | 7 | // The application's name. By default this is also the title of the Flutter window. 8 | PRODUCT_NAME = call_manager 9 | 10 | // The application's bundle identifier 11 | PRODUCT_BUNDLE_IDENTIFIER = com.groovinchip.flutter.callManager 12 | 13 | // The copyright displayed in application information 14 | PRODUCT_COPYRIGHT = Copyright © 2021 com.groovinchip.flutter. All rights reserved. 15 | -------------------------------------------------------------------------------- /macos/Runner/Configs/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include "../../Flutter/Flutter-Debug.xcconfig" 2 | #include "Warnings.xcconfig" 3 | -------------------------------------------------------------------------------- /macos/Runner/Configs/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include "../../Flutter/Flutter-Release.xcconfig" 2 | #include "Warnings.xcconfig" 3 | -------------------------------------------------------------------------------- /macos/Runner/Configs/Warnings.xcconfig: -------------------------------------------------------------------------------- 1 | WARNING_CFLAGS = -Wall -Wconditional-uninitialized -Wnullable-to-nonnull-conversion -Wmissing-method-return-type -Woverlength-strings 2 | GCC_WARN_UNDECLARED_SELECTOR = YES 3 | CLANG_UNDEFINED_BEHAVIOR_SANITIZER_NULLABILITY = YES 4 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE 5 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES 6 | CLANG_WARN_PRAGMA_PACK = YES 7 | CLANG_WARN_STRICT_PROTOTYPES = YES 8 | CLANG_WARN_COMMA = YES 9 | GCC_WARN_STRICT_SELECTOR_MATCH = YES 10 | CLANG_WARN_OBJC_REPEATED_USE_OF_WEAK = YES 11 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES 12 | GCC_WARN_SHADOW = YES 13 | CLANG_WARN_UNREACHABLE_CODE = YES 14 | -------------------------------------------------------------------------------- /macos/Runner/DebugProfile.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.app-sandbox 6 | 7 | com.apple.security.cs.allow-jit 8 | 9 | com.apple.security.network.server 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /macos/Runner/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIconFile 10 | 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | $(FLUTTER_BUILD_NAME) 21 | CFBundleVersion 22 | $(FLUTTER_BUILD_NUMBER) 23 | LSMinimumSystemVersion 24 | $(MACOSX_DEPLOYMENT_TARGET) 25 | NSHumanReadableCopyright 26 | $(PRODUCT_COPYRIGHT) 27 | NSMainNibFile 28 | MainMenu 29 | NSPrincipalClass 30 | NSApplication 31 | 32 | 33 | -------------------------------------------------------------------------------- /macos/Runner/MainFlutterWindow.swift: -------------------------------------------------------------------------------- 1 | import Cocoa 2 | import FlutterMacOS 3 | 4 | class MainFlutterWindow: NSWindow { 5 | override func awakeFromNib() { 6 | let flutterViewController = FlutterViewController.init() 7 | let windowFrame = self.frame 8 | self.contentViewController = flutterViewController 9 | self.setFrame(windowFrame, display: true) 10 | 11 | RegisterGeneratedPlugins(registry: flutterViewController) 12 | 13 | super.awakeFromNib() 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /macos/Runner/Release.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.app-sandbox 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /privacy policy.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GroovinChip/call_manager/abf78a0ee52784b7dc352cf0cbbb9428fbe6d2fb/privacy policy.txt -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: call_manager 2 | description: Call Manager in Flutter 3 | version: 2.8.5+54 4 | publish_to: none 5 | 6 | environment: 7 | sdk: ">=2.13.0 <3.0.0" 8 | 9 | dependencies: 10 | flutter: 11 | sdk: flutter 12 | 13 | bluejay: ^1.4.0 14 | cloud_firestore: ^2.5.3 15 | contacts_service: ^0.6.3 16 | crypto: ^3.0.1 17 | cupertino_icons: ^1.0.3 18 | datetime_picker_formfield: ^2.0.0 19 | direct_dialer: ^1.0.1+1 20 | firebase_auth: ^3.1.3 21 | firebase_core: ^1.7.0 22 | flutter_local_notifications: ^9.0.0 23 | flutter_typeahead: ^3.2.1 24 | google_fonts: ^2.1.0 25 | google_sign_in: ^5.1.1 26 | groovin_widgets: ^3.0.2 27 | material_design_icons_flutter: ^5.0.5955-rc.1 28 | package_info: ^2.0.2 29 | permission_handler: ^8.2.2 30 | provider: ^6.0.1 31 | rxdart: ^0.27.2 32 | shared_preferences: ^2.0.8 33 | sign_in_with_apple: ^3.2.0 34 | timezone: any 35 | url_launcher: ^6.0.12 36 | wiredash: ^0.7.0+1 37 | 38 | dev_dependencies: 39 | flutter_test: 40 | sdk: flutter 41 | #flutter_launcher_icons: "^0.7.2" 42 | dart_code_metrics: ^4.4.0 43 | flutter_lints: ^1.0.4 44 | 45 | flutter_icons: 46 | android: true 47 | #image_path: "assets/icon/call_manager_app_icon.png" 48 | #adaptive_icon_background: "#1976D2" 49 | 50 | flutter: 51 | uses-material-design: true 52 | 53 | assets: 54 | - android/app/src/main/res/drawable-hdpi/ic_notification.png 55 | - assets/icon/call_manager_app_icon.png 56 | - assets/glogo.png 57 | 58 | # An image asset can refer to one or more resolution-specific "variants", see 59 | # https://flutter.io/assets-and-images/#resolution-aware. 60 | # For details regarding adding assets from package dependencies, see 61 | # https://flutter.io/assets-and-images/#from-packages 62 | fonts: 63 | - family: SourceSansPro 64 | fonts: 65 | - asset: fonts/SourceSansPro-Regular.ttf 66 | - asset: fonts/SourceSansPro-Bold.ttf 67 | weight: 500 68 | -------------------------------------------------------------------------------- /test/widget_test.dart: -------------------------------------------------------------------------------- 1 | // ignore: no-empty-block 2 | void main() {} 3 | -------------------------------------------------------------------------------- /web/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GroovinChip/call_manager/abf78a0ee52784b7dc352cf0cbbb9428fbe6d2fb/web/favicon.png -------------------------------------------------------------------------------- /web/icons/Icon-192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GroovinChip/call_manager/abf78a0ee52784b7dc352cf0cbbb9428fbe6d2fb/web/icons/Icon-192.png -------------------------------------------------------------------------------- /web/icons/Icon-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GroovinChip/call_manager/abf78a0ee52784b7dc352cf0cbbb9428fbe6d2fb/web/icons/Icon-512.png -------------------------------------------------------------------------------- /web/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | call_manager 30 | 31 | 32 | 33 | 36 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /web/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "call_manager", 3 | "short_name": "call_manager", 4 | "start_url": ".", 5 | "display": "standalone", 6 | "background_color": "#0175C2", 7 | "theme_color": "#0175C2", 8 | "description": "A new Flutter project.", 9 | "orientation": "portrait-primary", 10 | "prefer_related_applications": false, 11 | "icons": [ 12 | { 13 | "src": "icons/Icon-192.png", 14 | "sizes": "192x192", 15 | "type": "image/png" 16 | }, 17 | { 18 | "src": "icons/Icon-512.png", 19 | "sizes": "512x512", 20 | "type": "image/png" 21 | } 22 | ] 23 | } 24 | --------------------------------------------------------------------------------