├── .github └── workflows │ ├── build.yml │ ├── manual.yml │ ├── publish.yml │ └── remove-old-artifacts.yml ├── .gitignore ├── .metadata ├── LICENSE ├── README.md ├── analysis_options.yaml ├── android ├── .gitignore ├── app │ ├── build.gradle.kts │ ├── key │ │ └── call.keystore │ └── src │ │ ├── debug │ │ └── AndroidManifest.xml │ │ ├── main │ │ ├── AndroidManifest.xml │ │ ├── kotlin │ │ │ └── com │ │ │ │ └── dc16 │ │ │ │ └── call │ │ │ │ └── MainActivity.kt │ │ └── res │ │ │ ├── drawable-hdpi │ │ │ └── ic_launcher_foreground.png │ │ │ ├── drawable-mdpi │ │ │ └── ic_launcher_foreground.png │ │ │ ├── drawable-v21 │ │ │ ├── background.png │ │ │ └── launch_background.xml │ │ │ ├── drawable-xhdpi │ │ │ └── ic_launcher_foreground.png │ │ │ ├── drawable-xxhdpi │ │ │ └── ic_launcher_foreground.png │ │ │ ├── drawable-xxxhdpi │ │ │ └── ic_launcher_foreground.png │ │ │ ├── drawable │ │ │ ├── background.png │ │ │ └── launch_background.xml │ │ │ ├── mipmap-anydpi-v26 │ │ │ └── ic_launcher.xml │ │ │ ├── mipmap-hdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-mdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xhdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xxhdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xxxhdpi │ │ │ └── ic_launcher.png │ │ │ ├── values-night-v31 │ │ │ └── styles.xml │ │ │ ├── values-night │ │ │ └── styles.xml │ │ │ ├── values-v31 │ │ │ └── styles.xml │ │ │ └── values │ │ │ ├── colors.xml │ │ │ └── styles.xml │ │ └── profile │ │ └── AndroidManifest.xml ├── build.gradle.kts ├── gradle.properties ├── gradle │ └── wrapper │ │ └── gradle-wrapper.properties ├── key.properties └── settings.gradle.kts ├── assets ├── icon │ ├── foreground.png │ └── icon.png └── screenshot │ └── ui.jpg ├── distribute_options.yaml ├── flutter_native_splash.yaml ├── ios ├── .gitignore ├── Flutter │ ├── AppFrameworkInfo.plist │ ├── Debug.xcconfig │ └── Release.xcconfig ├── Podfile ├── Runner.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ ├── IDEWorkspaceChecks.plist │ │ │ └── WorkspaceSettings.xcsettings │ └── xcshareddata │ │ └── xcschemes │ │ └── Runner.xcscheme ├── Runner.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ ├── IDEWorkspaceChecks.plist │ │ └── WorkspaceSettings.xcsettings ├── Runner │ ├── AppDelegate.swift │ ├── Assets.xcassets │ │ ├── AppIcon.appiconset │ │ │ ├── Contents.json │ │ │ ├── Icon-App-1024x1024@1x.png │ │ │ ├── Icon-App-20x20@1x.png │ │ │ ├── Icon-App-20x20@2x.png │ │ │ ├── Icon-App-20x20@3x.png │ │ │ ├── Icon-App-29x29@1x.png │ │ │ ├── Icon-App-29x29@2x.png │ │ │ ├── Icon-App-29x29@3x.png │ │ │ ├── Icon-App-40x40@1x.png │ │ │ ├── Icon-App-40x40@2x.png │ │ │ ├── Icon-App-40x40@3x.png │ │ │ ├── Icon-App-50x50@1x.png │ │ │ ├── Icon-App-50x50@2x.png │ │ │ ├── Icon-App-57x57@1x.png │ │ │ ├── Icon-App-57x57@2x.png │ │ │ ├── Icon-App-60x60@2x.png │ │ │ ├── Icon-App-60x60@3x.png │ │ │ ├── Icon-App-72x72@1x.png │ │ │ ├── Icon-App-72x72@2x.png │ │ │ ├── Icon-App-76x76@1x.png │ │ │ ├── Icon-App-76x76@2x.png │ │ │ └── Icon-App-83.5x83.5@2x.png │ │ ├── LaunchBackground.imageset │ │ │ ├── Contents.json │ │ │ └── background.png │ │ └── LaunchImage.imageset │ │ │ ├── Contents.json │ │ │ ├── LaunchImage.png │ │ │ ├── LaunchImage@2x.png │ │ │ ├── LaunchImage@3x.png │ │ │ └── README.md │ ├── Base.lproj │ │ ├── LaunchScreen.storyboard │ │ └── Main.storyboard │ ├── Info.plist │ └── Runner-Bridging-Header.h └── RunnerTests │ └── RunnerTests.swift ├── lib ├── data │ ├── call_record.dart │ ├── phone_info.dart │ └── voice_vibration_set.dart ├── main.dart ├── page │ ├── about_page.dart │ ├── add_page.dart │ ├── data_page.dart │ ├── delete_page.dart │ ├── edit_page.dart │ ├── export_page.dart │ ├── home_page.dart │ ├── image_edit_page.dart │ ├── menu_page.dart │ ├── permissions_page.dart │ ├── record_page.dart │ ├── reorder_page.dart │ └── voice_vibration_page.dart └── utls │ ├── crop_editor_helper.dart │ ├── db_util.dart │ ├── flavor_util.dart │ ├── style_util.dart │ ├── voice_vibration_util.dart │ ├── wechat_util.dart │ └── widget_util.dart ├── pubspec.lock ├── pubspec.yaml └── test └── widget_test.dart /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build and archive 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | tags-ignore: 8 | - '[0-9]+.[0-9]+.[0-9]+*' 9 | pull_request: 10 | types: 11 | - opened 12 | 13 | jobs: 14 | build: 15 | runs-on: ubuntu-latest 16 | steps: 17 | - name: Checkout 18 | uses: actions/checkout@main 19 | 20 | - name: Set Up Java 21 | uses: actions/setup-java@v4 22 | with: 23 | distribution: 'oracle' 24 | cache: 'gradle' 25 | java-version: '17' 26 | 27 | - name: Set Up Flutter 28 | uses: subosito/flutter-action@v2 29 | with: 30 | channel: stable 31 | cache: true 32 | 33 | - name: Install Dependencies 34 | run: flutter pub get 35 | shell: bash 36 | 37 | - name: Install flutter_distributor 38 | run: dart pub global activate flutter_distributor 39 | shell: bash 40 | 41 | - name: Checking flutter 42 | run: flutter doctor --verbose 43 | shell: bash 44 | 45 | - name: Analyze project source 46 | run: dart analyze 47 | shell: bash 48 | 49 | - name: Build APK 50 | run: flutter_distributor release --name all 51 | shell: bash 52 | 53 | - name: Archive APK 54 | uses: actions/upload-artifact@v4 55 | with: 56 | name: call 57 | path: | 58 | dist/** 59 | overwrite: true 60 | compression-level: 9 61 | retention-days: 3 62 | -------------------------------------------------------------------------------- /.github/workflows/manual.yml: -------------------------------------------------------------------------------- 1 | name: Manual operation 2 | 3 | on: 4 | workflow_dispatch: 5 | inputs: 6 | flutter_channel: 7 | default: 'beta' 8 | required: true 9 | type: choice 10 | options: 11 | - beta 12 | - master 13 | - stable 14 | description: 'flutter channel' 15 | 16 | jobs: 17 | build: 18 | runs-on: ubuntu-latest 19 | steps: 20 | - name: Checkout 21 | uses: actions/checkout@main 22 | 23 | - name: Set Up Java 24 | uses: actions/setup-java@v4 25 | with: 26 | distribution: 'oracle' 27 | cache: 'gradle' 28 | java-version: '17' 29 | 30 | - name: Set Up Flutter 31 | uses: subosito/flutter-action@v2 32 | with: 33 | channel: ${{ inputs.flutter_channel }} 34 | cache: true 35 | 36 | - name: Install Dependencies 37 | run: flutter pub get 38 | shell: bash 39 | 40 | - name: Install flutter_distributor 41 | run: dart pub global activate flutter_distributor 42 | shell: bash 43 | 44 | - name: Checking flutter 45 | run: flutter doctor --verbose 46 | shell: bash 47 | 48 | - name: Analyze project source 49 | run: dart analyze 50 | shell: bash 51 | 52 | - name: Build APK 53 | run: flutter_distributor release --name all 54 | shell: bash 55 | 56 | - name: Archive APK 57 | uses: actions/upload-artifact@v4 58 | with: 59 | name: call 60 | path: | 61 | dist/** 62 | overwrite: true 63 | compression-level: 9 64 | retention-days: 3 65 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: Build and publish 2 | 3 | on: 4 | push: 5 | tags: 6 | - '[0-9]+.[0-9]+.[0-9]+*' 7 | 8 | jobs: 9 | build: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: Checkout 13 | uses: actions/checkout@main 14 | 15 | - name: Set Up Java 16 | uses: actions/setup-java@v4 17 | with: 18 | distribution: 'oracle' 19 | cache: 'gradle' 20 | java-version: '17' 21 | 22 | - name: Set Up Flutter 23 | uses: subosito/flutter-action@v2 24 | with: 25 | channel: stable 26 | cache: true 27 | 28 | - name: Install Dependencies 29 | run: flutter pub get 30 | shell: bash 31 | 32 | - name: Install flutter_distributor 33 | run: dart pub global activate flutter_distributor 34 | shell: bash 35 | 36 | - name: Build APK 37 | run: flutter_distributor release --name all 38 | shell: bash 39 | 40 | - name: Release APK 41 | uses: softprops/action-gh-release@v2 42 | if: startsWith(github.ref, 'refs/tags/') 43 | with: 44 | files: | 45 | dist/** 46 | draft: true 47 | token: ${{ secrets.CALL_RELEASE }} 48 | 49 | - name: Archive APK 50 | uses: actions/upload-artifact@v4 51 | with: 52 | name: call 53 | path: | 54 | dist/** 55 | overwrite: true 56 | compression-level: 9 57 | retention-days: 3 58 | -------------------------------------------------------------------------------- /.github/workflows/remove-old-artifacts.yml: -------------------------------------------------------------------------------- 1 | name: Remove old artifacts 2 | 3 | on: 4 | schedule: 5 | # Every day at 1am 6 | - cron: '0 1 * * *' 7 | workflow_dispatch: 8 | 9 | jobs: 10 | remove-old-artifacts: 11 | runs-on: ubuntu-latest 12 | timeout-minutes: 10 13 | 14 | steps: 15 | - name: Remove old artifacts 16 | uses: c-hive/gha-remove-artifacts@v1 17 | with: 18 | age: '10 days' # ' ', e.g. 1 month, 5 days, 2 years, 90 seconds, parsed by Moment.js 19 | GITHUB_TOKEN: ${{ secrets.CALL_RELEASE }} 20 | # Optional inputs 21 | # skip-tags: true 22 | # skip-recent: 5 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .build/ 9 | .buildlog/ 10 | .history 11 | .svn/ 12 | .swiftpm/ 13 | migrate_working_dir/ 14 | 15 | # IntelliJ related 16 | *.iml 17 | *.ipr 18 | *.iws 19 | .idea/ 20 | 21 | # The .vscode folder contains launch configuration and tasks you configure in 22 | # VS Code which you may wish to be included in version control, so this line 23 | # is commented out by default. 24 | #.vscode/ 25 | 26 | # Flutter/Dart/Pub related 27 | **/doc/api/ 28 | **/ios/Flutter/.last_build_id 29 | .dart_tool/ 30 | .flutter-plugins 31 | .flutter-plugins-dependencies 32 | .pub-cache/ 33 | .pub/ 34 | /build/ 35 | 36 | # Symbolication related 37 | app.*.symbols 38 | 39 | # Obfuscation related 40 | app.*.map.json 41 | 42 | # Android Studio will place build artifacts here 43 | /android/app/debug 44 | /android/app/profile 45 | /android/app/release 46 | 47 | dist/ 48 | -------------------------------------------------------------------------------- /.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: "35c388afb57ef061d06a39b537336c87e0e3d1b1" 8 | channel: "stable" 9 | 10 | project_type: app 11 | 12 | # Tracks metadata for the flutter migrate command 13 | migration: 14 | platforms: 15 | - platform: root 16 | create_revision: 35c388afb57ef061d06a39b537336c87e0e3d1b1 17 | base_revision: 35c388afb57ef061d06a39b537336c87e0e3d1b1 18 | - platform: android 19 | create_revision: 35c388afb57ef061d06a39b537336c87e0e3d1b1 20 | base_revision: 35c388afb57ef061d06a39b537336c87e0e3d1b1 21 | - platform: ios 22 | create_revision: 35c388afb57ef061d06a39b537336c87e0e3d1b1 23 | base_revision: 35c388afb57ef061d06a39b537336c87e0e3d1b1 24 | 25 | # User provided section 26 | 27 | # List of Local paths (relative to this file) that should be 28 | # ignored by the migrate tool. 29 | # 30 | # Files that are not part of the templates will be ignored by default. 31 | unmanaged_files: 32 | - 'lib/main.dart' 33 | - 'ios/Runner.xcodeproj/project.pbxproj' 34 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 davidche 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # Call打电话 4 | 5 | 一个工具类APP,用于老人快捷方便拨打家人电话、微信视频通话。使用Flutter编写 6 | 7 | ## 开发环境 8 | 9 | - flutter 3.29.0 10 | - dart 3.7.0 11 | - gradle 8.10.2 12 | - gradle-plugin 8.7.0 13 | - kotlin 1.8.22 14 | 15 | ## 功能特点 16 | 17 | - 点击头像拨打普通电话、微信视频通话 18 | - 添加、编辑、删除联系人姓名、图片、微信昵称/备注、语音播报内容 19 | - 查询通话记录(电话、微信) 20 | - 设置语音播报TTS音量、语速,点击联系人震动时长、强度 21 | - 联系人拖动排序 22 | - 权限管理(无障碍、打电话、相册、设置) 23 | - 导出联系人照片 24 | - 联系人数据库管理 25 | - 手机系统信息查看 26 | 27 | ## 界面截图 28 | ![UI](assets/screenshot/ui.jpg) 29 | 30 | ## 源码说明 31 | 32 | > ``` 33 | > Call 34 | > ├─android # Android工程配置 35 | > ├─assets # 资源文件目录 36 | > ├─ios # iOS工程配置 37 | > ├─lib # flutter源代码目录 38 | > │ ├─data # 数据类 39 | > │ ├─page # 各个页面 40 | > │ ├─utls # 工具类 41 | > │ └─main.dart # APP入口 42 | > └─test # 测试目录 43 | > ``` 44 | -------------------------------------------------------------------------------- /analysis_options.yaml: -------------------------------------------------------------------------------- 1 | # This file configures the analyzer, which statically analyzes Dart code to 2 | # check for errors, warnings, and lints. 3 | # 4 | # The issues identified by the analyzer are surfaced in the UI of Dart-enabled 5 | # IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be 6 | # invoked from the command line by running `flutter analyze`. 7 | 8 | # The following line activates a set of recommended lints for Flutter apps, 9 | # packages, and plugins designed to encourage good coding practices. 10 | include: package:flutter_lints/flutter.yaml 11 | 12 | linter: 13 | # The lint rules applied to this project can be customized in the 14 | # section below to disable rules from the `package:flutter_lints/flutter.yaml` 15 | # included above or to enable additional rules. A list of all available lints 16 | # and their documentation is published at https://dart.dev/lints. 17 | # 18 | # Instead of disabling a lint rule for the entire project in the 19 | # section below, it can also be suppressed for a single line of code 20 | # or a specific dart file by using the `// ignore: name_of_lint` and 21 | # `// ignore_for_file: name_of_lint` syntax on the line or in the file 22 | # producing the lint. 23 | rules: 24 | # avoid_print: false # Uncomment to disable the `avoid_print` rule 25 | # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule 26 | 27 | # Additional information about this file can be found at 28 | # https://dart.dev/guides/language/analysis-options 29 | -------------------------------------------------------------------------------- /android/.gitignore: -------------------------------------------------------------------------------- 1 | gradle-wrapper.jar 2 | /.gradle 3 | /captures/ 4 | /gradlew 5 | /gradlew.bat 6 | /local.properties 7 | GeneratedPluginRegistrant.java 8 | .cxx/ 9 | 10 | # Remember to never publicly share your keystore. 11 | # See https://flutter.dev/to/reference-keystore 12 | key.properties 13 | **/*.keystore 14 | **/*.jks 15 | -------------------------------------------------------------------------------- /android/app/build.gradle.kts: -------------------------------------------------------------------------------- 1 | import java.util.Properties 2 | import java.io.FileInputStream 3 | 4 | plugins { 5 | id("com.android.application") 6 | id("kotlin-android") 7 | // The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins. 8 | id("dev.flutter.flutter-gradle-plugin") 9 | } 10 | 11 | val keystoreProperties = Properties() 12 | val keystorePropertiesFile = rootProject.file("key.properties") 13 | if (keystorePropertiesFile.exists()) { 14 | keystoreProperties.load(FileInputStream(keystorePropertiesFile)) 15 | } 16 | 17 | android { 18 | namespace = "com.dc16.call" 19 | compileSdk = flutter.compileSdkVersion 20 | ndkVersion = flutter.ndkVersion 21 | 22 | compileOptions { 23 | sourceCompatibility = JavaVersion.VERSION_11 24 | targetCompatibility = JavaVersion.VERSION_11 25 | } 26 | 27 | kotlinOptions { 28 | jvmTarget = JavaVersion.VERSION_11.toString() 29 | } 30 | 31 | defaultConfig { 32 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). 33 | applicationId = "com.dc16.call" 34 | // You can update the following values to match your application needs. 35 | // For more information, see: https://flutter.dev/to/review-gradle-config. 36 | minSdk = 26 37 | targetSdk = flutter.targetSdkVersion 38 | versionCode = flutter.versionCode 39 | versionName = flutter.versionName 40 | } 41 | 42 | signingConfigs { 43 | create("release") { 44 | keyAlias = keystoreProperties["keyAlias"] as String 45 | keyPassword = keystoreProperties["keyPassword"] as String 46 | storeFile = keystoreProperties["storeFile"]?.let { file(it) } 47 | storePassword = keystoreProperties["storePassword"] as String 48 | } 49 | } 50 | 51 | buildTypes { 52 | debug { 53 | signingConfig = signingConfigs.getByName("release") 54 | } 55 | release { 56 | signingConfig = signingConfigs.getByName("release") 57 | } 58 | } 59 | 60 | } 61 | 62 | flutter { 63 | source = "../.." 64 | } 65 | -------------------------------------------------------------------------------- /android/app/key/call.keystore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidche1116/CallApp/90c26c41e15e7534a12a7b9047192f5b7ba597b4/android/app/key/call.keystore -------------------------------------------------------------------------------- /android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 15 | 16 | 17 | 18 | 19 | 20 | 24 | 33 | 37 | 41 | 42 | 43 | 44 | 45 | 46 | 48 | 51 | 52 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | -------------------------------------------------------------------------------- /android/app/src/main/kotlin/com/dc16/call/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.dc16.call 2 | 3 | import io.flutter.embedding.android.FlutterActivity 4 | 5 | class MainActivity : FlutterActivity() 6 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-hdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidche1116/CallApp/90c26c41e15e7534a12a7b9047192f5b7ba597b4/android/app/src/main/res/drawable-hdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-mdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidche1116/CallApp/90c26c41e15e7534a12a7b9047192f5b7ba597b4/android/app/src/main/res/drawable-mdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-v21/background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidche1116/CallApp/90c26c41e15e7534a12a7b9047192f5b7ba597b4/android/app/src/main/res/drawable-v21/background.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-v21/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-xhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidche1116/CallApp/90c26c41e15e7534a12a7b9047192f5b7ba597b4/android/app/src/main/res/drawable-xhdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-xxhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidche1116/CallApp/90c26c41e15e7534a12a7b9047192f5b7ba597b4/android/app/src/main/res/drawable-xxhdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-xxxhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidche1116/CallApp/90c26c41e15e7534a12a7b9047192f5b7ba597b4/android/app/src/main/res/drawable-xxxhdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable/background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidche1116/CallApp/90c26c41e15e7534a12a7b9047192f5b7ba597b4/android/app/src/main/res/drawable/background.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidche1116/CallApp/90c26c41e15e7534a12a7b9047192f5b7ba597b4/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidche1116/CallApp/90c26c41e15e7534a12a7b9047192f5b7ba597b4/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidche1116/CallApp/90c26c41e15e7534a12a7b9047192f5b7ba597b4/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidche1116/CallApp/90c26c41e15e7534a12a7b9047192f5b7ba597b4/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidche1116/CallApp/90c26c41e15e7534a12a7b9047192f5b7ba597b4/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/values-night-v31/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 12 | 18 | 21 | 22 | -------------------------------------------------------------------------------- /android/app/src/main/res/values-night/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 13 | 19 | 22 | 23 | -------------------------------------------------------------------------------- /android/app/src/main/res/values-v31/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 12 | 18 | 21 | 22 | -------------------------------------------------------------------------------- /android/app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #1677FF 4 | -------------------------------------------------------------------------------- /android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 13 | 19 | 22 | 23 | -------------------------------------------------------------------------------- /android/app/src/profile/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /android/build.gradle.kts: -------------------------------------------------------------------------------- 1 | allprojects { 2 | repositories { 3 | google() 4 | mavenCentral() 5 | } 6 | } 7 | 8 | val newBuildDir: Directory = rootProject.layout.buildDirectory.dir("../../build").get() 9 | rootProject.layout.buildDirectory.value(newBuildDir) 10 | 11 | subprojects { 12 | val newSubprojectBuildDir: Directory = newBuildDir.dir(project.name) 13 | project.layout.buildDirectory.value(newSubprojectBuildDir) 14 | } 15 | subprojects { 16 | project.evaluationDependsOn(":app") 17 | } 18 | 19 | tasks.register("clean") { 20 | delete(rootProject.layout.buildDirectory) 21 | } 22 | -------------------------------------------------------------------------------- /android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx8G -XX:MaxMetaspaceSize=4G -XX:ReservedCodeCacheSize=512m -XX:+HeapDumpOnOutOfMemoryError 2 | android.useAndroidX=true 3 | android.enableJetifier=true 4 | -------------------------------------------------------------------------------- /android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | zipStoreBase=GRADLE_USER_HOME 4 | zipStorePath=wrapper/dists 5 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-all.zip 6 | -------------------------------------------------------------------------------- /android/key.properties: -------------------------------------------------------------------------------- 1 | storePassword=dc16@call 2 | keyPassword=dc16@call 3 | keyAlias=call 4 | storeFile=key\\call.keystore 5 | -------------------------------------------------------------------------------- /android/settings.gradle.kts: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | val flutterSdkPath = run { 3 | val properties = java.util.Properties() 4 | file("local.properties").inputStream().use { properties.load(it) } 5 | val flutterSdkPath = properties.getProperty("flutter.sdk") 6 | require(flutterSdkPath != null) { "flutter.sdk not set in local.properties" } 7 | flutterSdkPath 8 | } 9 | 10 | includeBuild("$flutterSdkPath/packages/flutter_tools/gradle") 11 | 12 | repositories { 13 | google() 14 | mavenCentral() 15 | gradlePluginPortal() 16 | } 17 | } 18 | 19 | plugins { 20 | id("dev.flutter.flutter-plugin-loader") version "1.0.0" 21 | id("com.android.application") version "8.7.0" apply false 22 | id("org.jetbrains.kotlin.android") version "1.8.22" apply false 23 | } 24 | 25 | include(":app") 26 | -------------------------------------------------------------------------------- /assets/icon/foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidche1116/CallApp/90c26c41e15e7534a12a7b9047192f5b7ba597b4/assets/icon/foreground.png -------------------------------------------------------------------------------- /assets/icon/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidche1116/CallApp/90c26c41e15e7534a12a7b9047192f5b7ba597b4/assets/icon/icon.png -------------------------------------------------------------------------------- /assets/screenshot/ui.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidche1116/CallApp/90c26c41e15e7534a12a7b9047192f5b7ba597b4/assets/screenshot/ui.jpg -------------------------------------------------------------------------------- /distribute_options.yaml: -------------------------------------------------------------------------------- 1 | output: dist/ 2 | releases: 3 | - name: all 4 | jobs: 5 | - name: github 6 | package: 7 | platform: android 8 | target: apk 9 | build_args: 10 | target-platform: android-arm64 11 | obfuscate: 12 | split-debug-info: build/symbols/apk 13 | -------------------------------------------------------------------------------- /flutter_native_splash.yaml: -------------------------------------------------------------------------------- 1 | flutter_native_splash: 2 | # This package generates native code to customize Flutter's default white native splash screen 3 | # with background color and splash image. 4 | # Customize the parameters below, and run the following command in the terminal: 5 | # dart run flutter_native_splash:create 6 | # To restore Flutter's default white splash screen, run the following command in the terminal: 7 | # dart run flutter_native_splash:remove 8 | 9 | # color or background_image is the only required parameter. Use color to set the background 10 | # of your splash screen to a solid color. Use background_image to set the background of your 11 | # splash screen to a png image. This is useful for gradients. The image will be stretch to the 12 | # size of the app. Only one parameter can be used, color and background_image cannot both be set. 13 | color: "#000000" 14 | #background_image: "assets/background.png" 15 | 16 | # Optional parameters are listed below. To enable a parameter, uncomment the line by removing 17 | # the leading # character. 18 | 19 | # The image parameter allows you to specify an image used in the splash screen. It must be a 20 | # png file and should be sized for 4x pixel density. 21 | #image: assets/splash.png 22 | 23 | # The branding property allows you to specify an image used as branding in the splash screen. 24 | # It must be a png file. It is supported for Android, iOS and the Web. For Android 12, 25 | # see the Android 12 section below. 26 | #branding: assets/dart.png 27 | 28 | # To position the branding image at the bottom of the screen you can use bottom, bottomRight, 29 | # and bottomLeft. The default values is bottom if not specified or specified something else. 30 | #branding_mode: bottom 31 | 32 | # The color_dark, background_image_dark, image_dark, branding_dark are parameters that set the background 33 | # and image when the device is in dark mode. If they are not specified, the app will use the 34 | # parameters from above. If the image_dark parameter is specified, color_dark or 35 | # background_image_dark must be specified. color_dark and background_image_dark cannot both be 36 | # set. 37 | #color_dark: "#042a49" 38 | #background_image_dark: "assets/dark-background.png" 39 | #image_dark: assets/splash-invert.png 40 | #branding_dark: assets/dart_dark.png 41 | 42 | # Android 12 handles the splash screen differently than previous versions. Please visit 43 | # https://developer.android.com/guide/topics/ui/splash-screen 44 | # Following are Android 12 specific parameter. 45 | android_12: 46 | # The image parameter sets the splash screen icon image. If this parameter is not specified, 47 | # the app's launcher icon will be used instead. 48 | # Please note that the splash screen will be clipped to a circle on the center of the screen. 49 | # App icon with an icon background: This should be 960×960 pixels, and fit within a circle 50 | # 640 pixels in diameter. 51 | # App icon without an icon background: This should be 1152×1152 pixels, and fit within a circle 52 | # 768 pixels in diameter. 53 | image: assets/icon/icon.png 54 | 55 | # Splash screen background color. 56 | color: "#000000" 57 | 58 | # App icon background color. 59 | #icon_background_color: "#111111" 60 | 61 | # The branding property allows you to specify an image used as branding in the splash screen. 62 | #branding: assets/dart.png 63 | 64 | # The image_dark, color_dark, icon_background_color_dark, and branding_dark set values that 65 | # apply when the device is in dark mode. If they are not specified, the app will use the 66 | # parameters from above. 67 | #image_dark: assets/android12splash-invert.png 68 | #color_dark: "#042a49" 69 | #icon_background_color_dark: "#eeeeee" 70 | 71 | # The android, ios and web parameters can be used to disable generating a splash screen on a given 72 | # platform. 73 | #android: false 74 | #ios: false 75 | #web: false 76 | 77 | # Platform specific images can be specified with the following parameters, which will override 78 | # the respective parameter. You may specify all, selected, or none of these parameters: 79 | #color_android: "#42a5f5" 80 | #color_dark_android: "#042a49" 81 | #color_ios: "#42a5f5" 82 | #color_dark_ios: "#042a49" 83 | #color_web: "#42a5f5" 84 | #color_dark_web: "#042a49" 85 | #image_android: assets/splash-android.png 86 | #image_dark_android: assets/splash-invert-android.png 87 | #image_ios: assets/splash-ios.png 88 | #image_dark_ios: assets/splash-invert-ios.png 89 | #image_web: assets/splash-web.gif 90 | #image_dark_web: assets/splash-invert-web.gif 91 | #background_image_android: "assets/background-android.png" 92 | #background_image_dark_android: "assets/dark-background-android.png" 93 | #background_image_ios: "assets/background-ios.png" 94 | #background_image_dark_ios: "assets/dark-background-ios.png" 95 | #background_image_web: "assets/background-web.png" 96 | #background_image_dark_web: "assets/dark-background-web.png" 97 | #branding_android: assets/brand-android.png 98 | #branding_dark_android: assets/dart_dark-android.png 99 | #branding_ios: assets/brand-ios.gif 100 | #branding_dark_ios: assets/dart_dark-ios.gif 101 | 102 | # The position of the splash image can be set with android_gravity, ios_content_mode, and 103 | # web_image_mode parameters. All default to center. 104 | # 105 | # android_gravity can be one of the following Android Gravity (see 106 | # https://developer.android.com/reference/android/view/Gravity): bottom, center, 107 | # center_horizontal, center_vertical, clip_horizontal, clip_vertical, end, fill, fill_horizontal, 108 | # fill_vertical, left, right, start, or top. 109 | #android_gravity: center 110 | # 111 | # ios_content_mode can be one of the following iOS UIView.ContentMode (see 112 | # https://developer.apple.com/documentation/uikit/uiview/contentmode): scaleToFill, 113 | # scaleAspectFit, scaleAspectFill, center, top, bottom, left, right, topLeft, topRight, 114 | # bottomLeft, or bottomRight. 115 | #ios_content_mode: center 116 | # 117 | # web_image_mode can be one of the following modes: center, contain, stretch, and cover. 118 | #web_image_mode: center 119 | 120 | # The screen orientation can be set in Android with the android_screen_orientation parameter. 121 | # Valid parameters can be found here: 122 | # https://developer.android.com/guide/topics/manifest/activity-element#screen 123 | #android_screen_orientation: sensorLandscape 124 | 125 | # To hide the notification bar, use the fullscreen parameter. Has no effect in web since web 126 | # has no notification bar. Defaults to false. 127 | # NOTE: Unlike Android, iOS will not automatically show the notification bar when the app loads. 128 | # To show the notification bar, add the following code to your Flutter app: 129 | # WidgetsFlutterBinding.ensureInitialized(); 130 | # SystemChrome.setEnabledSystemUIMode(SystemUiMode.manual, overlays: [SystemUiOverlay.bottom, SystemUiOverlay.top], ); 131 | fullscreen: true 132 | 133 | # If you have changed the name(s) of your info.plist file(s), you can specify the filename(s) 134 | # with the info_plist_files parameter. Remove only the # characters in the three lines below, 135 | # do not remove any spaces: 136 | #info_plist_files: 137 | # - 'ios/Runner/Info-Debug.plist' 138 | # - 'ios/Runner/Info-Release.plist' 139 | -------------------------------------------------------------------------------- /ios/.gitignore: -------------------------------------------------------------------------------- 1 | **/dgph 2 | *.mode1v3 3 | *.mode2v3 4 | *.moved-aside 5 | *.pbxuser 6 | *.perspectivev3 7 | **/*sync/ 8 | .sconsign.dblite 9 | .tags* 10 | **/.vagrant/ 11 | **/DerivedData/ 12 | Icon? 13 | **/Pods/ 14 | **/.symlinks/ 15 | profile 16 | xcuserdata 17 | **/.generated/ 18 | Flutter/App.framework 19 | Flutter/Flutter.framework 20 | Flutter/Flutter.podspec 21 | Flutter/Generated.xcconfig 22 | Flutter/ephemeral/ 23 | Flutter/app.flx 24 | Flutter/app.zip 25 | Flutter/flutter_assets/ 26 | Flutter/flutter_export_environment.sh 27 | ServiceDefinitions.json 28 | Runner/GeneratedPluginRegistrant.* 29 | 30 | # Exceptions to above rules. 31 | !default.mode1v3 32 | !default.mode2v3 33 | !default.pbxuser 34 | !default.perspectivev3 35 | -------------------------------------------------------------------------------- /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 | 12.0 25 | 26 | 27 | -------------------------------------------------------------------------------- /ios/Flutter/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Generated.xcconfig" 2 | -------------------------------------------------------------------------------- /ios/Flutter/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Generated.xcconfig" 2 | -------------------------------------------------------------------------------- /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 | target 'RunnerTests' do 36 | inherit! :search_paths 37 | end 38 | end 39 | 40 | post_install do |installer| 41 | installer.pods_project.targets.each do |target| 42 | flutter_additional_ios_build_settings(target) 43 | 44 | # Start of the permission_handler configuration 45 | target.build_configurations.each do |config| 46 | 47 | # You can enable the permissions needed here. For example to enable camera 48 | # permission, just remove the `#` character in front so it looks like this: 49 | # 50 | # ## dart: PermissionGroup.camera 51 | # 'PERMISSION_CAMERA=1' 52 | # 53 | # Preprocessor definitions can be found at: https://github.com/Baseflow/flutter-permission-handler/blob/master/permission_handler_apple/ios/Classes/PermissionHandlerEnums.h 54 | config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] ||= [ 55 | '$(inherited)', 56 | 57 | ## dart: [PermissionGroup.calendarWriteOnly, PermissionGroup.calendar (iOS 16 and below)] 58 | # 'PERMISSION_EVENTS=1', 59 | 60 | ## dart: [PermissionGroup.calendarFullAccess, PermissionGroup.calendar (iOS 17 and above)] 61 | # 'PERMISSION_EVENTS_FULL_ACCESS=1', 62 | 63 | ## dart: PermissionGroup.reminders 64 | # 'PERMISSION_REMINDERS=1', 65 | 66 | ## dart: PermissionGroup.contacts 67 | # 'PERMISSION_CONTACTS=1', 68 | 69 | ## dart: PermissionGroup.camera 70 | # 'PERMISSION_CAMERA=1', 71 | 72 | ## dart: PermissionGroup.microphone 73 | # 'PERMISSION_MICROPHONE=1', 74 | 75 | ## dart: PermissionGroup.speech 76 | # 'PERMISSION_SPEECH_RECOGNIZER=1', 77 | 78 | ## dart: PermissionGroup.photos 79 | 'PERMISSION_PHOTOS=1', 80 | 81 | ## dart: [PermissionGroup.location, PermissionGroup.locationAlways, PermissionGroup.locationWhenInUse] 82 | # 'PERMISSION_LOCATION=1', 83 | 84 | ## dart: PermissionGroup.notification 85 | # 'PERMISSION_NOTIFICATIONS=1', 86 | 87 | ## dart: PermissionGroup.mediaLibrary 88 | 'PERMISSION_MEDIA_LIBRARY=1', 89 | 90 | ## dart: PermissionGroup.sensors 91 | # 'PERMISSION_SENSORS=1', 92 | 93 | ## dart: PermissionGroup.bluetooth 94 | # 'PERMISSION_BLUETOOTH=1', 95 | 96 | ## dart: PermissionGroup.appTrackingTransparency 97 | # 'PERMISSION_APP_TRACKING_TRANSPARENCY=1', 98 | 99 | ## dart: PermissionGroup.criticalAlerts 100 | # 'PERMISSION_CRITICAL_ALERTS=1' 101 | ] 102 | 103 | end 104 | # End of the permission_handler configuration 105 | end 106 | end -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 54; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; 11 | 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; }; 12 | 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; 13 | 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; 14 | 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 15 | 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; 16 | 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; 17 | /* End PBXBuildFile section */ 18 | 19 | /* Begin PBXContainerItemProxy section */ 20 | 331C8085294A63A400263BE5 /* PBXContainerItemProxy */ = { 21 | isa = PBXContainerItemProxy; 22 | containerPortal = 97C146E61CF9000F007C117D /* Project object */; 23 | proxyType = 1; 24 | remoteGlobalIDString = 97C146ED1CF9000F007C117D; 25 | remoteInfo = Runner; 26 | }; 27 | /* End PBXContainerItemProxy section */ 28 | 29 | /* Begin PBXCopyFilesBuildPhase section */ 30 | 9705A1C41CF9048500538489 /* Embed Frameworks */ = { 31 | isa = PBXCopyFilesBuildPhase; 32 | buildActionMask = 2147483647; 33 | dstPath = ""; 34 | dstSubfolderSpec = 10; 35 | files = ( 36 | ); 37 | name = "Embed Frameworks"; 38 | runOnlyForDeploymentPostprocessing = 0; 39 | }; 40 | /* End PBXCopyFilesBuildPhase section */ 41 | 42 | /* Begin PBXFileReference section */ 43 | 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; 44 | 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; 45 | 331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; 46 | 331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 47 | 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; 48 | 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; 49 | 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 50 | 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; 51 | 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; 52 | 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; 53 | 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; 54 | 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 55 | 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 56 | 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 57 | 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 58 | /* End PBXFileReference section */ 59 | 60 | /* Begin PBXFrameworksBuildPhase section */ 61 | 97C146EB1CF9000F007C117D /* Frameworks */ = { 62 | isa = PBXFrameworksBuildPhase; 63 | buildActionMask = 2147483647; 64 | files = ( 65 | ); 66 | runOnlyForDeploymentPostprocessing = 0; 67 | }; 68 | /* End PBXFrameworksBuildPhase section */ 69 | 70 | /* Begin PBXGroup section */ 71 | 331C8082294A63A400263BE5 /* RunnerTests */ = { 72 | isa = PBXGroup; 73 | children = ( 74 | 331C807B294A618700263BE5 /* RunnerTests.swift */, 75 | ); 76 | path = RunnerTests; 77 | sourceTree = ""; 78 | }; 79 | 9740EEB11CF90186004384FC /* Flutter */ = { 80 | isa = PBXGroup; 81 | children = ( 82 | 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, 83 | 9740EEB21CF90195004384FC /* Debug.xcconfig */, 84 | 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, 85 | 9740EEB31CF90195004384FC /* Generated.xcconfig */, 86 | ); 87 | name = Flutter; 88 | sourceTree = ""; 89 | }; 90 | 97C146E51CF9000F007C117D = { 91 | isa = PBXGroup; 92 | children = ( 93 | 9740EEB11CF90186004384FC /* Flutter */, 94 | 97C146F01CF9000F007C117D /* Runner */, 95 | 97C146EF1CF9000F007C117D /* Products */, 96 | 331C8082294A63A400263BE5 /* RunnerTests */, 97 | ); 98 | sourceTree = ""; 99 | }; 100 | 97C146EF1CF9000F007C117D /* Products */ = { 101 | isa = PBXGroup; 102 | children = ( 103 | 97C146EE1CF9000F007C117D /* Runner.app */, 104 | 331C8081294A63A400263BE5 /* RunnerTests.xctest */, 105 | ); 106 | name = Products; 107 | sourceTree = ""; 108 | }; 109 | 97C146F01CF9000F007C117D /* Runner */ = { 110 | isa = PBXGroup; 111 | children = ( 112 | 97C146FA1CF9000F007C117D /* Main.storyboard */, 113 | 97C146FD1CF9000F007C117D /* Assets.xcassets */, 114 | 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, 115 | 97C147021CF9000F007C117D /* Info.plist */, 116 | 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, 117 | 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, 118 | 74858FAE1ED2DC5600515810 /* AppDelegate.swift */, 119 | 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */, 120 | ); 121 | path = Runner; 122 | sourceTree = ""; 123 | }; 124 | /* End PBXGroup section */ 125 | 126 | /* Begin PBXNativeTarget section */ 127 | 331C8080294A63A400263BE5 /* RunnerTests */ = { 128 | isa = PBXNativeTarget; 129 | buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; 130 | buildPhases = ( 131 | 331C807D294A63A400263BE5 /* Sources */, 132 | 331C807F294A63A400263BE5 /* Resources */, 133 | ); 134 | buildRules = ( 135 | ); 136 | dependencies = ( 137 | 331C8086294A63A400263BE5 /* PBXTargetDependency */, 138 | ); 139 | name = RunnerTests; 140 | productName = RunnerTests; 141 | productReference = 331C8081294A63A400263BE5 /* RunnerTests.xctest */; 142 | productType = "com.apple.product-type.bundle.unit-test"; 143 | }; 144 | 97C146ED1CF9000F007C117D /* Runner */ = { 145 | isa = PBXNativeTarget; 146 | buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; 147 | buildPhases = ( 148 | 9740EEB61CF901F6004384FC /* Run Script */, 149 | 97C146EA1CF9000F007C117D /* Sources */, 150 | 97C146EB1CF9000F007C117D /* Frameworks */, 151 | 97C146EC1CF9000F007C117D /* Resources */, 152 | 9705A1C41CF9048500538489 /* Embed Frameworks */, 153 | 3B06AD1E1E4923F5004D2608 /* Thin Binary */, 154 | ); 155 | buildRules = ( 156 | ); 157 | dependencies = ( 158 | ); 159 | name = Runner; 160 | productName = Runner; 161 | productReference = 97C146EE1CF9000F007C117D /* Runner.app */; 162 | productType = "com.apple.product-type.application"; 163 | }; 164 | /* End PBXNativeTarget section */ 165 | 166 | /* Begin PBXProject section */ 167 | 97C146E61CF9000F007C117D /* Project object */ = { 168 | isa = PBXProject; 169 | attributes = { 170 | BuildIndependentTargetsInParallel = YES; 171 | LastUpgradeCheck = 1510; 172 | ORGANIZATIONNAME = ""; 173 | TargetAttributes = { 174 | 331C8080294A63A400263BE5 = { 175 | CreatedOnToolsVersion = 14.0; 176 | TestTargetID = 97C146ED1CF9000F007C117D; 177 | }; 178 | 97C146ED1CF9000F007C117D = { 179 | CreatedOnToolsVersion = 7.3.1; 180 | LastSwiftMigration = 1100; 181 | }; 182 | }; 183 | }; 184 | buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; 185 | compatibilityVersion = "Xcode 9.3"; 186 | developmentRegion = en; 187 | hasScannedForEncodings = 0; 188 | knownRegions = ( 189 | en, 190 | Base, 191 | ); 192 | mainGroup = 97C146E51CF9000F007C117D; 193 | productRefGroup = 97C146EF1CF9000F007C117D /* Products */; 194 | projectDirPath = ""; 195 | projectRoot = ""; 196 | targets = ( 197 | 97C146ED1CF9000F007C117D /* Runner */, 198 | 331C8080294A63A400263BE5 /* RunnerTests */, 199 | ); 200 | }; 201 | /* End PBXProject section */ 202 | 203 | /* Begin PBXResourcesBuildPhase section */ 204 | 331C807F294A63A400263BE5 /* Resources */ = { 205 | isa = PBXResourcesBuildPhase; 206 | buildActionMask = 2147483647; 207 | files = ( 208 | ); 209 | runOnlyForDeploymentPostprocessing = 0; 210 | }; 211 | 97C146EC1CF9000F007C117D /* Resources */ = { 212 | isa = PBXResourcesBuildPhase; 213 | buildActionMask = 2147483647; 214 | files = ( 215 | 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, 216 | 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, 217 | 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, 218 | 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, 219 | ); 220 | runOnlyForDeploymentPostprocessing = 0; 221 | }; 222 | /* End PBXResourcesBuildPhase section */ 223 | 224 | /* Begin PBXShellScriptBuildPhase section */ 225 | 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { 226 | isa = PBXShellScriptBuildPhase; 227 | alwaysOutOfDate = 1; 228 | buildActionMask = 2147483647; 229 | files = ( 230 | ); 231 | inputPaths = ( 232 | "${TARGET_BUILD_DIR}/${INFOPLIST_PATH}", 233 | ); 234 | name = "Thin Binary"; 235 | outputPaths = ( 236 | ); 237 | runOnlyForDeploymentPostprocessing = 0; 238 | shellPath = /bin/sh; 239 | shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; 240 | }; 241 | 9740EEB61CF901F6004384FC /* Run Script */ = { 242 | isa = PBXShellScriptBuildPhase; 243 | alwaysOutOfDate = 1; 244 | buildActionMask = 2147483647; 245 | files = ( 246 | ); 247 | inputPaths = ( 248 | ); 249 | name = "Run Script"; 250 | outputPaths = ( 251 | ); 252 | runOnlyForDeploymentPostprocessing = 0; 253 | shellPath = /bin/sh; 254 | shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; 255 | }; 256 | /* End PBXShellScriptBuildPhase section */ 257 | 258 | /* Begin PBXSourcesBuildPhase section */ 259 | 331C807D294A63A400263BE5 /* Sources */ = { 260 | isa = PBXSourcesBuildPhase; 261 | buildActionMask = 2147483647; 262 | files = ( 263 | 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */, 264 | ); 265 | runOnlyForDeploymentPostprocessing = 0; 266 | }; 267 | 97C146EA1CF9000F007C117D /* Sources */ = { 268 | isa = PBXSourcesBuildPhase; 269 | buildActionMask = 2147483647; 270 | files = ( 271 | 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, 272 | 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, 273 | ); 274 | runOnlyForDeploymentPostprocessing = 0; 275 | }; 276 | /* End PBXSourcesBuildPhase section */ 277 | 278 | /* Begin PBXTargetDependency section */ 279 | 331C8086294A63A400263BE5 /* PBXTargetDependency */ = { 280 | isa = PBXTargetDependency; 281 | target = 97C146ED1CF9000F007C117D /* Runner */; 282 | targetProxy = 331C8085294A63A400263BE5 /* PBXContainerItemProxy */; 283 | }; 284 | /* End PBXTargetDependency section */ 285 | 286 | /* Begin PBXVariantGroup section */ 287 | 97C146FA1CF9000F007C117D /* Main.storyboard */ = { 288 | isa = PBXVariantGroup; 289 | children = ( 290 | 97C146FB1CF9000F007C117D /* Base */, 291 | ); 292 | name = Main.storyboard; 293 | sourceTree = ""; 294 | }; 295 | 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { 296 | isa = PBXVariantGroup; 297 | children = ( 298 | 97C147001CF9000F007C117D /* Base */, 299 | ); 300 | name = LaunchScreen.storyboard; 301 | sourceTree = ""; 302 | }; 303 | /* End PBXVariantGroup section */ 304 | 305 | /* Begin XCBuildConfiguration section */ 306 | 249021D3217E4FDB00AE95B9 /* Profile */ = { 307 | isa = XCBuildConfiguration; 308 | buildSettings = { 309 | ALWAYS_SEARCH_USER_PATHS = NO; 310 | ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; 311 | CLANG_ANALYZER_NONNULL = YES; 312 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 313 | CLANG_CXX_LIBRARY = "libc++"; 314 | CLANG_ENABLE_MODULES = YES; 315 | CLANG_ENABLE_OBJC_ARC = YES; 316 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 317 | CLANG_WARN_BOOL_CONVERSION = YES; 318 | CLANG_WARN_COMMA = YES; 319 | CLANG_WARN_CONSTANT_CONVERSION = YES; 320 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 321 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 322 | CLANG_WARN_EMPTY_BODY = YES; 323 | CLANG_WARN_ENUM_CONVERSION = YES; 324 | CLANG_WARN_INFINITE_RECURSION = YES; 325 | CLANG_WARN_INT_CONVERSION = YES; 326 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 327 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 328 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 329 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 330 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 331 | CLANG_WARN_STRICT_PROTOTYPES = YES; 332 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 333 | CLANG_WARN_UNREACHABLE_CODE = YES; 334 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 335 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 336 | COPY_PHASE_STRIP = NO; 337 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 338 | ENABLE_NS_ASSERTIONS = NO; 339 | ENABLE_STRICT_OBJC_MSGSEND = YES; 340 | ENABLE_USER_SCRIPT_SANDBOXING = NO; 341 | GCC_C_LANGUAGE_STANDARD = gnu99; 342 | GCC_NO_COMMON_BLOCKS = YES; 343 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 344 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 345 | GCC_WARN_UNDECLARED_SELECTOR = YES; 346 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 347 | GCC_WARN_UNUSED_FUNCTION = YES; 348 | GCC_WARN_UNUSED_VARIABLE = YES; 349 | IPHONEOS_DEPLOYMENT_TARGET = 12.0; 350 | MTL_ENABLE_DEBUG_INFO = NO; 351 | SDKROOT = iphoneos; 352 | SUPPORTED_PLATFORMS = iphoneos; 353 | TARGETED_DEVICE_FAMILY = "1,2"; 354 | VALIDATE_PRODUCT = YES; 355 | }; 356 | name = Profile; 357 | }; 358 | 249021D4217E4FDB00AE95B9 /* Profile */ = { 359 | isa = XCBuildConfiguration; 360 | baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; 361 | buildSettings = { 362 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 363 | CLANG_ENABLE_MODULES = YES; 364 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; 365 | ENABLE_BITCODE = NO; 366 | INFOPLIST_FILE = Runner/Info.plist; 367 | LD_RUNPATH_SEARCH_PATHS = ( 368 | "$(inherited)", 369 | "@executable_path/Frameworks", 370 | ); 371 | PRODUCT_BUNDLE_IDENTIFIER = com.dc16.call; 372 | PRODUCT_NAME = "$(TARGET_NAME)"; 373 | SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; 374 | SWIFT_VERSION = 5.0; 375 | VERSIONING_SYSTEM = "apple-generic"; 376 | }; 377 | name = Profile; 378 | }; 379 | 331C8088294A63A400263BE5 /* Debug */ = { 380 | isa = XCBuildConfiguration; 381 | buildSettings = { 382 | BUNDLE_LOADER = "$(TEST_HOST)"; 383 | CODE_SIGN_STYLE = Automatic; 384 | CURRENT_PROJECT_VERSION = 1; 385 | GENERATE_INFOPLIST_FILE = YES; 386 | MARKETING_VERSION = 1.0; 387 | PRODUCT_BUNDLE_IDENTIFIER = com.dc16.call.RunnerTests; 388 | PRODUCT_NAME = "$(TARGET_NAME)"; 389 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 390 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 391 | SWIFT_VERSION = 5.0; 392 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; 393 | }; 394 | name = Debug; 395 | }; 396 | 331C8089294A63A400263BE5 /* Release */ = { 397 | isa = XCBuildConfiguration; 398 | buildSettings = { 399 | BUNDLE_LOADER = "$(TEST_HOST)"; 400 | CODE_SIGN_STYLE = Automatic; 401 | CURRENT_PROJECT_VERSION = 1; 402 | GENERATE_INFOPLIST_FILE = YES; 403 | MARKETING_VERSION = 1.0; 404 | PRODUCT_BUNDLE_IDENTIFIER = com.dc16.call.RunnerTests; 405 | PRODUCT_NAME = "$(TARGET_NAME)"; 406 | SWIFT_VERSION = 5.0; 407 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; 408 | }; 409 | name = Release; 410 | }; 411 | 331C808A294A63A400263BE5 /* Profile */ = { 412 | isa = XCBuildConfiguration; 413 | buildSettings = { 414 | BUNDLE_LOADER = "$(TEST_HOST)"; 415 | CODE_SIGN_STYLE = Automatic; 416 | CURRENT_PROJECT_VERSION = 1; 417 | GENERATE_INFOPLIST_FILE = YES; 418 | MARKETING_VERSION = 1.0; 419 | PRODUCT_BUNDLE_IDENTIFIER = com.dc16.call.RunnerTests; 420 | PRODUCT_NAME = "$(TARGET_NAME)"; 421 | SWIFT_VERSION = 5.0; 422 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; 423 | }; 424 | name = Profile; 425 | }; 426 | 97C147031CF9000F007C117D /* Debug */ = { 427 | isa = XCBuildConfiguration; 428 | buildSettings = { 429 | ALWAYS_SEARCH_USER_PATHS = NO; 430 | ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = AppIcon; 431 | CLANG_ANALYZER_NONNULL = YES; 432 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 433 | CLANG_CXX_LIBRARY = "libc++"; 434 | CLANG_ENABLE_MODULES = YES; 435 | CLANG_ENABLE_OBJC_ARC = YES; 436 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 437 | CLANG_WARN_BOOL_CONVERSION = YES; 438 | CLANG_WARN_COMMA = YES; 439 | CLANG_WARN_CONSTANT_CONVERSION = YES; 440 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 441 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 442 | CLANG_WARN_EMPTY_BODY = YES; 443 | CLANG_WARN_ENUM_CONVERSION = YES; 444 | CLANG_WARN_INFINITE_RECURSION = YES; 445 | CLANG_WARN_INT_CONVERSION = YES; 446 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 447 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 448 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 449 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 450 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 451 | CLANG_WARN_STRICT_PROTOTYPES = YES; 452 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 453 | CLANG_WARN_UNREACHABLE_CODE = YES; 454 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 455 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 456 | COPY_PHASE_STRIP = NO; 457 | DEBUG_INFORMATION_FORMAT = dwarf; 458 | ENABLE_STRICT_OBJC_MSGSEND = YES; 459 | ENABLE_TESTABILITY = YES; 460 | ENABLE_USER_SCRIPT_SANDBOXING = NO; 461 | GCC_C_LANGUAGE_STANDARD = gnu99; 462 | GCC_DYNAMIC_NO_PIC = NO; 463 | GCC_NO_COMMON_BLOCKS = YES; 464 | GCC_OPTIMIZATION_LEVEL = 0; 465 | GCC_PREPROCESSOR_DEFINITIONS = ( 466 | "DEBUG=1", 467 | "$(inherited)", 468 | ); 469 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 470 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 471 | GCC_WARN_UNDECLARED_SELECTOR = YES; 472 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 473 | GCC_WARN_UNUSED_FUNCTION = YES; 474 | GCC_WARN_UNUSED_VARIABLE = YES; 475 | IPHONEOS_DEPLOYMENT_TARGET = 12.0; 476 | MTL_ENABLE_DEBUG_INFO = YES; 477 | ONLY_ACTIVE_ARCH = YES; 478 | SDKROOT = iphoneos; 479 | TARGETED_DEVICE_FAMILY = "1,2"; 480 | }; 481 | name = Debug; 482 | }; 483 | 97C147041CF9000F007C117D /* Release */ = { 484 | isa = XCBuildConfiguration; 485 | buildSettings = { 486 | ALWAYS_SEARCH_USER_PATHS = NO; 487 | ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = AppIcon; 488 | CLANG_ANALYZER_NONNULL = YES; 489 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 490 | CLANG_CXX_LIBRARY = "libc++"; 491 | CLANG_ENABLE_MODULES = YES; 492 | CLANG_ENABLE_OBJC_ARC = YES; 493 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 494 | CLANG_WARN_BOOL_CONVERSION = YES; 495 | CLANG_WARN_COMMA = YES; 496 | CLANG_WARN_CONSTANT_CONVERSION = YES; 497 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 498 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 499 | CLANG_WARN_EMPTY_BODY = YES; 500 | CLANG_WARN_ENUM_CONVERSION = YES; 501 | CLANG_WARN_INFINITE_RECURSION = YES; 502 | CLANG_WARN_INT_CONVERSION = YES; 503 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 504 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 505 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 506 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 507 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 508 | CLANG_WARN_STRICT_PROTOTYPES = YES; 509 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 510 | CLANG_WARN_UNREACHABLE_CODE = YES; 511 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 512 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 513 | COPY_PHASE_STRIP = NO; 514 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 515 | ENABLE_NS_ASSERTIONS = NO; 516 | ENABLE_STRICT_OBJC_MSGSEND = YES; 517 | ENABLE_USER_SCRIPT_SANDBOXING = NO; 518 | GCC_C_LANGUAGE_STANDARD = gnu99; 519 | GCC_NO_COMMON_BLOCKS = YES; 520 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 521 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 522 | GCC_WARN_UNDECLARED_SELECTOR = YES; 523 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 524 | GCC_WARN_UNUSED_FUNCTION = YES; 525 | GCC_WARN_UNUSED_VARIABLE = YES; 526 | IPHONEOS_DEPLOYMENT_TARGET = 12.0; 527 | MTL_ENABLE_DEBUG_INFO = NO; 528 | SDKROOT = iphoneos; 529 | SUPPORTED_PLATFORMS = iphoneos; 530 | SWIFT_COMPILATION_MODE = wholemodule; 531 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 532 | TARGETED_DEVICE_FAMILY = "1,2"; 533 | VALIDATE_PRODUCT = YES; 534 | }; 535 | name = Release; 536 | }; 537 | 97C147061CF9000F007C117D /* Debug */ = { 538 | isa = XCBuildConfiguration; 539 | baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; 540 | buildSettings = { 541 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 542 | CLANG_ENABLE_MODULES = YES; 543 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; 544 | ENABLE_BITCODE = NO; 545 | INFOPLIST_FILE = Runner/Info.plist; 546 | LD_RUNPATH_SEARCH_PATHS = ( 547 | "$(inherited)", 548 | "@executable_path/Frameworks", 549 | ); 550 | PRODUCT_BUNDLE_IDENTIFIER = com.dc16.call; 551 | PRODUCT_NAME = "$(TARGET_NAME)"; 552 | SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; 553 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 554 | SWIFT_VERSION = 5.0; 555 | VERSIONING_SYSTEM = "apple-generic"; 556 | }; 557 | name = Debug; 558 | }; 559 | 97C147071CF9000F007C117D /* Release */ = { 560 | isa = XCBuildConfiguration; 561 | baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; 562 | buildSettings = { 563 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 564 | CLANG_ENABLE_MODULES = YES; 565 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; 566 | ENABLE_BITCODE = NO; 567 | INFOPLIST_FILE = Runner/Info.plist; 568 | LD_RUNPATH_SEARCH_PATHS = ( 569 | "$(inherited)", 570 | "@executable_path/Frameworks", 571 | ); 572 | PRODUCT_BUNDLE_IDENTIFIER = com.dc16.call; 573 | PRODUCT_NAME = "$(TARGET_NAME)"; 574 | SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; 575 | SWIFT_VERSION = 5.0; 576 | VERSIONING_SYSTEM = "apple-generic"; 577 | }; 578 | name = Release; 579 | }; 580 | /* End XCBuildConfiguration section */ 581 | 582 | /* Begin XCConfigurationList section */ 583 | 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = { 584 | isa = XCConfigurationList; 585 | buildConfigurations = ( 586 | 331C8088294A63A400263BE5 /* Debug */, 587 | 331C8089294A63A400263BE5 /* Release */, 588 | 331C808A294A63A400263BE5 /* Profile */, 589 | ); 590 | defaultConfigurationIsVisible = 0; 591 | defaultConfigurationName = Release; 592 | }; 593 | 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { 594 | isa = XCConfigurationList; 595 | buildConfigurations = ( 596 | 97C147031CF9000F007C117D /* Debug */, 597 | 97C147041CF9000F007C117D /* Release */, 598 | 249021D3217E4FDB00AE95B9 /* Profile */, 599 | ); 600 | defaultConfigurationIsVisible = 0; 601 | defaultConfigurationName = Release; 602 | }; 603 | 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { 604 | isa = XCConfigurationList; 605 | buildConfigurations = ( 606 | 97C147061CF9000F007C117D /* Debug */, 607 | 97C147071CF9000F007C117D /* Release */, 608 | 249021D4217E4FDB00AE95B9 /* Profile */, 609 | ); 610 | defaultConfigurationIsVisible = 0; 611 | defaultConfigurationName = Release; 612 | }; 613 | /* End XCConfigurationList section */ 614 | }; 615 | rootObject = 97C146E61CF9000F007C117D /* Project object */; 616 | } 617 | -------------------------------------------------------------------------------- /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 | 37 | 38 | 39 | 40 | 43 | 49 | 50 | 51 | 52 | 53 | 64 | 66 | 72 | 73 | 74 | 75 | 81 | 83 | 89 | 90 | 91 | 92 | 94 | 95 | 98 | 99 | 100 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Runner/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import Flutter 2 | import UIKit 3 | 4 | @main 5 | @objc class AppDelegate: FlutterAppDelegate { 6 | override func application( 7 | _ application: UIApplication, 8 | didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? 9 | ) -> Bool { 10 | GeneratedPluginRegistrant.register(with: self) 11 | return super.application(application, didFinishLaunchingWithOptions: launchOptions) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | {"images":[{"size":"20x20","idiom":"iphone","filename":"Icon-App-20x20@2x.png","scale":"2x"},{"size":"20x20","idiom":"iphone","filename":"Icon-App-20x20@3x.png","scale":"3x"},{"size":"29x29","idiom":"iphone","filename":"Icon-App-29x29@1x.png","scale":"1x"},{"size":"29x29","idiom":"iphone","filename":"Icon-App-29x29@2x.png","scale":"2x"},{"size":"29x29","idiom":"iphone","filename":"Icon-App-29x29@3x.png","scale":"3x"},{"size":"40x40","idiom":"iphone","filename":"Icon-App-40x40@2x.png","scale":"2x"},{"size":"40x40","idiom":"iphone","filename":"Icon-App-40x40@3x.png","scale":"3x"},{"size":"57x57","idiom":"iphone","filename":"Icon-App-57x57@1x.png","scale":"1x"},{"size":"57x57","idiom":"iphone","filename":"Icon-App-57x57@2x.png","scale":"2x"},{"size":"60x60","idiom":"iphone","filename":"Icon-App-60x60@2x.png","scale":"2x"},{"size":"60x60","idiom":"iphone","filename":"Icon-App-60x60@3x.png","scale":"3x"},{"size":"20x20","idiom":"ipad","filename":"Icon-App-20x20@1x.png","scale":"1x"},{"size":"20x20","idiom":"ipad","filename":"Icon-App-20x20@2x.png","scale":"2x"},{"size":"29x29","idiom":"ipad","filename":"Icon-App-29x29@1x.png","scale":"1x"},{"size":"29x29","idiom":"ipad","filename":"Icon-App-29x29@2x.png","scale":"2x"},{"size":"40x40","idiom":"ipad","filename":"Icon-App-40x40@1x.png","scale":"1x"},{"size":"40x40","idiom":"ipad","filename":"Icon-App-40x40@2x.png","scale":"2x"},{"size":"50x50","idiom":"ipad","filename":"Icon-App-50x50@1x.png","scale":"1x"},{"size":"50x50","idiom":"ipad","filename":"Icon-App-50x50@2x.png","scale":"2x"},{"size":"72x72","idiom":"ipad","filename":"Icon-App-72x72@1x.png","scale":"1x"},{"size":"72x72","idiom":"ipad","filename":"Icon-App-72x72@2x.png","scale":"2x"},{"size":"76x76","idiom":"ipad","filename":"Icon-App-76x76@1x.png","scale":"1x"},{"size":"76x76","idiom":"ipad","filename":"Icon-App-76x76@2x.png","scale":"2x"},{"size":"83.5x83.5","idiom":"ipad","filename":"Icon-App-83.5x83.5@2x.png","scale":"2x"},{"size":"1024x1024","idiom":"ios-marketing","filename":"Icon-App-1024x1024@1x.png","scale":"1x"}],"info":{"version":1,"author":"xcode"}} -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidche1116/CallApp/90c26c41e15e7534a12a7b9047192f5b7ba597b4/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidche1116/CallApp/90c26c41e15e7534a12a7b9047192f5b7ba597b4/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidche1116/CallApp/90c26c41e15e7534a12a7b9047192f5b7ba597b4/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidche1116/CallApp/90c26c41e15e7534a12a7b9047192f5b7ba597b4/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidche1116/CallApp/90c26c41e15e7534a12a7b9047192f5b7ba597b4/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidche1116/CallApp/90c26c41e15e7534a12a7b9047192f5b7ba597b4/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidche1116/CallApp/90c26c41e15e7534a12a7b9047192f5b7ba597b4/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidche1116/CallApp/90c26c41e15e7534a12a7b9047192f5b7ba597b4/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidche1116/CallApp/90c26c41e15e7534a12a7b9047192f5b7ba597b4/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidche1116/CallApp/90c26c41e15e7534a12a7b9047192f5b7ba597b4/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidche1116/CallApp/90c26c41e15e7534a12a7b9047192f5b7ba597b4/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidche1116/CallApp/90c26c41e15e7534a12a7b9047192f5b7ba597b4/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidche1116/CallApp/90c26c41e15e7534a12a7b9047192f5b7ba597b4/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidche1116/CallApp/90c26c41e15e7534a12a7b9047192f5b7ba597b4/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidche1116/CallApp/90c26c41e15e7534a12a7b9047192f5b7ba597b4/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidche1116/CallApp/90c26c41e15e7534a12a7b9047192f5b7ba597b4/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidche1116/CallApp/90c26c41e15e7534a12a7b9047192f5b7ba597b4/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidche1116/CallApp/90c26c41e15e7534a12a7b9047192f5b7ba597b4/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidche1116/CallApp/90c26c41e15e7534a12a7b9047192f5b7ba597b4/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidche1116/CallApp/90c26c41e15e7534a12a7b9047192f5b7ba597b4/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidche1116/CallApp/90c26c41e15e7534a12a7b9047192f5b7ba597b4/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchBackground.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "background.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchBackground.imageset/background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidche1116/CallApp/90c26c41e15e7534a12a7b9047192f5b7ba597b4/ios/Runner/Assets.xcassets/LaunchBackground.imageset/background.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "LaunchImage.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "filename" : "LaunchImage@2x.png", 10 | "idiom" : "universal", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "filename" : "LaunchImage@3x.png", 15 | "idiom" : "universal", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "author" : "xcode", 21 | "version" : 1 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidche1116/CallApp/90c26c41e15e7534a12a7b9047192f5b7ba597b4/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidche1116/CallApp/90c26c41e15e7534a12a7b9047192f5b7ba597b4/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidche1116/CallApp/90c26c41e15e7534a12a7b9047192f5b7ba597b4/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md: -------------------------------------------------------------------------------- 1 | # Launch Screen Assets 2 | 3 | You can customize the launch screen with your own desired assets by replacing the image files in this directory. 4 | 5 | You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. -------------------------------------------------------------------------------- /ios/Runner/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /ios/Runner/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /ios/Runner/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleDisplayName 8 | Call 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | call 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | $(FLUTTER_BUILD_NAME) 21 | CFBundleSignature 22 | ???? 23 | CFBundleVersion 24 | $(FLUTTER_BUILD_NUMBER) 25 | LSRequiresIPhoneOS 26 | 27 | UILaunchStoryboardName 28 | LaunchScreen 29 | UIMainStoryboardFile 30 | Main 31 | UISupportedInterfaceOrientations 32 | 33 | UIInterfaceOrientationPortrait 34 | UIInterfaceOrientationLandscapeLeft 35 | UIInterfaceOrientationLandscapeRight 36 | 37 | UISupportedInterfaceOrientations~ipad 38 | 39 | UIInterfaceOrientationPortrait 40 | UIInterfaceOrientationPortraitUpsideDown 41 | UIInterfaceOrientationLandscapeLeft 42 | UIInterfaceOrientationLandscapeRight 43 | 44 | CADisableMinimumFrameDurationOnPhone 45 | 46 | UIApplicationSupportsIndirectInputEvents 47 | 48 | UIStatusBarHidden 49 | 50 | UIViewControllerBasedStatusBarAppearance 51 | 52 | NSPhotoLibraryUsageDescription 53 | Add a contact Select a photo as your profile picture. 54 | LSApplicationQueriesSchemes 55 | 56 | tel 57 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /ios/Runner/Runner-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | #import "GeneratedPluginRegistrant.h" 2 | -------------------------------------------------------------------------------- /ios/RunnerTests/RunnerTests.swift: -------------------------------------------------------------------------------- 1 | import Flutter 2 | import UIKit 3 | import XCTest 4 | 5 | class RunnerTests: XCTestCase { 6 | 7 | func testExample() { 8 | // If you add code to the Runner application, consider adding tests here. 9 | // See https://developer.apple.com/documentation/xctest for more information about using XCTest. 10 | } 11 | 12 | } 13 | -------------------------------------------------------------------------------- /lib/data/call_record.dart: -------------------------------------------------------------------------------- 1 | class CallRecord { 2 | final String phone; 3 | final DateTime time; 4 | 5 | final int id; 6 | final String name; 7 | final String photo; 8 | 9 | CallRecord(this.phone, this.time, 10 | {this.id = 0, this.name = '', this.photo = ''}); 11 | 12 | // Convert into a Map. The keys must correspond to the names of the 13 | // columns in the database. 14 | Map toMap() { 15 | return { 16 | 'PHONE': phone, 17 | 'TIME': time.toString().substring(0, 19), 18 | }; 19 | } 20 | 21 | // Implement toString to make it easier to see information about 22 | // each when using the print statement. 23 | @override 24 | String toString() { 25 | return 'CALL_RECORD{ID: $id, PHONE: $phone, TIME: $time, NAME: $name, PHOTO: $photo}'; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /lib/data/phone_info.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | /// 卡片手机号码信息 4 | class PhoneInfo { 5 | PhoneInfo(this.name, this.phone, this.photo, 6 | {this.id = -1, this.num = 0, this.voice = '', this.wechat = ''}); 7 | 8 | final int id; 9 | 10 | /// 姓名,当[photo]为空时显示姓名 11 | String name; 12 | 13 | /// 拨打的号码 14 | String phone; 15 | 16 | /// 头像图片在assets/$flavor目录下的名称,可以为空 17 | /// 添加的头像图片在APP缓存目录包名目录中绝对路径 18 | String photo; 19 | 20 | /// 显示序号 21 | int num; 22 | 23 | /// 语音播报 24 | String voice; 25 | 26 | /// 微信视频 27 | String wechat; 28 | 29 | /// 没有头像只显示姓名时,卡片的背景色 30 | /// [show]为true时,强制显示颜色 31 | Color color({bool show = false}) { 32 | return (photo.isEmpty || show) 33 | ? Colors.primaries[int.parse(phone) % Colors.primaries.length].shade400 34 | : Colors.transparent; 35 | } 36 | 37 | // Convert into a Map. The keys must correspond to the names of the 38 | // columns in the database. 39 | Map toMap() { 40 | if (id < 0) { 41 | return { 42 | 'NAME': name, 43 | 'PHONE': phone, 44 | 'PHOTO': photo, 45 | 'NUM': num, 46 | 'VOICE': voice, 47 | 'WECHAT': wechat, 48 | }; 49 | } else { 50 | return { 51 | 'ID': id, 52 | 'NAME': name, 53 | 'PHONE': phone, 54 | 'PHOTO': photo, 55 | 'NUM': num, 56 | 'VOICE': voice, 57 | 'WECHAT': wechat, 58 | }; 59 | } 60 | } 61 | 62 | @override 63 | String toString() { 64 | return 'CALL_INFO{ID: $id, PHONE: $phone, NUM: $num, NAME: $name, PHOTO: $photo, VOICE: $voice, WECHAT: $wechat}'; 65 | } 66 | 67 | static List defaultList() { 68 | return _listGithub; 69 | } 70 | 71 | /// 全局电话信息(每个界面都用) 72 | static ValueNotifier> globalInfoList = 73 | ValueNotifier(defaultList()); 74 | 75 | /// github 76 | static final List _listGithub = []; 77 | } 78 | -------------------------------------------------------------------------------- /lib/data/voice_vibration_set.dart: -------------------------------------------------------------------------------- 1 | /// 语音播报设置 2 | class VoiceVibrationSet { 3 | static final defaultVoiceVibrationSet = 4 | VoiceVibrationSet(true, 100, 40, 100, true, 100, 125); 5 | 6 | VoiceVibrationSet(this.voice, this.volume, this.rate, this.pitch, 7 | this.vibration, this.duration, this.amplitude); 8 | 9 | final int id = 0; 10 | 11 | bool voice; 12 | 13 | int volume; 14 | 15 | int rate; 16 | 17 | int pitch; 18 | 19 | bool vibration; 20 | 21 | int duration; 22 | 23 | int amplitude; 24 | 25 | Map toMap() { 26 | return { 27 | 'ID': 0, 28 | 'VOICE': voice ? 1 : 0, 29 | 'VOLUME': volume, 30 | 'RATE': rate, 31 | 'PITCH': pitch, 32 | 'VIBRATION': vibration ? 1 : 0, 33 | 'DURATION': duration, 34 | 'AMPLITUDE': amplitude, 35 | }; 36 | } 37 | 38 | double getVolume() { 39 | return volume / 100.0; 40 | } 41 | 42 | void setVolume(double v) { 43 | volume = (v * 100).toInt(); 44 | } 45 | 46 | double getRate() { 47 | return rate / 100.0; 48 | } 49 | 50 | void setRate(double r) { 51 | rate = (r * 100).toInt(); 52 | } 53 | 54 | double getPitch() { 55 | return pitch / 100.0; 56 | } 57 | 58 | void setPitch(double p) { 59 | pitch = (p * 100).toInt(); 60 | } 61 | 62 | double getDuration() { 63 | return duration / 1000.0; 64 | } 65 | 66 | void setDuration(double d) { 67 | duration = (d * 1000).toInt(); 68 | } 69 | 70 | double getAmplitude() { 71 | double tmp = amplitude / 255.0; 72 | return tmp > 0.0 ? tmp : 0.0; 73 | } 74 | 75 | void setAmplitude(double a) { 76 | int tmp = (a * 255).toInt(); 77 | amplitude = tmp >= 1 ? tmp : -1; 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'package:call/utls/db_util.dart'; 2 | import 'package:call/utls/voice_vibration_util.dart'; 3 | import 'package:flutter/material.dart'; 4 | import 'package:flutter_localizations/flutter_localizations.dart'; 5 | import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; 6 | 7 | import 'page/home_page.dart'; 8 | 9 | void main() { 10 | WidgetsFlutterBinding.ensureInitialized(); 11 | DbUtil().init().then((_) { 12 | TtsVibrationUtil().init(); 13 | }); 14 | 15 | runApp(const CallApp()); 16 | } 17 | 18 | class CallApp extends StatelessWidget { 19 | const CallApp({super.key}); 20 | 21 | // This widget is the root of your application. 22 | @override 23 | Widget build(BuildContext context) { 24 | return MediaQuery.withNoTextScaling( 25 | child: MaterialApp( 26 | title: '打电话', 27 | theme: ThemeData( 28 | brightness: Brightness.dark, 29 | colorScheme: ColorScheme.fromSeed( 30 | seedColor: const Color(0xFF1677FF), 31 | brightness: Brightness.dark, 32 | dynamicSchemeVariant: DynamicSchemeVariant.fidelity, 33 | ), 34 | ), 35 | home: const HomePage(), 36 | navigatorObservers: [FlutterSmartDialog.observer], 37 | builder: FlutterSmartDialog.init(), 38 | localizationsDelegates: const [ 39 | GlobalMaterialLocalizations.delegate, 40 | GlobalWidgetsLocalizations.delegate, 41 | GlobalCupertinoLocalizations.delegate, 42 | ], 43 | supportedLocales: const [ 44 | Locale('zh', 'CN'), 45 | ], 46 | locale: const Locale('zh', 'CN'), 47 | ), 48 | ); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /lib/page/about_page.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | import 'package:auto_size_text/auto_size_text.dart'; 4 | import 'package:device_info_plus/device_info_plus.dart'; 5 | import 'package:flutter/material.dart'; 6 | import 'package:package_info_plus/package_info_plus.dart'; 7 | 8 | import '../utls/flavor_util.dart'; 9 | import '../utls/style_util.dart'; 10 | 11 | class AboutPage extends StatelessWidget { 12 | const AboutPage({super.key}); 13 | 14 | Future> _initData() async { 15 | Map info = {}; 16 | DeviceInfoPlugin deviceInfo = DeviceInfoPlugin(); 17 | if (Platform.isAndroid) { 18 | AndroidDeviceInfo androidInfo = await deviceInfo.androidInfo; 19 | info['品牌'] = androidInfo.brand; 20 | info['设备'] = androidInfo.product; 21 | info['型号'] = androidInfo.model; 22 | info['系统'] = androidInfo.version.release; 23 | } else if (Platform.isIOS) { 24 | IosDeviceInfo iosDeviceInfo = await deviceInfo.iosInfo; 25 | info['系统'] = iosDeviceInfo.systemVersion; 26 | info['型号'] = iosDeviceInfo.model; 27 | info['标识'] = iosDeviceInfo.utsname.machine; 28 | } 29 | 30 | PackageInfo packageInfo = await PackageInfo.fromPlatform(); 31 | info['名称'] = packageInfo.appName; 32 | info['版本'] = packageInfo.version; 33 | info['构建'] = packageInfo.buildNumber; 34 | 35 | info['风格'] = FlavorUtil.flavor(); 36 | 37 | return info; 38 | } 39 | 40 | @override 41 | Widget build(BuildContext context) { 42 | return Scaffold( 43 | appBar: AppBar( 44 | title: const Text('关于信息'), 45 | ), 46 | body: FutureBuilder( 47 | future: _initData(), 48 | builder: (BuildContext context, 49 | AsyncSnapshot> snapshot) { 50 | return Visibility( 51 | visible: (snapshot.connectionState == ConnectionState.done && 52 | snapshot.hasData && 53 | snapshot.data!.isNotEmpty), 54 | child: ListView( 55 | padding: const EdgeInsets.symmetric(horizontal: 20), 56 | children: [ 57 | for (String key in snapshot.data?.keys ?? []) 58 | ListTile( 59 | title: AutoSizeText( 60 | key, 61 | style: StyleUtil.textStyle, 62 | ), 63 | trailing: AutoSizeText( 64 | snapshot.data![key] ?? '', 65 | style: StyleUtil.textStyle, 66 | ), 67 | ), 68 | ], 69 | ), 70 | ); 71 | }, 72 | ), 73 | ); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /lib/page/add_page.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | import 'package:call/data/phone_info.dart'; 4 | import 'package:call/page/image_edit_page.dart'; 5 | import 'package:call/utls/db_util.dart'; 6 | import 'package:call/utls/style_util.dart'; 7 | import 'package:call/utls/widget_util.dart'; 8 | import 'package:flutter/cupertino.dart'; 9 | import 'package:flutter/material.dart'; 10 | import 'package:flutter/services.dart'; 11 | import 'package:path_provider/path_provider.dart'; 12 | import 'package:wechat_assets_picker/wechat_assets_picker.dart'; 13 | 14 | class AddPage extends StatefulWidget { 15 | const AddPage({this.info, super.key}); 16 | 17 | /// info默认null:新增联系人;!=null:修改联系人 18 | final PhoneInfo? info; 19 | 20 | @override 21 | State createState() => _AddPageState(); 22 | } 23 | 24 | class _AddPageState extends State { 25 | late ValueNotifier _photoFilePath; 26 | 27 | late TextEditingController _controllerName; 28 | late TextEditingController _controllerPhone; 29 | late TextEditingController _controllerVoice; 30 | late TextEditingController _controllerWechat; 31 | late FocusNode _focusNodeName; 32 | late FocusNode _focusNodePhone; 33 | late FocusNode _focusNodeVoice; 34 | late FocusNode _focusNodeWechat; 35 | late int _num; 36 | 37 | @override 38 | void initState() { 39 | super.initState(); 40 | 41 | _controllerName = TextEditingController(); 42 | _controllerPhone = TextEditingController(); 43 | _controllerVoice = TextEditingController(); 44 | _controllerWechat = TextEditingController(); 45 | 46 | _focusNodeName = FocusNode(); 47 | _focusNodePhone = FocusNode(); 48 | _focusNodeVoice = FocusNode(); 49 | _focusNodeWechat = FocusNode(); 50 | 51 | if (widget.info != null) { 52 | _photoFilePath = ValueNotifier(widget.info!.photo); 53 | _controllerName.text = widget.info!.name; 54 | _controllerPhone.text = widget.info!.phone; 55 | _controllerVoice.text = widget.info!.voice; 56 | _controllerWechat.text = widget.info!.wechat; 57 | _num = widget.info!.num; 58 | } else { 59 | _photoFilePath = ValueNotifier(""); 60 | } 61 | } 62 | 63 | @override 64 | void dispose() { 65 | _focusNodeName.dispose(); 66 | _focusNodePhone.dispose(); 67 | _focusNodeVoice.dispose(); 68 | _focusNodeWechat.dispose(); 69 | 70 | _controllerName.dispose(); 71 | _controllerPhone.dispose(); 72 | _controllerVoice.dispose(); 73 | _controllerWechat.dispose(); 74 | 75 | super.dispose(); 76 | } 77 | 78 | @override 79 | Widget build(BuildContext context) { 80 | return Scaffold( 81 | appBar: AppBar( 82 | title: 83 | (widget.info != null) ? const Text('编辑联系人') : const Text('添加联系人'), 84 | actions: [ 85 | IconButton( 86 | onPressed: () { 87 | _saveOrEdit(widget.info != null); 88 | }, 89 | icon: const Icon( 90 | CupertinoIcons.tray, 91 | ), 92 | ), 93 | ], 94 | ), 95 | body: ListView( 96 | padding: const EdgeInsets.symmetric(horizontal: 40), 97 | children: [ 98 | WidgetUtil.titleText('头像'), 99 | _photoWidget(), 100 | WidgetUtil.titleText('姓名'), 101 | TextField( 102 | controller: _controllerName, 103 | focusNode: _focusNodeName, 104 | textAlign: TextAlign.center, 105 | style: StyleUtil.textStyle, 106 | maxLength: 16, 107 | onSubmitted: (_) { 108 | if (_controllerPhone.text.isEmpty) { 109 | FocusScope.of(context).requestFocus(_focusNodePhone); 110 | } 111 | }, 112 | ), 113 | WidgetUtil.titleText('号码'), 114 | TextField( 115 | controller: _controllerPhone, 116 | focusNode: _focusNodePhone, 117 | textAlign: TextAlign.center, 118 | keyboardType: TextInputType.phone, 119 | style: StyleUtil.textStyle, 120 | maxLength: 32, 121 | inputFormatters: [ 122 | FilteringTextInputFormatter.allow(RegExp(r'[0-9]')), 123 | ], 124 | onSubmitted: (_) => _saveOrEdit(widget.info != null), 125 | ), 126 | WidgetUtil.titleText('语音'), 127 | TextField( 128 | controller: _controllerVoice, 129 | focusNode: _focusNodeVoice, 130 | textAlign: TextAlign.center, 131 | style: StyleUtil.textStyle, 132 | maxLength: 16, 133 | onSubmitted: (_) => _saveOrEdit(widget.info != null), 134 | ), 135 | WidgetUtil.titleText('微信'), 136 | TextField( 137 | controller: _controllerWechat, 138 | focusNode: _focusNodeWechat, 139 | textAlign: TextAlign.center, 140 | style: StyleUtil.textStyle, 141 | maxLength: 16, 142 | onSubmitted: (_) => _saveOrEdit(widget.info != null), 143 | ) 144 | ], 145 | ), 146 | ); 147 | } 148 | 149 | Widget _photoWidget() { 150 | return GestureDetector( 151 | onTap: () async { 152 | final List? list = await AssetPicker.pickAssets( 153 | context, 154 | pickerConfig: AssetPickerConfig( 155 | maxAssets: 1, 156 | requestType: RequestType.image, 157 | themeColor: Theme.of(context).colorScheme.onPrimary, 158 | ), 159 | ); 160 | if (list != null && list.length == 1) { 161 | File? path = await list.first.file; 162 | if (!mounted) return; 163 | final result = await Navigator.push( 164 | context, 165 | MaterialPageRoute(builder: (context) => ImageEditPage(path!)), 166 | ); 167 | 168 | if (result != null) { 169 | /// 获取存储目录 170 | var tempDir = await getApplicationDocumentsDirectory(); 171 | 172 | /// 生成file文件格式 173 | var file = await File( 174 | '${tempDir.path}/image_${DateTime.now().toString().replaceAll(' ', '_')}.jpg') 175 | .create(); 176 | //转成file文件 177 | file.writeAsBytesSync(result); 178 | _photoFilePath.value = file.path; 179 | 180 | await Future.delayed(const Duration(seconds: 1)); 181 | if (!mounted) { 182 | return; 183 | } 184 | if (_controllerName.text.isEmpty) { 185 | FocusScope.of(context).requestFocus(_focusNodeName); 186 | } 187 | } 188 | } 189 | }, 190 | child: Container( 191 | constraints: const BoxConstraints(maxWidth: 200, maxHeight: 200), 192 | padding: const EdgeInsets.symmetric(horizontal: 40, vertical: 10), 193 | child: Center( 194 | child: AspectRatio( 195 | aspectRatio: 1.0, 196 | child: ValueListenableBuilder( 197 | valueListenable: _photoFilePath, 198 | builder: (BuildContext context, String value, Widget? child) { 199 | return WidgetUtil.photoImageIcon( 200 | value, 200, Colors.transparent); 201 | }), 202 | ), 203 | ), 204 | ), 205 | ); 206 | } 207 | 208 | Future _saveOrEdit(bool isEdit) async { 209 | String name = _controllerName.text; 210 | String phone = _controllerPhone.text; 211 | if (name.isEmpty || phone.isEmpty) { 212 | WidgetUtil.showToast('姓名和号码不能为空'); 213 | } else { 214 | await WidgetUtil.confirmPopup('是否保存【$name】?', onTap: () async { 215 | if (isEdit) { 216 | await DbUtil().updateInfo(PhoneInfo(name, phone, _photoFilePath.value, 217 | id: widget.info!.id, 218 | num: _num, 219 | voice: _controllerVoice.text, 220 | wechat: _controllerWechat.text)); 221 | } else { 222 | await DbUtil().addInfo(PhoneInfo(name, phone, _photoFilePath.value, 223 | voice: _controllerVoice.text, wechat: _controllerWechat.text)); 224 | } 225 | await DbUtil().queryInfo(); 226 | WidgetUtil.showToast(isEdit ? '编辑成功' : '保存成功'); 227 | if (!mounted) return; 228 | Navigator.pop(context); 229 | }); 230 | } 231 | } 232 | } 233 | -------------------------------------------------------------------------------- /lib/page/data_page.dart: -------------------------------------------------------------------------------- 1 | import 'dart:typed_data'; 2 | 3 | import 'package:call/utls/db_util.dart'; 4 | import 'package:call/utls/style_util.dart'; 5 | import 'package:call/utls/widget_util.dart'; 6 | import 'package:file_picker/file_picker.dart'; 7 | import 'package:flutter/material.dart'; 8 | 9 | class DataPage extends StatelessWidget { 10 | const DataPage({super.key}); 11 | 12 | static List titleList = ['清空数据库', '导入数据库', '导出数据库']; 13 | 14 | static List subtitleList = [ 15 | '清空所有数据,清空所有联系人信息', 16 | '从手机文件中选择一个数据库,替换当前数据库(表格式必须相同,否则软件会故障)', 17 | '将当前数据库导出并(不包含图片信息)', 18 | ]; 19 | 20 | static _clear() async { 21 | WidgetUtil.confirmPopup('是否清空所有数据?不可恢复!', onTap: () async { 22 | await DbUtil().cleanAllTab(); 23 | WidgetUtil.showToast('已清空'); 24 | }); 25 | } 26 | 27 | static _import() async { 28 | FilePickerResult? result = await FilePicker.platform.pickFiles(); 29 | if (result != null) { 30 | await DbUtil().import(result.paths.first!); 31 | } 32 | WidgetUtil.showToast(result != null ? '导入成功' : '取消导入'); 33 | } 34 | 35 | static _export() async { 36 | Uint8List bytes = await DbUtil().getDataBytes(); 37 | String? outputFile = await FilePicker.platform 38 | .saveFile(fileName: 'call_info.db', bytes: bytes); 39 | WidgetUtil.showToast(outputFile != null ? '导出成功' : '取消导出'); 40 | } 41 | 42 | static List funList = [_clear, _import, _export]; 43 | 44 | @override 45 | Widget build(BuildContext context) { 46 | return Scaffold( 47 | appBar: AppBar( 48 | title: const Text('数据库管理'), 49 | ), 50 | body: ListView( 51 | padding: const EdgeInsets.symmetric(horizontal: 10), 52 | children: [for (int i = 0; i < titleList.length; ++i) _buildItem(i)], 53 | ), 54 | ); 55 | } 56 | 57 | Widget _buildItem(int index) { 58 | return GestureDetector( 59 | onTap: () async { 60 | await funList[index](); 61 | }, 62 | child: Card( 63 | margin: const EdgeInsets.all(10), 64 | child: Padding( 65 | padding: const EdgeInsets.symmetric(vertical: 10), 66 | child: ListTile( 67 | title: Text(titleList[index], style: StyleUtil.textStyle), 68 | subtitle: Text(subtitleList[index]), 69 | ), 70 | ), 71 | ), 72 | ); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /lib/page/delete_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:auto_size_text/auto_size_text.dart'; 2 | import 'package:call/data/phone_info.dart'; 3 | import 'package:call/utls/style_util.dart'; 4 | import 'package:call/utls/widget_util.dart'; 5 | import 'package:flutter/material.dart'; 6 | 7 | import '../utls/db_util.dart'; 8 | 9 | class DeletePage extends StatelessWidget { 10 | const DeletePage({super.key}); 11 | 12 | @override 13 | Widget build(BuildContext context) { 14 | return Scaffold( 15 | appBar: AppBar( 16 | title: const Text('删除联系人'), 17 | ), 18 | body: ValueListenableBuilder( 19 | valueListenable: PhoneInfo.globalInfoList, 20 | builder: 21 | (BuildContext context, List value, Widget? child) { 22 | return value.isNotEmpty 23 | ? ListView.builder( 24 | itemCount: value.length, 25 | itemBuilder: (context, index) { 26 | return ListTile( 27 | leading: WidgetUtil.photoImageIcon(value[index].photo), 28 | title: AutoSizeText( 29 | value[index].name, 30 | style: StyleUtil.textStyle, 31 | ), 32 | trailing: AutoSizeText( 33 | value[index].phone, 34 | style: StyleUtil.textStyle, 35 | ), 36 | onTap: () async { 37 | await WidgetUtil.confirmPopup( 38 | '是否删除【${value[index].name}】这个联系人?', 39 | onTap: () async { 40 | await DbUtil().deleteInfo(value[index]); 41 | await DbUtil().queryInfo(); 42 | WidgetUtil.showToast('删除成功'); 43 | }, 44 | buttonColor: Colors.red, 45 | ); 46 | }, 47 | ); 48 | }, 49 | ) 50 | : Center( 51 | child: Text( 52 | '没有联系人', 53 | style: StyleUtil.textStyle, 54 | ), 55 | ); 56 | }), 57 | ); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /lib/page/edit_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:auto_size_text/auto_size_text.dart'; 2 | import 'package:call/data/phone_info.dart'; 3 | import 'package:call/page/add_page.dart'; 4 | import 'package:call/utls/style_util.dart'; 5 | import 'package:call/utls/widget_util.dart'; 6 | import 'package:flutter/material.dart'; 7 | 8 | class EditPage extends StatelessWidget { 9 | const EditPage({super.key}); 10 | 11 | @override 12 | Widget build(BuildContext context) { 13 | return Scaffold( 14 | appBar: AppBar( 15 | title: const Text('编辑联系人'), 16 | ), 17 | body: ValueListenableBuilder( 18 | valueListenable: PhoneInfo.globalInfoList, 19 | builder: 20 | (BuildContext context, List value, Widget? child) { 21 | return value.isNotEmpty 22 | ? ListView.builder( 23 | itemCount: value.length, 24 | itemBuilder: (context, index) { 25 | return ListTile( 26 | leading: WidgetUtil.photoImageIcon(value[index].photo), 27 | title: AutoSizeText( 28 | value[index].name, 29 | style: StyleUtil.textStyle, 30 | ), 31 | trailing: AutoSizeText( 32 | value[index].phone, 33 | style: StyleUtil.textStyle, 34 | ), 35 | onTap: () async { 36 | await Navigator.push( 37 | context, 38 | MaterialPageRoute( 39 | builder: (context) => 40 | AddPage(info: value[index])), 41 | ); 42 | }, 43 | ); 44 | }, 45 | ) 46 | : Center( 47 | child: Text( 48 | '没有联系人', 49 | style: StyleUtil.textStyle, 50 | ), 51 | ); 52 | }), 53 | ); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /lib/page/export_page.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | import 'package:auto_size_text/auto_size_text.dart'; 4 | import 'package:call/data/phone_info.dart'; 5 | import 'package:call/utls/style_util.dart'; 6 | import 'package:call/utls/widget_util.dart'; 7 | import 'package:flutter/cupertino.dart'; 8 | import 'package:flutter/material.dart'; 9 | import 'package:flutter/services.dart'; 10 | import 'package:photo_manager/photo_manager.dart'; 11 | 12 | import '../utls/flavor_util.dart'; 13 | 14 | class ExportPage extends StatelessWidget { 15 | const ExportPage({super.key}); 16 | 17 | @override 18 | Widget build(BuildContext context) { 19 | return Scaffold( 20 | appBar: AppBar( 21 | title: const Text('导出照片'), 22 | actions: [ 23 | IconButton( 24 | onPressed: () async { 25 | WidgetUtil.confirmPopup('是否导出所有联系人照片到相册?', onTap: () async { 26 | await _exportAll(); 27 | WidgetUtil.showToast('导出成功'); 28 | }); 29 | }, 30 | icon: const Icon( 31 | CupertinoIcons.tray, 32 | ), 33 | ), 34 | ], 35 | ), 36 | body: ValueListenableBuilder( 37 | valueListenable: PhoneInfo.globalInfoList, 38 | builder: 39 | (BuildContext context, List value, Widget? child) { 40 | return value.isNotEmpty 41 | ? ListView.builder( 42 | itemCount: value.length, 43 | itemBuilder: (context, index) { 44 | return ListTile( 45 | leading: WidgetUtil.photoImageIcon(value[index].photo), 46 | title: AutoSizeText( 47 | value[index].name, 48 | style: StyleUtil.textStyle, 49 | ), 50 | trailing: AutoSizeText( 51 | value[index].phone, 52 | style: StyleUtil.textStyle, 53 | ), 54 | onTap: () async { 55 | await WidgetUtil.confirmPopup( 56 | '是否导出【${value[index].name}】这个联系人的照片到相册?', 57 | onTap: () async { 58 | WidgetUtil.showLoading('导出中'); 59 | try { 60 | await _export(value[index]); 61 | WidgetUtil.hideLoading(); 62 | WidgetUtil.showToast( 63 | '导出【${value[index].name}】成功'); 64 | } catch (e) { 65 | WidgetUtil.hideLoading(); 66 | WidgetUtil.showToast( 67 | '导出【${value[index].name}】失败'); 68 | } 69 | }, 70 | ); 71 | }, 72 | ); 73 | }, 74 | ) 75 | : Center( 76 | child: Text( 77 | '没有联系人', 78 | style: StyleUtil.textStyle, 79 | ), 80 | ); 81 | }), 82 | ); 83 | } 84 | 85 | Future _export(PhoneInfo info) async { 86 | if (info.photo.isNotEmpty) { 87 | Uint8List data; 88 | if (info.photo.contains('/')) { 89 | File file = File(info.photo); 90 | data = await file.readAsBytes(); 91 | } else { 92 | ByteData bytes = await rootBundle 93 | .load('assets/${FlavorUtil.flavor()}/${info.photo}'); 94 | data = bytes.buffer.asUint8List(); 95 | } 96 | PhotoManager.editor.saveImage(data, filename: '${info.name}.jpg'); 97 | } 98 | } 99 | 100 | Future _exportAll() async { 101 | /// 导出照片 102 | for (PhoneInfo info in PhoneInfo.globalInfoList.value) { 103 | await _export(info); 104 | } 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /lib/page/home_page.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | import 'dart:math'; 3 | import 'dart:ui'; 4 | 5 | import 'package:android_intent_plus/android_intent.dart'; 6 | import 'package:auto_size_text/auto_size_text.dart'; 7 | import 'package:call/data/phone_info.dart'; 8 | import 'package:call/utls/db_util.dart'; 9 | import 'package:call/utls/flavor_util.dart'; 10 | import 'package:call/utls/style_util.dart'; 11 | import 'package:call/utls/voice_vibration_util.dart'; 12 | import 'package:call/utls/wechat_util.dart'; 13 | import 'package:call/utls/widget_util.dart'; 14 | import 'package:flutter/cupertino.dart'; 15 | import 'package:flutter/material.dart'; 16 | import 'package:flutter/services.dart'; 17 | import 'package:flutter_animate/flutter_animate.dart'; 18 | import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; 19 | import 'package:permission_handler/permission_handler.dart'; 20 | import 'package:url_launcher/url_launcher.dart'; 21 | 22 | import 'menu_page.dart'; 23 | 24 | class HomePage extends StatelessWidget { 25 | const HomePage({super.key}); 26 | 27 | @override 28 | Widget build(BuildContext context) { 29 | SystemUiOverlayStyle systemUiOverlayStyle = SystemUiOverlayStyle( 30 | systemNavigationBarColor: Colors.transparent, 31 | systemNavigationBarIconBrightness: Theme.of(context).brightness, 32 | ); 33 | SystemChrome.setSystemUIOverlayStyle(systemUiOverlayStyle); 34 | SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge); 35 | 36 | return Scaffold( 37 | backgroundColor: Colors.black, 38 | body: SafeArea( 39 | child: OrientationBuilder( 40 | builder: (context, orientation) { 41 | return ValueListenableBuilder( 42 | valueListenable: PhoneInfo.globalInfoList, 43 | builder: (BuildContext context, List value, 44 | Widget? child) { 45 | return value.isNotEmpty 46 | ? GridView.count( 47 | crossAxisCount: 48 | orientation == Orientation.portrait ? 2 : 4, 49 | padding: const EdgeInsets.all(5), 50 | childAspectRatio: 1.2, 51 | children: List.generate(value.length, (i) { 52 | return _infoCard(context, value[i]); 53 | }), 54 | ) 55 | : _noContact(context, orientation); 56 | }); 57 | }, 58 | ), 59 | ), 60 | ); 61 | } 62 | 63 | Widget _noContact(BuildContext context, Orientation orientation) { 64 | if (orientation == Orientation.portrait) { 65 | return Center( 66 | child: Padding( 67 | padding: EdgeInsets.all(30), 68 | child: Column( 69 | mainAxisSize: MainAxisSize.min, 70 | spacing: 20, 71 | children: [ 72 | Icon( 73 | CupertinoIcons.person_2, 74 | size: 200, 75 | color: Theme.of(context).colorScheme.primary, 76 | ), 77 | Text( 78 | '没有联系人', 79 | style: StyleUtil.buttonTextStyle.copyWith( 80 | fontWeight: FontWeight.bold, 81 | color: Theme.of(context).colorScheme.primary, 82 | ), 83 | ), 84 | ListTile( 85 | subtitle: Text( 86 | '注意:当有联系人之后,想要进入菜单的话,需要长按任意一个联系人不松手,1秒后手指拖动至屏幕最左上角后再松开!切记切记!!!', 87 | style: StyleUtil.trailingTextStyle, 88 | ), 89 | ), 90 | SizedBox(height: 60), 91 | FilledButton( 92 | onPressed: () { 93 | Navigator.push( 94 | context, 95 | MaterialPageRoute(builder: (context) => MenuPage()), 96 | ); 97 | }, 98 | child: SizedBox( 99 | width: 160, 100 | height: 60, 101 | child: Center( 102 | child: Text( 103 | '进入菜单', 104 | style: StyleUtil.textStyle, 105 | ), 106 | ), 107 | ), 108 | ), 109 | ], 110 | ), 111 | ), 112 | ); 113 | } else { 114 | return Center( 115 | child: Padding( 116 | padding: EdgeInsets.all(30), 117 | child: Row( 118 | mainAxisAlignment: MainAxisAlignment.spaceAround, 119 | children: [ 120 | Column( 121 | mainAxisSize: MainAxisSize.min, 122 | spacing: 20, 123 | children: [ 124 | Icon( 125 | CupertinoIcons.person_2, 126 | size: 180, 127 | color: Theme.of(context).colorScheme.primary, 128 | ), 129 | Text( 130 | '没有联系人', 131 | style: StyleUtil.buttonTextStyle.copyWith( 132 | fontWeight: FontWeight.bold, 133 | color: Theme.of(context).colorScheme.primary, 134 | ), 135 | ), 136 | ], 137 | ), 138 | Column( 139 | mainAxisSize: MainAxisSize.min, 140 | spacing: 60, 141 | children: [ 142 | SizedBox( 143 | width: 300, 144 | child: Text( 145 | '注意:当有联系人之后,想要进入菜单的话,需要长按任意一个联系人不松手,1秒后手指拖动至屏幕最左上角后再松开!切记切记!!!', 146 | style: StyleUtil.trailingTextStyle, 147 | maxLines: 10, 148 | ), 149 | ), 150 | FilledButton( 151 | onPressed: () { 152 | Navigator.push( 153 | context, 154 | MaterialPageRoute(builder: (context) => MenuPage()), 155 | ); 156 | }, 157 | child: SizedBox( 158 | width: 160, 159 | height: 60, 160 | child: Center( 161 | child: Text( 162 | '进入菜单', 163 | style: StyleUtil.textStyle, 164 | ), 165 | ), 166 | ), 167 | ), 168 | ], 169 | ) 170 | ], 171 | ), 172 | ), 173 | ); 174 | } 175 | } 176 | 177 | Widget _infoCard(BuildContext context, PhoneInfo info) { 178 | Offset startOffset = Offset.infinite; 179 | return GestureDetector( 180 | onTap: () => _showDialog(info), 181 | onLongPressStart: (LongPressStartDetails details) { 182 | startOffset = details.globalPosition; 183 | }, 184 | onLongPressEnd: (LongPressEndDetails details) { 185 | Offset endOffset = details.globalPosition; 186 | bool distance = (endOffset - startOffset).distance > 100; 187 | bool leftTop = (endOffset.dx < 100 && endOffset.dy < 100); 188 | if (distance && leftTop) { 189 | Navigator.push( 190 | context, 191 | MaterialPageRoute(builder: (context) => MenuPage()), 192 | ); 193 | } else if (!distance) { 194 | _showDialog(info); 195 | } 196 | }, 197 | child: Card( 198 | color: info.color(), 199 | child: _picture(info), 200 | ), 201 | ); 202 | } 203 | 204 | Widget _picture(PhoneInfo info) { 205 | return ConstrainedBox( 206 | constraints: const BoxConstraints.expand(), 207 | child: ClipRRect( 208 | borderRadius: BorderRadius.circular(10), 209 | child: info.photo.isNotEmpty 210 | ? info.photo.contains('/') 211 | ? Image.file( 212 | File(info.photo), 213 | fit: BoxFit.cover, 214 | errorBuilder: WidgetUtil.errorImage, 215 | ) 216 | : Image.asset( 217 | 'assets/${FlavorUtil.flavor()}/${info.photo}', 218 | fit: BoxFit.cover, 219 | errorBuilder: WidgetUtil.errorImage, 220 | ) 221 | : Padding( 222 | padding: const EdgeInsets.all(20), 223 | child: Center( 224 | child: AutoSizeText( 225 | info.name, 226 | style: StyleUtil.textLargeBlack, 227 | maxLines: 1, 228 | ), 229 | ), 230 | ), 231 | ), 232 | ); 233 | } 234 | 235 | Widget _buildCallNum(int num) { 236 | return Visibility( 237 | visible: (num > 0), 238 | child: Padding( 239 | padding: const EdgeInsets.symmetric(horizontal: 10), 240 | child: AutoSizeText.rich( 241 | maxFontSize: 40, 242 | TextSpan( 243 | children: [ 244 | const TextSpan(text: '今天已打 '), 245 | TextSpan( 246 | text: num.toString(), 247 | style: const TextStyle(color: Colors.red), 248 | ), 249 | const TextSpan(text: ' 次'), 250 | ], 251 | ), 252 | style: StyleUtil.textLargeBlack, 253 | maxLines: 1, 254 | ), 255 | ), 256 | ); 257 | } 258 | 259 | Widget _buildButton(PhoneInfo? info, Color color, IconData iconData, 260 | {double? w, double? h}) { 261 | return GestureDetector( 262 | onTap: () { 263 | if (info != null) { 264 | if (iconData == CupertinoIcons.videocam_fill) { 265 | _wechat(info); 266 | } else { 267 | _call(info); 268 | } 269 | } 270 | SmartDialog.dismiss(status: SmartStatus.allDialog); 271 | }, 272 | child: Card( 273 | color: color, 274 | child: SizedBox( 275 | width: w ?? double.infinity, 276 | height: h ?? double.infinity, 277 | child: Icon( 278 | iconData, 279 | color: Colors.white70, 280 | size: 68, 281 | ), 282 | ), 283 | ), 284 | ); 285 | } 286 | 287 | Widget _buildDialogLandscape(PhoneInfo info, int num, bool wechat) { 288 | return Column( 289 | children: [ 290 | Expanded( 291 | child: Row( 292 | children: [ 293 | Expanded( 294 | child: GestureDetector( 295 | onTap: () { 296 | if (info.wechat.isNotEmpty && wechat) { 297 | _wechat(info); 298 | } else { 299 | _call(info); 300 | } 301 | SmartDialog.dismiss(status: SmartStatus.allDialog); 302 | }, 303 | child: Card( 304 | color: info.color(), 305 | child: _picture(info), 306 | ), 307 | ), 308 | ), 309 | Column( 310 | children: [ 311 | Visibility( 312 | visible: info.wechat.isNotEmpty && wechat, 313 | child: Expanded( 314 | child: _buildButton( 315 | info, Colors.blue, CupertinoIcons.videocam_fill, 316 | w: 180), 317 | ), 318 | ), 319 | Expanded( 320 | child: _buildButton( 321 | info, Colors.green, CupertinoIcons.phone_fill, 322 | w: 180), 323 | ), 324 | Expanded( 325 | child: _buildButton( 326 | null, Colors.red, CupertinoIcons.phone_down_fill, 327 | w: 180), 328 | ), 329 | ], 330 | ) 331 | ], 332 | ), 333 | ), 334 | _buildCallNum(num), 335 | ], 336 | ); 337 | } 338 | 339 | Widget _buildDialogPortrait(PhoneInfo info, int num, bool wechat) { 340 | return Column( 341 | children: [ 342 | Expanded( 343 | child: GestureDetector( 344 | onTap: () { 345 | if (info.wechat.isNotEmpty && wechat) { 346 | _wechat(info); 347 | } else { 348 | _call(info); 349 | } 350 | SmartDialog.dismiss(status: SmartStatus.allDialog); 351 | }, 352 | child: Card( 353 | color: info.color(), 354 | child: _picture(info), 355 | ), 356 | ), 357 | ), 358 | _buildCallNum(num), 359 | Visibility( 360 | visible: info.wechat.isNotEmpty && wechat, 361 | child: _buildButton(info, Colors.blue, CupertinoIcons.videocam_fill, 362 | h: 68), 363 | ), 364 | _buildButton(info, Colors.green, CupertinoIcons.phone_fill, h: 68), 365 | _buildButton(null, Colors.red, CupertinoIcons.phone_down_fill, h: 68), 366 | ], 367 | ); 368 | } 369 | 370 | void _showDialog(PhoneInfo info) async { 371 | TtsVibrationUtil().speak(info); 372 | TtsVibrationUtil().vibration(); 373 | final int num = await DbUtil().getTodayNum(info.phone); 374 | bool wechat = await WechatUtil().isAccessibilityPermissionEnabled(); 375 | await SmartDialog.show( 376 | builder: (context) { 377 | double w = MediaQuery.of(context).size.width / 1.25; 378 | double h = w * 1.68; 379 | h = min(h, MediaQuery.of(context).size.height); 380 | if (MediaQuery.of(context).orientation == Orientation.landscape) { 381 | h = MediaQuery.of(context).size.height / 1.25; 382 | w = h * 1.68; 383 | w = min(w, MediaQuery.of(context).size.width); 384 | } 385 | Widget child = 386 | MediaQuery.of(context).orientation == Orientation.portrait 387 | ? _buildDialogPortrait(info, num, wechat) 388 | : _buildDialogLandscape(info, num, wechat); 389 | child = child.animate().shimmer( 390 | duration: 1500.ms, 391 | color: Colors.white.withValues(alpha: 0.4), 392 | ); 393 | 394 | return Container( 395 | constraints: BoxConstraints(maxWidth: w, maxHeight: h), 396 | decoration: BoxDecoration( 397 | color: Colors.white60, 398 | borderRadius: BorderRadius.circular(20), 399 | ), 400 | alignment: Alignment.center, 401 | padding: const EdgeInsets.all(15), 402 | child: child, 403 | ); 404 | }, 405 | maskWidget: BackdropFilter( 406 | filter: ImageFilter.blur( 407 | sigmaX: 5, 408 | sigmaY: 5, 409 | ), 410 | child: Container( 411 | width: double.infinity, 412 | height: double.infinity, 413 | color: Colors.black54, 414 | ), 415 | ), 416 | ); 417 | } 418 | 419 | Future _call(PhoneInfo info) async { 420 | DbUtil().addRecord(info); 421 | 422 | bool open = false; 423 | if (Platform.isAndroid) { 424 | if (await Permission.phone.request().isGranted) { 425 | // Either the permission was already granted before or the user just granted it. 426 | AndroidIntent intent = AndroidIntent( 427 | action: 'android.intent.action.CALL', 428 | data: 'tel:${info.phone}', 429 | ); 430 | await intent.launch(); 431 | open = true; 432 | } 433 | } 434 | if (!open) { 435 | Uri url = Uri(scheme: 'tel', path: info.phone); 436 | if (await canLaunchUrl(url)) { 437 | await launchUrl(url); 438 | } else { 439 | WidgetUtil.showToast('拨号失败'); 440 | } 441 | } 442 | } 443 | 444 | Future _wechat(PhoneInfo info) async { 445 | DbUtil().addWechatRecord(info); 446 | 447 | if (Platform.isAndroid) { 448 | bool status = await WechatUtil().check(); 449 | 450 | if (status) { 451 | WidgetUtil.showToast('正在控制手机'); 452 | WechatUtil().video(info); 453 | } else { 454 | WidgetUtil.showToast('没有无障碍权限'); 455 | } 456 | } else { 457 | WidgetUtil.showToast('暂不支持'); 458 | } 459 | } 460 | } 461 | -------------------------------------------------------------------------------- /lib/page/image_edit_page.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'dart:io'; 3 | 4 | import 'package:extended_image/extended_image.dart'; 5 | import 'package:flutter/foundation.dart'; 6 | import 'package:flutter/material.dart'; 7 | import 'package:flutter/services.dart'; 8 | import 'package:flutter_image_compress/flutter_image_compress.dart'; 9 | import 'package:flutter_ruler_picker/flutter_ruler_picker.dart'; 10 | 11 | import '../utls/crop_editor_helper.dart'; 12 | import '../utls/widget_util.dart'; 13 | 14 | class ImageEditPage extends StatefulWidget { 15 | final File _file; 16 | 17 | const ImageEditPage(this._file, {super.key}); 18 | 19 | @override 20 | State createState() => _ImageEditPageState(); 21 | } 22 | 23 | class _ImageEditPageState extends State { 24 | final ImageEditorController _editorController = ImageEditorController(); 25 | final MyRulerPickerController _rulerPickerController = 26 | MyRulerPickerController(value: 0.0); 27 | 28 | @override 29 | void initState() { 30 | super.initState(); 31 | } 32 | 33 | @override 34 | Widget build(BuildContext context) { 35 | ImageProvider imageProvider = ExtendedFileImageProvider( 36 | widget._file, 37 | cacheRawData: true, 38 | ); 39 | 40 | return Scaffold( 41 | body: Stack( 42 | children: [ 43 | ExtendedImage( 44 | image: imageProvider, 45 | fit: BoxFit.contain, 46 | mode: ExtendedImageMode.editor, 47 | enableLoadState: true, 48 | initEditorConfigHandler: (ExtendedImageState? state) { 49 | return EditorConfig( 50 | maxScale: 16.0, 51 | cropRectPadding: const EdgeInsets.all(20.0), 52 | hitTestSize: 20.0, 53 | cropLayerPainter: const EditorCropLayerPainter(), 54 | initCropRectType: InitCropRectType.imageRect, 55 | cropAspectRatio: CropAspectRatios.ratio1_1, 56 | controller: _editorController, 57 | cornerColor: Theme.of(context).colorScheme.onPrimary, 58 | ); 59 | }, 60 | ), 61 | SafeArea( 62 | child: Column( 63 | children: [ 64 | AppBar( 65 | backgroundColor: Colors.transparent, 66 | actions: [ 67 | FilledButton( 68 | onPressed: () async { 69 | WidgetUtil.showLoading('处理中'); 70 | 71 | /// 裁剪 72 | var list = await _cropImage(true); 73 | 74 | /// 压缩 75 | var result = 76 | await FlutterImageCompress.compressWithList( 77 | list!, 78 | minHeight: 800, 79 | minWidth: 800, 80 | quality: 90, 81 | ); 82 | 83 | WidgetUtil.hideLoading(); 84 | 85 | if (context.mounted) { 86 | Navigator.pop(context, result); 87 | } 88 | }, 89 | child: const Text( 90 | '完成', 91 | ), 92 | ), 93 | SizedBox(width: 20), 94 | ], 95 | ), 96 | const Spacer(), 97 | Padding( 98 | padding: EdgeInsets.symmetric(horizontal: 20), 99 | child: Row( 100 | children: [ 101 | Expanded( 102 | child: LayoutBuilder( 103 | builder: (BuildContext c, BoxConstraints b) { 104 | return RulerPicker( 105 | controller: _rulerPickerController, 106 | rulerBackgroundColor: Colors.transparent, 107 | onValueChanged: (num value) { 108 | if (_rulerPickerController.value 109 | .toDouble() 110 | .equalTo(value.toDouble()) && 111 | !_onUndoOrRedoing) { 112 | return; 113 | } 114 | HapticFeedback.vibrate(); 115 | 116 | _editorController.rotate( 117 | degree: value.toDouble() - 118 | _rulerPickerController.value, 119 | ); 120 | 121 | _rulerPickerController 122 | .setValueWithOutNotify(value); 123 | }, 124 | width: b.maxWidth, 125 | height: 50, 126 | onBuildRulerScaleText: 127 | (int index, num rulerScaleValue) { 128 | return '$rulerScaleValue'; 129 | }, 130 | ranges: const [ 131 | RulerRange(begin: -45, end: 45, scale: 1), 132 | ], 133 | ); 134 | }, 135 | ), 136 | ), 137 | ], 138 | ), 139 | ), 140 | Padding( 141 | padding: EdgeInsets.all(20), 142 | child: Row( 143 | mainAxisAlignment: MainAxisAlignment.spaceAround, 144 | children: [ 145 | IconButton( 146 | onPressed: () { 147 | _onUndoOrRedo(() { 148 | _editorController.undo(); 149 | }); 150 | }, 151 | icon: Icon( 152 | Icons.undo, 153 | size: 30, 154 | ), 155 | ), 156 | IconButton( 157 | onPressed: () { 158 | _onUndoOrRedo(() { 159 | _editorController.redo(); 160 | }); 161 | }, 162 | icon: Icon( 163 | Icons.redo, 164 | size: 30, 165 | ), 166 | ), 167 | IconButton( 168 | onPressed: () { 169 | _rulerPickerController.value = 0; 170 | _editorController.reset(); 171 | }, 172 | icon: Icon( 173 | Icons.restore, 174 | size: 30, 175 | ), 176 | ), 177 | IconButton( 178 | onPressed: () { 179 | _editorController.flip( 180 | animation: true, 181 | ); 182 | }, 183 | icon: const Icon(Icons.flip, size: 30), 184 | ), 185 | IconButton( 186 | onPressed: () { 187 | _editorController.rotate( 188 | degree: 90, 189 | animation: true, 190 | rotateCropRect: true, 191 | ); 192 | }, 193 | icon: const Icon(Icons.rotate_right, size: 30), 194 | ), 195 | ], 196 | ), 197 | ), 198 | ], 199 | ), 200 | ), 201 | ], 202 | ), 203 | ); 204 | } 205 | 206 | bool _onUndoOrRedoing = false; 207 | void _onUndoOrRedo(Function fn) { 208 | final double oldRotateDegrees = _editorController.rotateDegrees; 209 | _onUndoOrRedoing = true; 210 | fn(); 211 | _onUndoOrRedoing = false; 212 | final double newRotateDegrees = _editorController.rotateDegrees; 213 | if (oldRotateDegrees != newRotateDegrees && 214 | !(newRotateDegrees - oldRotateDegrees).isZero && 215 | (newRotateDegrees - oldRotateDegrees) % 90 != 0) { 216 | _rulerPickerController.value = 217 | _rulerPickerController.value + (newRotateDegrees - oldRotateDegrees); 218 | } 219 | } 220 | 221 | Future _cropImage(bool useNative) async { 222 | String msg = ''; 223 | try { 224 | EditImageInfo imageInfo = 225 | await cropImageDataWithNativeLibrary(_editorController); 226 | return imageInfo.data; 227 | } catch (e, stack) { 228 | msg = 'save failed: $e\n $stack'; 229 | debugPrint(msg); 230 | } 231 | 232 | return null; 233 | } 234 | } 235 | 236 | class MyRulerPickerController extends RulerPickerController { 237 | MyRulerPickerController({num value = 0}) : _value = value; 238 | @override 239 | num get value => _value; 240 | num _value; 241 | @override 242 | set value(num newValue) { 243 | if (_value == newValue) { 244 | return; 245 | } 246 | _value = newValue; 247 | notifyListeners(); 248 | } 249 | 250 | void setValueWithOutNotify(num newValue) { 251 | _value = newValue; 252 | } 253 | } 254 | -------------------------------------------------------------------------------- /lib/page/menu_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:animations/animations.dart'; 2 | import 'package:auto_size_text/auto_size_text.dart'; 3 | import 'package:call/page/about_page.dart'; 4 | import 'package:call/page/add_page.dart'; 5 | import 'package:call/page/data_page.dart'; 6 | import 'package:call/page/delete_page.dart'; 7 | import 'package:call/page/edit_page.dart'; 8 | import 'package:call/page/export_page.dart'; 9 | import 'package:call/page/permissions_page.dart'; 10 | import 'package:call/page/record_page.dart'; 11 | import 'package:call/page/reorder_page.dart'; 12 | import 'package:call/page/voice_vibration_page.dart'; 13 | import 'package:call/utls/style_util.dart'; 14 | import 'package:flutter/cupertino.dart'; 15 | import 'package:flutter/material.dart'; 16 | import 'package:flutter_animate/flutter_animate.dart'; 17 | 18 | class MenuPage extends StatelessWidget { 19 | MenuPage({super.key}); 20 | 21 | final List _menuList = [ 22 | '通话记录', 23 | '语音震动', 24 | '添加联系人', 25 | '编辑联系人', 26 | '删除联系人', 27 | '联系人排序', 28 | '权限管理', 29 | '导出照片', 30 | '数据库管理', 31 | '关于信息' 32 | ]; 33 | final List _iconList = [ 34 | CupertinoIcons.square_list, 35 | CupertinoIcons.bell, 36 | CupertinoIcons.person_badge_plus, 37 | CupertinoIcons.captions_bubble, 38 | CupertinoIcons.trash, 39 | CupertinoIcons.square_stack, 40 | CupertinoIcons.exclamationmark_shield, 41 | CupertinoIcons.photo_on_rectangle, 42 | CupertinoIcons.tray_full, 43 | CupertinoIcons.house 44 | ]; 45 | final List _pageList = [ 46 | const RecordPage(), 47 | const VoiceVibrationPage(), 48 | const AddPage(), 49 | const EditPage(), 50 | const DeletePage(), 51 | const ReorderPage(), 52 | const PermissionsPage(), 53 | const ExportPage(), 54 | const DataPage(), 55 | const AboutPage() 56 | ]; 57 | 58 | @override 59 | Widget build(BuildContext context) { 60 | List listMenu = [ 61 | for (final menu in _menuList) _menuCard(_menuList.indexOf(menu), context) 62 | ]; 63 | listMenu = 64 | listMenu.animate().flip(duration: const Duration(milliseconds: 500)); 65 | return Scaffold( 66 | appBar: AppBar( 67 | title: const Text('菜单'), 68 | ), 69 | body: OrientationBuilder( 70 | builder: (context, orientation) { 71 | return GridView.count( 72 | crossAxisCount: orientation == Orientation.portrait ? 2 : 4, 73 | padding: const EdgeInsets.all(5), 74 | childAspectRatio: 1.2, 75 | children: [...listMenu], 76 | ); 77 | }, 78 | ), 79 | ); 80 | } 81 | 82 | Widget _menuCard(int index, BuildContext context) { 83 | return OpenContainer( 84 | closedColor: Colors.transparent, 85 | middleColor: Color.lerp( 86 | Colors.primaries[index % Colors.primaries.length].shade500, 87 | Theme.of(context).scaffoldBackgroundColor, 88 | 0.5), 89 | openColor: Theme.of(context).scaffoldBackgroundColor, 90 | transitionType: ContainerTransitionType.fadeThrough, 91 | closedShape: RoundedRectangleBorder( 92 | borderRadius: BorderRadius.circular(20), 93 | ), 94 | closedBuilder: (context, action) { 95 | return Card( 96 | color: Colors.primaries[index % Colors.primaries.length].shade500, 97 | child: Padding( 98 | padding: const EdgeInsets.all(20), 99 | child: Column( 100 | mainAxisSize: MainAxisSize.min, 101 | mainAxisAlignment: MainAxisAlignment.spaceAround, 102 | children: [ 103 | Icon( 104 | _iconList[index], 105 | size: MediaQuery.of(context).size.shortestSide / 8, 106 | ), 107 | AutoSizeText( 108 | _menuList[index], 109 | style: StyleUtil.textLargeWhite, 110 | maxLines: 1, 111 | ) 112 | ], 113 | ), 114 | ), 115 | ); 116 | }, 117 | openShape: RoundedRectangleBorder( 118 | borderRadius: BorderRadius.circular(20), 119 | ), 120 | openBuilder: (context, action) { 121 | return _pageList[index]; 122 | }, 123 | ); 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /lib/page/permissions_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:call/utls/style_util.dart'; 2 | import 'package:call/utls/wechat_util.dart'; 3 | import 'package:call/utls/widget_util.dart'; 4 | import 'package:flutter/cupertino.dart'; 5 | import 'package:flutter/material.dart'; 6 | import 'package:permission_handler/permission_handler.dart'; 7 | 8 | class PermissionsPage extends StatefulWidget { 9 | const PermissionsPage({super.key}); 10 | 11 | @override 12 | State createState() => _PermissionsPageState(); 13 | } 14 | 15 | class _PermissionsPageState extends State { 16 | static List titleList = ['无障碍', '打电话', '相册', '设置']; 17 | 18 | static List subtitleList = [ 19 | '使用无障碍服务,拨打微信视频', 20 | '使用拨打电话权限,直接拨号', 21 | '使用相册权限,添加联系人时选择头像', 22 | '软件的所有设置', 23 | ]; 24 | 25 | static List openList = [false, false, false, true]; 26 | 27 | static _accessible() async { 28 | bool ret = await WechatUtil().check(); 29 | openList[0] = ret; 30 | WidgetUtil.showToast('设置${ret ? '成功' : '失败'}'); 31 | } 32 | 33 | static _call() async { 34 | bool ret = await Permission.phone.request().isGranted; 35 | openList[1] = ret; 36 | WidgetUtil.showToast('设置${ret ? '成功' : '失败'}'); 37 | } 38 | 39 | static _photo() async { 40 | bool ret = await Permission.photos.request().isGranted; 41 | openList[2] = ret; 42 | WidgetUtil.showToast('设置${ret ? '成功' : '失败'}'); 43 | } 44 | 45 | static _settings() async { 46 | openAppSettings(); 47 | } 48 | 49 | static List funList = [_accessible, _call, _photo, _settings]; 50 | 51 | @override 52 | void initState() { 53 | super.initState(); 54 | 55 | WechatUtil().isAccessibilityPermissionEnabled().then((res) async { 56 | openList[0] = res; 57 | openList[1] = await Permission.phone.isGranted; 58 | openList[2] = await Permission.photos.isGranted; 59 | setState(() {}); 60 | }); 61 | } 62 | 63 | @override 64 | Widget build(BuildContext context) { 65 | return Scaffold( 66 | appBar: AppBar( 67 | title: const Text('权限管理'), 68 | ), 69 | body: ListView( 70 | padding: const EdgeInsets.symmetric(horizontal: 10), 71 | children: [for (int i = 0; i < titleList.length; ++i) _buildItem(i)], 72 | ), 73 | ); 74 | } 75 | 76 | Widget _buildItem(int index) { 77 | return GestureDetector( 78 | onTap: () async { 79 | await funList[index](); 80 | setState(() {}); 81 | }, 82 | child: Card( 83 | margin: const EdgeInsets.all(10), 84 | child: Padding( 85 | padding: const EdgeInsets.symmetric(vertical: 10), 86 | child: ListTile( 87 | title: Text(titleList[index], style: StyleUtil.textStyle), 88 | subtitle: Text(subtitleList[index]), 89 | trailing: Icon( 90 | openList[index] 91 | ? CupertinoIcons.checkmark_alt_circle_fill 92 | : CupertinoIcons.xmark_circle, 93 | color: openList[index] 94 | ? Theme.of(context).colorScheme.secondary 95 | : Theme.of(context).colorScheme.error, 96 | ), 97 | ), 98 | ), 99 | ), 100 | ); 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /lib/page/record_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:auto_size_text/auto_size_text.dart'; 2 | import 'package:call/data/call_record.dart'; 3 | import 'package:call/utls/db_util.dart'; 4 | import 'package:call/utls/style_util.dart'; 5 | import 'package:call/utls/widget_util.dart'; 6 | import 'package:flutter/cupertino.dart'; 7 | import 'package:flutter/material.dart'; 8 | 9 | class RecordPage extends StatefulWidget { 10 | const RecordPage({super.key}); 11 | 12 | @override 13 | State createState() => _RecordPage(); 14 | } 15 | 16 | class _RecordPage extends State { 17 | final ValueNotifier> _callRecordList = 18 | ValueNotifier>([]); 19 | 20 | final ValueNotifier _isWechat = ValueNotifier(false); 21 | 22 | String _startDate = DateTime.now() 23 | .subtract(const Duration(days: 7)) 24 | .toString() 25 | .substring(0, 10); 26 | String _endDate = DateTime.now().toString().substring(0, 10); 27 | 28 | @override 29 | void initState() { 30 | super.initState(); 31 | 32 | DbUtil().queryRecord(_startDate, _endDate).then((list) { 33 | _callRecordList.value = list; 34 | }); 35 | } 36 | 37 | @override 38 | Widget build(BuildContext context) { 39 | return Scaffold( 40 | appBar: AppBar( 41 | // title: Text(_isWechat ? '视频记录' : '拨号记录'), 42 | title: ValueListenableBuilder( 43 | valueListenable: _isWechat, 44 | builder: (BuildContext context, bool value, Widget? child) { 45 | return Text(value ? '微信记录' : '拨号记录'); 46 | }), 47 | actions: [ 48 | IconButton( 49 | onPressed: () async { 50 | _isWechat.value = !_isWechat.value; 51 | if (_isWechat.value) { 52 | DbUtil().queryWechatRecord(_startDate, _endDate).then((list) { 53 | _callRecordList.value = list; 54 | }); 55 | } else { 56 | DbUtil().queryRecord(_startDate, _endDate).then((list) { 57 | _callRecordList.value = list; 58 | }); 59 | } 60 | }, 61 | icon: const Icon( 62 | CupertinoIcons.arrow_right_arrow_left_circle, 63 | ), 64 | ), 65 | IconButton( 66 | onPressed: () async { 67 | await WidgetUtil.confirmPopup( 68 | '是否删除全部${_isWechat.value ? '视频' : '通话'}记录?', 69 | buttonColor: Colors.red, onTap: () async { 70 | if (_isWechat.value) { 71 | await DbUtil().deleteAllWechatRecord(); 72 | } else { 73 | await DbUtil().deleteAllRecord(); 74 | } 75 | 76 | _callRecordList.value = []; 77 | WidgetUtil.showToast('删除成功'); 78 | }); 79 | }, 80 | icon: const Icon( 81 | CupertinoIcons.delete, 82 | ), 83 | ), 84 | IconButton( 85 | onPressed: () async { 86 | DateTimeRange? selectTimeRange = await showDateRangePicker( 87 | //语言环境 88 | // locale: Locale("zh", "CH"), 89 | context: context, 90 | //开始时间 91 | firstDate: DateTime(2024, 1, 1), 92 | //结束时间 93 | lastDate: DateTime(2050, 1, 1), 94 | cancelText: "取消", 95 | confirmText: "确定", 96 | //初始的时间范围选择 97 | initialDateRange: DateTimeRange( 98 | start: DateTime.parse(_startDate), 99 | end: DateTime.parse(_endDate)), 100 | ); 101 | 102 | if (selectTimeRange != null) { 103 | _startDate = selectTimeRange.start.toString().substring(0, 10); 104 | _endDate = selectTimeRange.end.toString().substring(0, 10); 105 | List list = 106 | await DbUtil().queryRecord(_startDate, _endDate); 107 | _callRecordList.value = list; 108 | WidgetUtil.showToast('刷新完成'); 109 | } 110 | }, 111 | icon: const Icon( 112 | CupertinoIcons.time, 113 | ), 114 | ), 115 | ], 116 | ), 117 | body: ValueListenableBuilder( 118 | valueListenable: _callRecordList, 119 | builder: 120 | (BuildContext context, List value, Widget? child) { 121 | return value.isNotEmpty 122 | ? ListView.builder( 123 | itemCount: value.length, 124 | itemBuilder: (context, index) { 125 | return ListTile( 126 | leading: WidgetUtil.photoImageIcon(value[index].photo), 127 | title: AutoSizeText(value[index].name), 128 | subtitle: AutoSizeText(value[index].phone), 129 | trailing: AutoSizeText( 130 | value[index].time.toString().substring(0, 19)), 131 | onTap: () async { 132 | await WidgetUtil.confirmPopup( 133 | '是否删除【${value[index].name}】这条记录?', 134 | onTap: () async { 135 | if (_isWechat.value) { 136 | await DbUtil().deleteWechatRecord(value[index]); 137 | _callRecordList.value = await DbUtil() 138 | .queryWechatRecord(_startDate, _endDate); 139 | } else { 140 | await DbUtil().deleteRecord(value[index]); 141 | _callRecordList.value = await DbUtil() 142 | .queryRecord(_startDate, _endDate); 143 | } 144 | 145 | WidgetUtil.showToast('删除成功'); 146 | }, 147 | buttonColor: Colors.red, 148 | ); 149 | }, 150 | ); 151 | }, 152 | ) 153 | : Center( 154 | child: Text( 155 | '没有记录', 156 | style: StyleUtil.textStyle, 157 | ), 158 | ); 159 | }), 160 | ); 161 | } 162 | } 163 | -------------------------------------------------------------------------------- /lib/page/reorder_page.dart: -------------------------------------------------------------------------------- 1 | import 'dart:ui'; 2 | 3 | import 'package:auto_size_text/auto_size_text.dart'; 4 | import 'package:call/data/phone_info.dart'; 5 | import 'package:call/utls/db_util.dart'; 6 | import 'package:call/utls/style_util.dart'; 7 | import 'package:call/utls/widget_util.dart'; 8 | import 'package:flutter/cupertino.dart'; 9 | import 'package:flutter/material.dart'; 10 | 11 | class ReorderPage extends StatefulWidget { 12 | const ReorderPage({super.key}); 13 | 14 | @override 15 | State createState() => _ReorderPageState(); 16 | } 17 | 18 | class _ReorderPageState extends State { 19 | List _phoneInfoList = PhoneInfo.defaultList(); 20 | 21 | @override 22 | void initState() { 23 | super.initState(); 24 | 25 | _phoneInfoList = PhoneInfo.globalInfoList.value; 26 | } 27 | 28 | @override 29 | Widget build(BuildContext context) { 30 | final List cards = [ 31 | for (int index = 0; index < _phoneInfoList.length; index += 1) 32 | Card( 33 | key: Key('$index'), 34 | color: _phoneInfoList[index].color(show: true), 35 | child: SizedBox( 36 | height: 60, 37 | child: Center( 38 | child: Padding( 39 | padding: const EdgeInsets.all(10), 40 | child: AutoSizeText( 41 | '${_phoneInfoList[index].name} ${_phoneInfoList[index].phone}', 42 | style: StyleUtil.textLargeBlack, 43 | ), 44 | ), 45 | )), 46 | ), 47 | ]; 48 | 49 | Widget proxyDecorator( 50 | Widget child, int index, Animation animation) { 51 | return AnimatedBuilder( 52 | animation: animation, 53 | builder: (BuildContext context, Widget? child) { 54 | final double animValue = Curves.easeInOut.transform(animation.value); 55 | final double elevation = lerpDouble(1, 6, animValue)!; 56 | final double scale = lerpDouble(1, 1.02, animValue)!; 57 | return Transform.scale( 58 | scale: scale, 59 | // Create a Card based on the color and the content of the dragged one 60 | // and set its elevation to the animated value. 61 | child: Card( 62 | elevation: elevation, 63 | color: cards[index].color, 64 | child: cards[index].child, 65 | ), 66 | ); 67 | }, 68 | child: child, 69 | ); 70 | } 71 | 72 | return Scaffold( 73 | appBar: AppBar( 74 | title: const Text('联系人排序'), 75 | actions: [ 76 | IconButton( 77 | onPressed: () async { 78 | await WidgetUtil.confirmPopup('是否保存全新顺序?', onTap: () async { 79 | for (int i = 0; i < _phoneInfoList.length; ++i) { 80 | _phoneInfoList[i].num = i; 81 | } 82 | await DbUtil().updateInfoList(_phoneInfoList); 83 | await DbUtil().queryInfo(); 84 | WidgetUtil.showToast('保存成功!'); 85 | }); 86 | }, 87 | icon: const Icon( 88 | CupertinoIcons.tray, 89 | ), 90 | ), 91 | ], 92 | ), 93 | body: ReorderableListView( 94 | padding: const EdgeInsets.all(10), 95 | proxyDecorator: proxyDecorator, 96 | onReorder: (int oldIndex, int newIndex) { 97 | setState(() { 98 | if (oldIndex < newIndex) { 99 | newIndex -= 1; 100 | } 101 | final PhoneInfo item = _phoneInfoList.removeAt(oldIndex); 102 | _phoneInfoList.insert(newIndex, item); 103 | }); 104 | }, 105 | children: cards, 106 | ), 107 | ); 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /lib/page/voice_vibration_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:call/data/voice_vibration_set.dart'; 2 | import 'package:call/utls/db_util.dart'; 3 | import 'package:call/utls/voice_vibration_util.dart'; 4 | import 'package:call/utls/widget_util.dart'; 5 | import 'package:flutter/cupertino.dart'; 6 | import 'package:flutter/material.dart'; 7 | 8 | class VoiceVibrationPage extends StatefulWidget { 9 | const VoiceVibrationPage({super.key}); 10 | 11 | @override 12 | State createState() => _VoiceVibrationPageState(); 13 | } 14 | 15 | class _VoiceVibrationPageState extends State { 16 | VoiceVibrationSet _voiceSet = VoiceVibrationSet.defaultVoiceVibrationSet; 17 | 18 | @override 19 | void initState() { 20 | super.initState(); 21 | 22 | DbUtil().getVoiceVibration().then((value) { 23 | setState(() { 24 | _voiceSet = value; 25 | }); 26 | }); 27 | } 28 | 29 | @override 30 | Widget build(BuildContext context) { 31 | return Scaffold( 32 | appBar: AppBar( 33 | title: const Text('语音震动设置'), 34 | actions: [ 35 | IconButton( 36 | onPressed: () async { 37 | await DbUtil().setVoiceVibration(_voiceSet); 38 | TtsVibrationUtil().setVoice(_voiceSet); 39 | WidgetUtil.showToast('保存成功'); 40 | }, 41 | icon: const Icon( 42 | CupertinoIcons.tray, 43 | ), 44 | ), 45 | ], 46 | ), 47 | body: ListView( 48 | padding: const EdgeInsets.symmetric(horizontal: 20), 49 | children: [ 50 | WidgetUtil.titleText('语音开关'), 51 | Row( 52 | mainAxisAlignment: MainAxisAlignment.end, 53 | children: [ 54 | Switch( 55 | value: _voiceSet.voice, 56 | onChanged: (bool value) { 57 | setState(() { 58 | _voiceSet.voice = value; 59 | }); 60 | }, 61 | ), 62 | ], 63 | ), 64 | WidgetUtil.titleText('音量'), 65 | Slider( 66 | value: _voiceSet.getVolume(), 67 | onChanged: (value) { 68 | setState(() { 69 | _voiceSet.setVolume(value); 70 | }); 71 | }, 72 | ), 73 | WidgetUtil.titleText('语速'), 74 | Slider( 75 | value: _voiceSet.getRate(), 76 | onChanged: (value) { 77 | setState(() { 78 | _voiceSet.setRate(value); 79 | }); 80 | }, 81 | ), 82 | WidgetUtil.titleText('语调'), 83 | Slider( 84 | value: _voiceSet.getPitch(), 85 | min: 0.5, 86 | max: 2.0, 87 | onChanged: (value) { 88 | setState(() { 89 | _voiceSet.setPitch(value); 90 | }); 91 | }, 92 | ), 93 | WidgetUtil.titleText('震动开关'), 94 | Row( 95 | mainAxisAlignment: MainAxisAlignment.end, 96 | children: [ 97 | Switch( 98 | value: _voiceSet.vibration, 99 | onChanged: (bool value) { 100 | setState(() { 101 | _voiceSet.vibration = value; 102 | }); 103 | }, 104 | ), 105 | ], 106 | ), 107 | WidgetUtil.titleText('震动时长'), 108 | Slider( 109 | value: _voiceSet.getDuration(), 110 | min: 0.1, 111 | max: 1.0, 112 | onChanged: (value) { 113 | setState(() { 114 | _voiceSet.setDuration(value); 115 | }); 116 | }, 117 | ), 118 | WidgetUtil.titleText('震动强度'), 119 | Slider( 120 | value: _voiceSet.getAmplitude(), 121 | onChanged: (value) { 122 | setState(() { 123 | _voiceSet.setAmplitude(value); 124 | }); 125 | }, 126 | ), 127 | ], 128 | ), 129 | ); 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /lib/utls/crop_editor_helper.dart: -------------------------------------------------------------------------------- 1 | import 'dart:ui'; 2 | 3 | import 'package:extended_image/extended_image.dart'; 4 | import 'package:flutter/foundation.dart'; 5 | import 'package:image_editor/image_editor.dart'; 6 | 7 | enum ImageType { gif, jpg } 8 | 9 | class EditImageInfo { 10 | EditImageInfo( 11 | this.data, 12 | this.imageType, 13 | ); 14 | final Uint8List? data; 15 | final ImageType imageType; 16 | } 17 | 18 | Future cropImageDataWithNativeLibrary( 19 | ImageEditorController imageEditorController) async { 20 | debugPrint('native library start cropping'); 21 | 22 | final EditActionDetails action = imageEditorController.editActionDetails!; 23 | 24 | final Uint8List img = imageEditorController.state!.rawImageData; 25 | 26 | final ImageEditorOption option = ImageEditorOption(); 27 | 28 | if (action.hasRotateDegrees) { 29 | final int rotateDegrees = action.rotateDegrees.toInt(); 30 | option.addOption(RotateOption(rotateDegrees)); 31 | } 32 | if (action.flipY) { 33 | option.addOption(const FlipOption(horizontal: true, vertical: false)); 34 | } 35 | 36 | if (action.needCrop) { 37 | Rect cropRect = imageEditorController.getCropRect()!; 38 | if (imageEditorController.state!.widget.extendedImageState.imageProvider 39 | is ExtendedResizeImage) { 40 | final ImmutableBuffer buffer = await ImmutableBuffer.fromUint8List(img); 41 | final ImageDescriptor descriptor = await ImageDescriptor.encoded(buffer); 42 | 43 | final double widthRatio = 44 | descriptor.width / imageEditorController.state!.image!.width; 45 | final double heightRatio = 46 | descriptor.height / imageEditorController.state!.image!.height; 47 | cropRect = Rect.fromLTRB( 48 | cropRect.left * widthRatio, 49 | cropRect.top * heightRatio, 50 | cropRect.right * widthRatio, 51 | cropRect.bottom * heightRatio, 52 | ); 53 | } 54 | option.addOption(ClipOption.fromRect(cropRect)); 55 | } 56 | 57 | final DateTime start = DateTime.now(); 58 | final Uint8List? result = await ImageEditor.editImage( 59 | image: img, 60 | imageEditorOption: option, 61 | ); 62 | 63 | debugPrint('${DateTime.now().difference(start)} :total time'); 64 | return EditImageInfo(result, ImageType.jpg); 65 | } 66 | -------------------------------------------------------------------------------- /lib/utls/db_util.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'dart:typed_data'; 3 | 4 | import 'package:call/data/call_record.dart'; 5 | import 'package:call/data/phone_info.dart'; 6 | import 'package:call/data/voice_vibration_set.dart'; 7 | import 'package:flutter_image_compress/flutter_image_compress.dart'; 8 | import 'package:sqflite/sqflite.dart'; 9 | 10 | import 'flavor_util.dart'; 11 | 12 | class DbUtil { 13 | factory DbUtil() => _instance; 14 | 15 | DbUtil._internal(); 16 | 17 | static final DbUtil _instance = DbUtil._internal(); 18 | 19 | late Database _database; 20 | 21 | Future init() async { 22 | String path = 23 | '${await getDatabasesPath()}/${FlavorUtil.flavor()}_database.db'; 24 | _database = await openDatabase( 25 | path, 26 | onCreate: (db, version) async { 27 | /// 创建拨号记录表 28 | await db.execute( 29 | ''' 30 | CREATE TABLE CALL_RECORD( 31 | ID INTEGER PRIMARY KEY, 32 | PHONE TEXT(32) NOT NULL, 33 | TIME TEXT(32)) 34 | ''', 35 | ); 36 | 37 | /// 创建微信视频记录表 38 | await db.execute( 39 | ''' 40 | CREATE TABLE WECHAT_RECORD( 41 | ID INTEGER PRIMARY KEY, 42 | PHONE TEXT(32) NOT NULL, 43 | TIME TEXT(32)) 44 | ''', 45 | ); 46 | 47 | /// 创建信息表 48 | await db.execute( 49 | ''' 50 | CREATE TABLE CALL_INFO( 51 | ID INTEGER PRIMARY KEY, 52 | NUM INTEGER DEFAULT 100, 53 | PHONE TEXT(32) NOT NULL, 54 | NAME TEXT(32) NOT NULL, 55 | VOICE TEXT(32), 56 | WECHAT TEXT(32), 57 | PHOTO TEXT(256)) 58 | ''', 59 | ); 60 | 61 | /// 创建语音震动配置表 62 | await db.execute( 63 | ''' 64 | CREATE TABLE VOICE_SET( 65 | ID INTEGER PRIMARY KEY, 66 | VOICE INTEGER DEFAULT 1, 67 | VOLUME INTEGER DEFAULT 100, 68 | RATE INTEGER DEFAULT 40, 69 | PITCH INTEGER DEFAULT 100, 70 | VIBRATION INTEGER DEFAULT 1, 71 | DURATION INTEGER DEFAULT 100, 72 | AMPLITUDE INTEGER DEFAULT 125) 73 | ''', 74 | ); 75 | }, 76 | // Set the version. This executes the onCreate function and provides a 77 | // path to perform database upgrades and downgrades. 78 | version: 1, 79 | ); 80 | 81 | List infoList = await queryInfo(); 82 | 83 | /// APP第一次启动数据库为空时,添加数据 84 | if (infoList.isEmpty) { 85 | infoList = PhoneInfo.defaultList(); 86 | Batch batch = _database.batch(); 87 | for (int i = 0; i < infoList.length; ++i) { 88 | infoList[i].num = i; 89 | batch.insert( 90 | 'CALL_INFO', 91 | infoList[i].toMap(), 92 | conflictAlgorithm: ConflictAlgorithm.replace, 93 | ); 94 | } 95 | await batch.commit(); 96 | infoList = await queryInfo(); 97 | } 98 | 99 | PhoneInfo.globalInfoList.value = infoList; 100 | } 101 | 102 | /// 查询所有电话号码信息 103 | Future> queryInfo() async { 104 | List> maps = 105 | await _database.query('CALL_INFO', orderBy: 'NUM'); 106 | 107 | // Convert the List into a List. 108 | List ret = List.generate(maps.length, (i) { 109 | return PhoneInfo( 110 | maps[i]['NAME'] as String, 111 | maps[i]['PHONE'] as String, 112 | maps[i]['PHOTO'] as String, 113 | id: maps[i]['ID'] as int, 114 | num: maps[i]['NUM'] as int, 115 | voice: maps[i]['VOICE'] as String, 116 | wechat: maps[i]['WECHAT'] as String, 117 | ); 118 | }); 119 | PhoneInfo.globalInfoList.value = ret; 120 | return ret; 121 | } 122 | 123 | /// 根据ID查询电话号码信息 124 | Future queryInfoById(int id) async { 125 | List> maps = 126 | await _database.query('CALL_INFO', where: 'ID = ?', whereArgs: [id]); 127 | 128 | if (maps.isEmpty) { 129 | return null; 130 | } else { 131 | return PhoneInfo( 132 | maps.first['NAME'] as String, 133 | maps.first['PHONE'] as String, 134 | maps.first['PHOTO'] as String, 135 | id: maps.first['ID'] as int, 136 | num: maps.first['NUM'] as int, 137 | voice: maps.first['VOICE'] as String, 138 | wechat: maps.first['WECHAT'] as String, 139 | ); 140 | } 141 | } 142 | 143 | /// 批量添加/修改手机号码信息 144 | Future updateInfo(PhoneInfo info) async { 145 | await _database.update( 146 | 'CALL_INFO', 147 | info.toMap(), 148 | where: 'ID = ?', 149 | whereArgs: [info.id], 150 | conflictAlgorithm: ConflictAlgorithm.replace, 151 | ); 152 | } 153 | 154 | /// 批量添加/修改手机号码信息 155 | Future updateInfoList(List list) async { 156 | Batch batch = _database.batch(); 157 | for (PhoneInfo info in list) { 158 | batch.update( 159 | 'CALL_INFO', 160 | info.toMap(), 161 | where: 'ID = ?', 162 | whereArgs: [info.id], 163 | conflictAlgorithm: ConflictAlgorithm.replace, 164 | ); 165 | } 166 | await batch.commit(); 167 | } 168 | 169 | /// 添加联系人 170 | Future addInfo(PhoneInfo info) async { 171 | await _database.insert( 172 | 'CALL_INFO', 173 | info.toMap(), 174 | conflictAlgorithm: ConflictAlgorithm.replace, 175 | ); 176 | } 177 | 178 | /// 删除联系人 179 | Future deleteInfo(PhoneInfo info) async { 180 | await _database.delete('CALL_INFO', where: 'ID = ?', whereArgs: [info.id]); 181 | } 182 | 183 | /// 数据库插入[record】通话记录 184 | Future addRecord(PhoneInfo info) async { 185 | var record = CallRecord( 186 | info.phone, 187 | DateTime.now(), 188 | ); 189 | 190 | await _database.insert( 191 | 'CALL_RECORD', 192 | record.toMap(), 193 | conflictAlgorithm: ConflictAlgorithm.replace, 194 | ); 195 | } 196 | 197 | /// 数据库插入[record】通话记录 198 | Future addWechatRecord(PhoneInfo info) async { 199 | var record = CallRecord( 200 | info.wechat, 201 | DateTime.now(), 202 | ); 203 | 204 | await _database.insert( 205 | 'WECHAT_RECORD', 206 | record.toMap(), 207 | conflictAlgorithm: ConflictAlgorithm.replace, 208 | ); 209 | } 210 | 211 | /// 根据时间范围查询通话记录 212 | Future> queryRecord(String start, String end) async { 213 | List> maps = await _database.rawQuery(''' 214 | SELECT CALL_RECORD.ID, CALL_RECORD.PHONE, CALL_RECORD.TIME, CALL_INFO.NAME, CALL_INFO.PHOTO 215 | FROM CALL_RECORD LEFT OUTER JOIN CALL_INFO 216 | ON CALL_RECORD.PHONE = CALL_INFO.PHONE 217 | where 218 | CALL_RECORD.TIME >= ? AND CALL_RECORD.TIME <= ? 219 | ORDER BY CALL_RECORD.TIME DESC 220 | ''', [start, '$end 23:59:59']); 221 | 222 | // Convert the List into a List. 223 | return List.generate(maps.length, (i) { 224 | return CallRecord( 225 | maps[i]['PHONE'] as String, 226 | DateTime.parse(maps[i]['TIME'] as String), 227 | id: maps[i]['ID'] as int, 228 | name: maps[i]['NAME'] != null ? maps[i]['NAME'] as String : '', 229 | photo: maps[i]['PHOTO'] != null ? maps[i]['PHOTO'] as String : '', 230 | ); 231 | }); 232 | } 233 | 234 | /// 根据时间范围查询通话记录 235 | Future> queryWechatRecord(String start, String end) async { 236 | List> maps = await _database.rawQuery(''' 237 | SELECT WECHAT_RECORD.ID, WECHAT_RECORD.PHONE, WECHAT_RECORD.TIME, CALL_INFO.NAME, CALL_INFO.PHOTO 238 | FROM WECHAT_RECORD LEFT OUTER JOIN CALL_INFO 239 | ON WECHAT_RECORD.PHONE = CALL_INFO.WECHAT 240 | where 241 | WECHAT_RECORD.TIME >= ? AND WECHAT_RECORD.TIME <= ? 242 | ORDER BY WECHAT_RECORD.TIME DESC 243 | ''', [start, '$end 23:59:59']); 244 | 245 | // Convert the List into a List. 246 | return List.generate(maps.length, (i) { 247 | return CallRecord( 248 | maps[i]['PHONE'] as String, 249 | DateTime.parse(maps[i]['TIME'] as String), 250 | id: maps[i]['ID'] as int, 251 | name: maps[i]['NAME'] != null ? maps[i]['NAME'] as String : '', 252 | photo: maps[i]['PHOTO'] != null ? maps[i]['PHOTO'] as String : '', 253 | ); 254 | }); 255 | } 256 | 257 | /// 获取[phone]今日已拨打次数 258 | Future getTodayNum(String phone) async { 259 | List> maps = await _database.query('CALL_RECORD', 260 | where: 'PHONE = ? AND TIME >= ?', 261 | whereArgs: [ 262 | phone, 263 | '${DateTime.now().toString().substring(0, 10)} 00:00:00' 264 | ]); 265 | return maps.length; 266 | } 267 | 268 | /// 删除一条通话记录 269 | Future deleteRecord(CallRecord record) async { 270 | await _database.delete('CALL_RECORD', where: 'ID = ${record.id}'); 271 | } 272 | 273 | /// 删除一条通话记录 274 | Future deleteWechatRecord(CallRecord record) async { 275 | await _database.delete('WECHAT_RECORD', where: 'ID = ${record.id}'); 276 | } 277 | 278 | /// 删除所有通话记录 279 | Future deleteAllRecord() async { 280 | await _database.delete('CALL_RECORD'); 281 | } 282 | 283 | /// 删除所有通话记录 284 | Future deleteAllWechatRecord() async { 285 | await _database.delete('WECHAT_RECORD'); 286 | } 287 | 288 | /// 保存语音播报配置 289 | Future setVoiceVibration(VoiceVibrationSet set) async { 290 | await _database.insert( 291 | 'VOICE_SET', 292 | set.toMap(), 293 | conflictAlgorithm: ConflictAlgorithm.replace, 294 | ); 295 | } 296 | 297 | /// 获取语音播报配置 298 | Future getVoiceVibration() async { 299 | List> maps = 300 | await _database.query('VOICE_SET', where: 'ID = 0'); 301 | 302 | // Convert the List into a List. 303 | List list = List.generate(maps.length, (i) { 304 | return VoiceVibrationSet( 305 | maps[i]['VOICE'] as int == 1, 306 | maps[i]['VOLUME'] as int, 307 | maps[i]['RATE'] as int, 308 | maps[i]['PITCH'] as int, 309 | maps[i]['VIBRATION'] as int == 1, 310 | maps[i]['DURATION'] as int, 311 | maps[i]['AMPLITUDE'] as int, 312 | ); 313 | }); 314 | 315 | if (list.isNotEmpty) { 316 | return list.first; 317 | } else { 318 | return VoiceVibrationSet.defaultVoiceVibrationSet; 319 | } 320 | } 321 | 322 | Future cleanAllTab() async { 323 | await _database.delete('CALL_RECORD'); 324 | await _database.delete('WECHAT_RECORD'); 325 | await _database.delete('CALL_INFO'); 326 | await DbUtil().queryInfo(); 327 | } 328 | 329 | Future getDataBytes() async { 330 | String path = 331 | '${await getDatabasesPath()}/${FlavorUtil.flavor()}_database.db'; 332 | await _database.close(); 333 | XFile file = XFile(path); 334 | Uint8List bytes = await file.readAsBytes(); 335 | _database = await openDatabase(path); 336 | return bytes; 337 | } 338 | 339 | Future import(String path) async { 340 | String pathOld = 341 | '${await getDatabasesPath()}/${FlavorUtil.flavor()}_database.db'; 342 | await _database.close(); 343 | await deleteDatabase(pathOld); 344 | XFile file = XFile(path); 345 | await file.saveTo(pathOld); 346 | _database = await openDatabase(pathOld); 347 | await DbUtil().queryInfo(); 348 | } 349 | } 350 | -------------------------------------------------------------------------------- /lib/utls/flavor_util.dart: -------------------------------------------------------------------------------- 1 | class FlavorUtil { 2 | static const String github = 'github'; 3 | 4 | static String flavor() { 5 | return github; 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /lib/utls/style_util.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class StyleUtil { 4 | static TextStyle textLargeWhite = const TextStyle( 5 | color: Colors.white, 6 | fontSize: 128, 7 | fontWeight: FontWeight.bold, 8 | ); 9 | 10 | static TextStyle textLargeBlack = const TextStyle( 11 | color: Colors.black, 12 | fontSize: 128, 13 | fontWeight: FontWeight.bold, 14 | ); 15 | 16 | static TextStyle textStyle = const TextStyle( 17 | fontSize: 20, 18 | ); 19 | 20 | static TextStyle buttonTextStyle = const TextStyle( 21 | fontSize: 40, 22 | ); 23 | 24 | static TextStyle trailingTextStyle = const TextStyle( 25 | fontSize: 16, 26 | ); 27 | } 28 | -------------------------------------------------------------------------------- /lib/utls/voice_vibration_util.dart: -------------------------------------------------------------------------------- 1 | import 'package:call/data/phone_info.dart'; 2 | import 'package:call/utls/db_util.dart'; 3 | import 'package:flutter_tts/flutter_tts.dart'; 4 | import 'package:vibration/vibration.dart'; 5 | 6 | import '../data/voice_vibration_set.dart'; 7 | 8 | class TtsVibrationUtil { 9 | factory TtsVibrationUtil() => _instance; 10 | 11 | TtsVibrationUtil._internal(); 12 | 13 | static final TtsVibrationUtil _instance = TtsVibrationUtil._internal(); 14 | 15 | late final FlutterTts _tts; 16 | 17 | late VoiceVibrationSet _set; 18 | 19 | /// 不同手机声音、音调、语速不同,可自行调整 20 | /// 手机没有自带tts或自带的不好听可以下载安装讯飞tts 21 | init() async { 22 | _tts = FlutterTts(); 23 | 24 | _set = await DbUtil().getVoiceVibration(); 25 | 26 | /// 设置语言 27 | await _tts.setLanguage("zh-CN"); 28 | 29 | await setVoice(_set); 30 | } 31 | 32 | /// 文字[text】转语音并播放 33 | Future speak(PhoneInfo info) async { 34 | if (_set.voice) { 35 | String text = info.voice; 36 | if (text.isEmpty) { 37 | text = info.name; 38 | } 39 | text = text.replaceAll(' ', ''); 40 | 41 | if (text.isNotEmpty) { 42 | await _tts.speak(text); 43 | } 44 | } 45 | } 46 | 47 | Future setVoice(VoiceVibrationSet set) async { 48 | _set = set; 49 | 50 | /// 设置音量 51 | await _tts.setVolume(set.getVolume()); 52 | 53 | /// 设置语速 54 | await _tts.setSpeechRate(set.getRate()); 55 | 56 | /// 音调 57 | await _tts.setPitch(set.getPitch()); 58 | } 59 | 60 | Future vibration() async { 61 | if (_set.vibration) { 62 | Vibration.vibrate(duration: _set.duration, amplitude: _set.amplitude); 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /lib/utls/wechat_util.dart: -------------------------------------------------------------------------------- 1 | import 'package:call/data/phone_info.dart'; 2 | import 'package:wechat_video_call/wechat_video_call.dart'; 3 | 4 | class WechatUtil { 5 | factory WechatUtil() => _instance; 6 | 7 | WechatUtil._internal(); 8 | 9 | static final WechatUtil _instance = WechatUtil._internal(); 10 | 11 | Future check() async { 12 | bool status = await WeChatVideoCall.isAccessibilityPermissionEnabled(); 13 | if (!status) { 14 | status = await WeChatVideoCall.requestAccessibilityPermission(); 15 | } 16 | return status; 17 | } 18 | 19 | /// 拨打[info.wechat】微信视频通话 20 | Future video(PhoneInfo info) async { 21 | WeChatVideoCall.videoCall(info.wechat); 22 | } 23 | 24 | Future requestAccessibilityPermission() async { 25 | return WeChatVideoCall.requestAccessibilityPermission(); 26 | } 27 | 28 | Future isAccessibilityPermissionEnabled() async { 29 | return WeChatVideoCall.isAccessibilityPermissionEnabled(); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /lib/utls/widget_util.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | import 'package:auto_size_text/auto_size_text.dart'; 4 | import 'package:call/utls/style_util.dart'; 5 | import 'package:flutter/cupertino.dart'; 6 | import 'package:flutter/material.dart'; 7 | import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; 8 | 9 | import 'flavor_util.dart'; 10 | 11 | class WidgetUtil { 12 | static Future confirmPopup( 13 | String msg, { 14 | GestureTapCallback? onTap, 15 | String? buttonText, 16 | Color buttonColor = Colors.green, 17 | }) async { 18 | await SmartDialog.show(builder: (context) { 19 | return ConstrainedBox( 20 | constraints: BoxConstraints.loose(const Size(280, 500)), 21 | child: Container( 22 | decoration: BoxDecoration( 23 | color: Colors.white.withValues(alpha: 0.8), 24 | borderRadius: BorderRadius.circular(20), 25 | ), 26 | child: Padding( 27 | padding: const EdgeInsets.all(15), 28 | child: Column( 29 | mainAxisAlignment: MainAxisAlignment.spaceAround, 30 | mainAxisSize: MainAxisSize.min, 31 | children: [ 32 | AutoSizeText( 33 | msg, 34 | style: StyleUtil.textLargeBlack, 35 | maxLines: msg.length ~/ 8 + 1, 36 | ), 37 | GestureDetector( 38 | onTap: () { 39 | SmartDialog.dismiss(status: SmartStatus.allDialog); 40 | if (onTap != null) { 41 | onTap(); 42 | } 43 | }, 44 | child: Card( 45 | color: buttonColor, 46 | child: SizedBox( 47 | height: 60, 48 | child: Center( 49 | child: AutoSizeText(buttonText ?? '确定', 50 | style: StyleUtil.buttonTextStyle), 51 | )), 52 | ), 53 | ), 54 | ], 55 | ), 56 | ), 57 | ), 58 | ); 59 | }); 60 | } 61 | 62 | static Future showToast(String msg) async { 63 | SmartDialog.showToast( 64 | msg, 65 | animationType: SmartAnimationType.centerScale_otherSlide, 66 | builder: (context) { 67 | return Card( 68 | color: Colors.grey.shade800, 69 | margin: const EdgeInsets.all(40), 70 | child: Padding( 71 | padding: const EdgeInsets.all(20), 72 | child: AutoSizeText( 73 | msg, 74 | style: StyleUtil.textLargeWhite, 75 | maxFontSize: 30, 76 | ), 77 | ), 78 | ); 79 | }, 80 | ); 81 | } 82 | 83 | static Future showLoading(String msg) async { 84 | return SmartDialog.showLoading(msg: msg); 85 | } 86 | 87 | static Future hideLoading() async { 88 | return SmartDialog.dismiss(status: SmartStatus.loading); 89 | } 90 | 91 | static Widget titleText(String title) { 92 | return Padding( 93 | padding: const EdgeInsets.fromLTRB(0, 20, 0, 0), 94 | child: AutoSizeText( 95 | title, 96 | style: StyleUtil.textStyle, 97 | ), 98 | ); 99 | } 100 | 101 | static Widget photoImageIcon(String photoPath, 102 | [int size = 50, Color? color]) { 103 | return ClipRRect( 104 | borderRadius: BorderRadius.circular(10), 105 | child: photoPath.isNotEmpty 106 | ? photoPath.contains('/') 107 | ? Image.file( 108 | File(photoPath), 109 | fit: BoxFit.cover, 110 | width: size.toDouble(), 111 | height: size.toDouble(), 112 | cacheWidth: size * 3, 113 | cacheHeight: size * 3, 114 | errorBuilder: WidgetUtil.errorImageSmall, 115 | ) 116 | : Image.asset( 117 | 'assets/${FlavorUtil.flavor()}/$photoPath', 118 | fit: BoxFit.cover, 119 | width: size.toDouble(), 120 | height: size.toDouble(), 121 | cacheWidth: size * 3, 122 | cacheHeight: size * 3, 123 | errorBuilder: WidgetUtil.errorImageSmall, 124 | ) 125 | : Container( 126 | color: color ?? Colors.grey.shade600, 127 | width: size.toDouble(), 128 | height: size.toDouble(), 129 | child: Center( 130 | child: Icon( 131 | CupertinoIcons.person_solid, 132 | size: size / 1.2, 133 | ), 134 | ), 135 | ), 136 | ); 137 | } 138 | 139 | static Widget errorImage( 140 | BuildContext context, 141 | Object error, 142 | StackTrace? stackTrace, 143 | ) { 144 | return Container( 145 | color: Colors.grey, 146 | child: Center( 147 | child: Column( 148 | mainAxisSize: MainAxisSize.min, 149 | children: [ 150 | Icon( 151 | CupertinoIcons.xmark_circle, 152 | size: 40, 153 | color: Colors.red, 154 | ), 155 | Text( 156 | '图片加载失败', 157 | style: StyleUtil.textStyle.copyWith( 158 | color: Colors.red, 159 | ), 160 | ), 161 | ], 162 | ), 163 | ), 164 | ); 165 | } 166 | 167 | static Widget errorImageSmall( 168 | BuildContext context, 169 | Object error, 170 | StackTrace? stackTrace, 171 | ) { 172 | return Container( 173 | color: Colors.grey, 174 | child: SizedBox( 175 | width: 50, 176 | height: 50, 177 | child: Center( 178 | child: Icon( 179 | CupertinoIcons.xmark_circle, 180 | size: 30, 181 | color: Colors.red, 182 | ), 183 | ), 184 | ), 185 | ); 186 | } 187 | } 188 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: call 2 | description: "A new Flutter project." 3 | # The following line prevents the package from being accidentally published to 4 | # pub.dev using `flutter pub publish`. This is preferred for private packages. 5 | publish_to: 'none' # Remove this line if you wish to publish to pub.dev 6 | 7 | # The following defines the version and build number for your application. 8 | # A version number is three numbers separated by dots, like 1.2.43 9 | # followed by an optional build number separated by a +. 10 | # Both the version and the builder number may be overridden in flutter 11 | # build by specifying --build-name and --build-number, respectively. 12 | # In Android, build-name is used as versionName while build-number used as versionCode. 13 | # Read more about Android versioning at https://developer.android.com/studio/publish/versioning 14 | # In iOS, build-name is used as CFBundleShortVersionString while build-number is used as CFBundleVersion. 15 | # Read more about iOS versioning at 16 | # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html 17 | # In Windows, build-name is used as the major, minor, and patch parts 18 | # of the product and file versions while build-number is used as the build suffix. 19 | version: 3.7.0+250219 20 | 21 | environment: 22 | sdk: ^3.7.0 23 | 24 | # Dependencies specify other packages that your package needs in order to work. 25 | # To automatically upgrade your package dependencies to the latest versions 26 | # consider running `flutter pub upgrade --major-versions`. Alternatively, 27 | # dependencies can be manually updated by changing the version numbers below to 28 | # the latest version available on pub.dev. To see which dependencies have newer 29 | # versions available, run `flutter pub outdated`. 30 | dependencies: 31 | flutter: 32 | sdk: flutter 33 | 34 | flutter_localizations: 35 | sdk: flutter 36 | 37 | # Android直接跳转打电话 38 | android_intent_plus: ^5.3.0 39 | # 动画效果 40 | animations: ^2.0.11 41 | # 字尺寸自动调整 42 | auto_size_text: ^3.0.0 43 | # 图标 44 | cupertino_icons: ^1.0.8 45 | # 设备信息 46 | device_info_plus: ^11.3.0 47 | # 图片 48 | extended_image: ^9.1.0 49 | # 文件选择 50 | file_picker: ^9.0.0 51 | # 动画效果 52 | flutter_animate: ^4.5.2 53 | # 图片压缩 54 | flutter_image_compress: ^2.4.0 55 | # 尺子控件 56 | flutter_ruler_picker: ^1.0.6 57 | # 弹窗 58 | flutter_smart_dialog: ^4.9.8+6 59 | # 文字转语音 60 | flutter_tts: ^4.2.2 61 | # 图片编辑 62 | image_editor: ^1.6.0 63 | # 应用信息 64 | package_info_plus: ^8.2.1 65 | # 文件路径 66 | path_provider: ^2.1.5 67 | # 权限申请 68 | permission_handler: ^11.3.1 69 | # 照片存储 70 | photo_manager: ^3.6.4 71 | # SQLite数据库 72 | sqflite: ^2.4.2 73 | # Android、iOS跳转 74 | url_launcher: ^6.3.1 75 | # 震动 76 | vibration: ^3.1.3 77 | # 图片选择 78 | wechat_assets_picker: ^9.4.2 79 | # 微信视频拨打 80 | wechat_video_call: ^1.0.2 81 | 82 | dev_dependencies: 83 | flutter_test: 84 | sdk: flutter 85 | 86 | # The "flutter_lints" package below contains a set of recommended lints to 87 | # encourage good coding practices. The lint set provided by the package is 88 | # activated in the `analysis_options.yaml` file located at the root of your 89 | # package. See that file for information about deactivating specific lint 90 | # rules and activating additional ones. 91 | flutter_lints: ^5.0.0 92 | 93 | flutter_launcher_icons: ^0.14.3 94 | 95 | flutter_native_splash: ^2.4.5 96 | 97 | # For information on the generic Dart part of this file, see the 98 | # following page: https://dart.dev/tools/pub/pubspec 99 | 100 | # The following section is specific to Flutter packages. 101 | flutter: 102 | 103 | # The following line ensures that the Material Icons font is 104 | # included with your application, so that you can use the icons in 105 | # the material Icons class. 106 | uses-material-design: true 107 | 108 | # An image asset can refer to one or more resolution-specific "variants", see 109 | # https://flutter.dev/to/resolution-aware-images 110 | 111 | # For details regarding adding assets from package dependencies, see 112 | # https://flutter.dev/to/asset-from-package 113 | 114 | # To add custom fonts to your application, add a fonts section here, 115 | # in this "flutter" section. Each entry in this list should have a 116 | # "family" key with the font family name, and a "fonts" key with a 117 | # list giving the asset and other descriptors for the font. For 118 | # example: 119 | # fonts: 120 | # - family: Schyler 121 | # fonts: 122 | # - asset: fonts/Schyler-Regular.ttf 123 | # - asset: fonts/Schyler-Italic.ttf 124 | # style: italic 125 | # - family: Trajan Pro 126 | # fonts: 127 | # - asset: fonts/TrajanPro.ttf 128 | # - asset: fonts/TrajanPro_Bold.ttf 129 | # weight: 700 130 | # 131 | # For details regarding fonts from package dependencies, 132 | # see https://flutter.dev/to/font-from-package 133 | 134 | flutter_launcher_icons: 135 | android: "ic_launcher" 136 | ios: true 137 | image_path: "assets/icon/icon.png" 138 | min_sdk_android: 26 # android min sdk min:16, default 21 139 | adaptive_icon_background: "#1677FF" 140 | adaptive_icon_foreground: "assets/icon/foreground.png" 141 | adaptive_icon_foreground_inset: 0 142 | -------------------------------------------------------------------------------- /test/widget_test.dart: -------------------------------------------------------------------------------- 1 | // This is a basic Flutter widget test. 2 | // 3 | // To perform an interaction with a widget in your test, use the WidgetTester 4 | // utility in the flutter_test package. For example, you can send tap and scroll 5 | // gestures. You can also use WidgetTester to find child widgets in the widget 6 | // tree, read text, and verify that the values of widget properties are correct. 7 | 8 | import 'package:flutter/material.dart'; 9 | import 'package:flutter_test/flutter_test.dart'; 10 | 11 | import 'package:call/main.dart'; 12 | 13 | void main() { 14 | testWidgets('Counter increments smoke test', (WidgetTester tester) async { 15 | // Build our app and trigger a frame. 16 | await tester.pumpWidget(const CallApp()); 17 | 18 | // Verify that our counter starts at 0. 19 | expect(find.text('0'), findsOneWidget); 20 | expect(find.text('1'), findsNothing); 21 | 22 | // Tap the '+' icon and trigger a frame. 23 | await tester.tap(find.byIcon(Icons.add)); 24 | await tester.pump(); 25 | 26 | // Verify that our counter has incremented. 27 | expect(find.text('0'), findsNothing); 28 | expect(find.text('1'), findsOneWidget); 29 | }); 30 | } 31 | --------------------------------------------------------------------------------