├── .gitignore ├── .metadata ├── LICENSE ├── README.md ├── README.zh-Hans.md ├── analysis_options.yaml ├── android ├── .gitignore ├── app │ ├── build.gradle │ └── src │ │ ├── debug │ │ └── AndroidManifest.xml │ │ ├── main │ │ ├── AndroidManifest.xml │ │ ├── java │ │ │ └── com │ │ │ │ └── bapaws │ │ │ │ └── answer │ │ │ │ └── MainActivity.java │ │ └── res │ │ │ ├── drawable-hdpi │ │ │ └── splash.png │ │ │ ├── drawable-mdpi │ │ │ └── splash.png │ │ │ ├── drawable-v21 │ │ │ ├── background.png │ │ │ └── launch_background.xml │ │ │ ├── drawable-xhdpi │ │ │ └── splash.png │ │ │ ├── drawable-xxhdpi │ │ │ └── splash.png │ │ │ ├── drawable-xxxhdpi │ │ │ └── splash.png │ │ │ ├── drawable │ │ │ ├── background.png │ │ │ └── launch_background.xml │ │ │ ├── mipmap-hdpi │ │ │ └── launcher_icon.png │ │ │ ├── mipmap-mdpi │ │ │ └── launcher_icon.png │ │ │ ├── mipmap-xhdpi │ │ │ └── launcher_icon.png │ │ │ ├── mipmap-xxhdpi │ │ │ └── launcher_icon.png │ │ │ ├── mipmap-xxxhdpi │ │ │ └── launcher_icon.png │ │ │ ├── values-night │ │ │ └── styles.xml │ │ │ └── values │ │ │ └── styles.xml │ │ └── profile │ │ └── AndroidManifest.xml ├── build.gradle ├── gradle.properties ├── gradle │ └── wrapper │ │ └── gradle-wrapper.properties └── settings.gradle ├── assets ├── files │ ├── request_parameters.json │ ├── service_providers.json │ ├── service_tokens.json │ └── service_vendors.json └── images │ ├── ai_avatar.png │ ├── logo-green.png │ ├── logo-white.png │ ├── open_ai.svg │ ├── open_ai_chat_gpt.svg │ └── open_ai_gpt_4.svg ├── img ├── Icon-App-1024x1024@1x.png ├── android-screen-dark.png ├── android-screen-light.png ├── app-store.svg ├── google-play-badge.png ├── ios-screen-dark.png ├── ios-screen-light.png └── testflight.png ├── ios ├── .gitignore ├── Flutter │ ├── AppFrameworkInfo.plist │ ├── Debug.xcconfig │ └── Release.xcconfig ├── Podfile ├── Podfile.lock ├── Runner.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ ├── IDEWorkspaceChecks.plist │ │ │ └── WorkspaceSettings.xcsettings │ └── xcshareddata │ │ └── xcschemes │ │ └── Runner.xcscheme ├── Runner.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ ├── IDEWorkspaceChecks.plist │ │ └── WorkspaceSettings.xcsettings └── Runner │ ├── AppDelegate.swift │ ├── Assets.xcassets │ ├── AppIcon.appiconset │ │ ├── Contents.json │ │ ├── Icon-App-1024x1024@1x.png │ │ ├── Icon-App-20x20@1x.png │ │ ├── Icon-App-20x20@2x.png │ │ ├── Icon-App-20x20@3x.png │ │ ├── Icon-App-29x29@1x.png │ │ ├── Icon-App-29x29@2x.png │ │ ├── Icon-App-29x29@3x.png │ │ ├── Icon-App-40x40@1x.png │ │ ├── Icon-App-40x40@2x.png │ │ ├── Icon-App-40x40@3x.png │ │ ├── Icon-App-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 │ ├── en.lproj │ └── InfoPlist.strings │ └── zh-Hans.lproj │ └── InfoPlist.strings ├── lib ├── app │ ├── app.dart │ ├── app_translation.dart │ ├── core │ │ ├── app │ │ │ ├── app_controller_mixin.dart │ │ │ ├── app_hive_keys.dart │ │ │ ├── app_http.dart │ │ │ ├── app_http_overrides.dart │ │ │ ├── app_manager.dart │ │ │ ├── app_model.dart │ │ │ ├── app_plugin.dart │ │ │ ├── app_plugin_manager.dart │ │ │ ├── app_progress_hud.dart │ │ │ ├── app_theme.dart │ │ │ ├── app_toast.dart │ │ │ ├── app_urls.dart │ │ │ └── app_view_mixin.dart │ │ ├── binding │ │ │ └── initial_binding.dart │ │ └── mixin │ │ │ └── refresh_mixin.dart │ ├── data │ │ ├── db │ │ │ ├── app_database.dart │ │ │ ├── app_uuid.dart │ │ │ ├── conversations_dao.dart │ │ │ ├── groups_dao.dart │ │ │ ├── messages_dao.dart │ │ │ ├── prompt_dao.dart │ │ │ ├── request_parameters_dao.dart │ │ │ ├── service_providers_dao.dart │ │ │ ├── service_tokens_dao.dart │ │ │ └── service_vendors_dao.dart │ │ └── models │ │ │ ├── conversation.dart │ │ │ ├── group.dart │ │ │ ├── message.dart │ │ │ ├── prompt.dart │ │ │ ├── request_parameter.dart │ │ │ ├── service_token.dart │ │ │ └── value_serializer.dart │ ├── modules │ │ ├── conversation │ │ │ ├── bindings │ │ │ │ └── conversation_binding.dart │ │ │ ├── controllers │ │ │ │ └── conversation_controller.dart │ │ │ └── views │ │ │ │ ├── conversation_view.dart │ │ │ │ └── services_item_view.dart │ │ ├── home │ │ │ ├── bindings │ │ │ │ └── home_binding.dart │ │ │ ├── controllers │ │ │ │ └── home_controller.dart │ │ │ └── views │ │ │ │ ├── home_bar.dart │ │ │ │ ├── home_body.dart │ │ │ │ ├── home_drawer.dart │ │ │ │ └── home_view.dart │ │ ├── new_prompt │ │ │ ├── bindings │ │ │ │ └── new_prompt_binding.dart │ │ │ ├── controllers │ │ │ │ └── new_prompt_controller.dart │ │ │ └── views │ │ │ │ └── new_prompt_view.dart │ │ ├── prompt │ │ │ ├── bindings │ │ │ │ └── prompt_binding.dart │ │ │ ├── controllers │ │ │ │ └── prompt_controller.dart │ │ │ └── views │ │ │ │ └── prompt_view.dart │ │ ├── service │ │ │ ├── bindings │ │ │ │ └── service_binding.dart │ │ │ ├── controllers │ │ │ │ └── service_controller.dart │ │ │ └── views │ │ │ │ ├── service_info.dart │ │ │ │ ├── service_token_item_view.dart │ │ │ │ └── service_view.dart │ │ ├── settings │ │ │ ├── bindings │ │ │ │ └── settings_binding.dart │ │ │ ├── controllers │ │ │ │ └── settings_controller.dart │ │ │ └── views │ │ │ │ └── settings_view.dart │ │ └── vendor │ │ │ ├── bindings │ │ │ └── vendor_binding.dart │ │ │ ├── controllers │ │ │ └── vendor_controller.dart │ │ │ └── views │ │ │ └── vendor_view.dart │ ├── providers │ │ ├── open_ai │ │ │ ├── chat_gpt.dart │ │ │ ├── chat_gpt_3.dart │ │ │ ├── chat_gpt_4.dart │ │ │ ├── chat_gpt_api.dart │ │ │ └── chat_gpt_model.dart │ │ ├── service_provider.dart │ │ ├── service_provider_manager.dart │ │ └── service_vendor.dart │ ├── routes │ │ ├── app_pages.dart │ │ └── app_routes.dart │ └── views │ │ ├── app_cell.dart │ │ ├── app_section.dart │ │ ├── chat_avatar.dart │ │ ├── chat_base_item_view.dart │ │ ├── chat_input.dart │ │ ├── chat_loading_item_view.dart │ │ ├── chat_text_receive_item_view.dart │ │ ├── chat_view.dart │ │ ├── code_element_builder.dart │ │ └── code_highlight_view.dart ├── flavors │ ├── build_config.dart │ ├── env_config.dart │ └── environment.dart └── main.dart ├── linux ├── .gitignore ├── CMakeLists.txt ├── flutter │ ├── CMakeLists.txt │ ├── generated_plugin_registrant.cc │ ├── generated_plugin_registrant.h │ └── generated_plugins.cmake ├── main.cc ├── my_application.cc └── my_application.h ├── macos ├── .gitignore ├── Flutter │ ├── Flutter-Debug.xcconfig │ ├── Flutter-Release.xcconfig │ └── GeneratedPluginRegistrant.swift ├── Podfile ├── Runner.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ └── xcshareddata │ │ │ └── IDEWorkspaceChecks.plist │ └── xcshareddata │ │ └── xcschemes │ │ └── Runner.xcscheme ├── Runner.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist ├── Runner │ ├── AppDelegate.swift │ ├── Assets.xcassets │ │ └── AppIcon.appiconset │ │ │ ├── Contents.json │ │ │ ├── app_icon_1024.png │ │ │ ├── app_icon_128.png │ │ │ ├── app_icon_16.png │ │ │ ├── app_icon_256.png │ │ │ ├── app_icon_32.png │ │ │ ├── app_icon_512.png │ │ │ └── app_icon_64.png │ ├── Base.lproj │ │ └── MainMenu.xib │ ├── Configs │ │ ├── AppInfo.xcconfig │ │ ├── Debug.xcconfig │ │ ├── Release.xcconfig │ │ └── Warnings.xcconfig │ ├── DebugProfile.entitlements │ ├── Info.plist │ ├── MainFlutterWindow.swift │ └── Release.entitlements └── firebase_app_id_file.json ├── pubspec.lock ├── pubspec.yaml ├── test └── widget_test.dart ├── web ├── favicon.png ├── icons │ ├── Icon-192.png │ ├── Icon-512.png │ ├── Icon-maskable-192.png │ └── Icon-maskable-512.png ├── index.html ├── manifest.json └── splash │ ├── img │ ├── dark-1x.png │ ├── dark-2x.png │ ├── dark-3x.png │ ├── dark-4x.png │ ├── light-1x.png │ ├── light-2x.png │ ├── light-3x.png │ └── light-4x.png │ ├── splash.js │ └── style.css └── windows ├── .gitignore ├── CMakeLists.txt ├── flutter ├── CMakeLists.txt ├── generated_plugin_registrant.cc ├── generated_plugin_registrant.h └── generated_plugins.cmake └── runner ├── CMakeLists.txt ├── Runner.rc ├── flutter_window.cpp ├── flutter_window.h ├── main.cpp ├── resource.h ├── resources └── app_icon.ico ├── runner.exe.manifest ├── utils.cpp ├── utils.h ├── win32_window.cpp └── win32_window.h /.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | migrate_working_dir/ 12 | 13 | # IntelliJ related 14 | *.iml 15 | *.ipr 16 | *.iws 17 | .idea/ 18 | 19 | # The .vscode folder contains launch configuration and tasks you configure in 20 | # VS Code which you may wish to be included in version control, so this line 21 | # is commented out by default. 22 | .vscode/ 23 | 24 | # Flutter/Dart/Pub related 25 | **/doc/api/ 26 | **/ios/Flutter/.last_build_id 27 | .dart_tool/ 28 | .flutter-plugins 29 | .flutter-plugins-dependencies 30 | .packages 31 | .pub-cache/ 32 | .pub/ 33 | /build/ 34 | main_dev.dart 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 | -------------------------------------------------------------------------------- /.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. 5 | 6 | version: 7 | revision: 12cb4eb7a009f52b347b62ade7cb4854b926af72 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: 12cb4eb7a009f52b347b62ade7cb4854b926af72 17 | base_revision: 12cb4eb7a009f52b347b62ade7cb4854b926af72 18 | - platform: android 19 | create_revision: 12cb4eb7a009f52b347b62ade7cb4854b926af72 20 | base_revision: 12cb4eb7a009f52b347b62ade7cb4854b926af72 21 | - platform: ios 22 | create_revision: 12cb4eb7a009f52b347b62ade7cb4854b926af72 23 | base_revision: 12cb4eb7a009f52b347b62ade7cb4854b926af72 24 | - platform: linux 25 | create_revision: 12cb4eb7a009f52b347b62ade7cb4854b926af72 26 | base_revision: 12cb4eb7a009f52b347b62ade7cb4854b926af72 27 | - platform: macos 28 | create_revision: 12cb4eb7a009f52b347b62ade7cb4854b926af72 29 | base_revision: 12cb4eb7a009f52b347b62ade7cb4854b926af72 30 | - platform: web 31 | create_revision: 12cb4eb7a009f52b347b62ade7cb4854b926af72 32 | base_revision: 12cb4eb7a009f52b347b62ade7cb4854b926af72 33 | - platform: windows 34 | create_revision: 12cb4eb7a009f52b347b62ade7cb4854b926af72 35 | base_revision: 12cb4eb7a009f52b347b62ade7cb4854b926af72 36 | 37 | # User provided section 38 | 39 | # List of Local paths (relative to this file) that should be 40 | # ignored by the migrate tool. 41 | # 42 | # Files that are not part of the templates will be ignored by default. 43 | unmanaged_files: 44 | - 'lib/main.dart' 45 | - 'ios/Runner.xcodeproj/project.pbxproj' 46 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Chat Answer 2 | 3 |
4 | English | 中文 5 |
6 |
7 | 8 | 9 | Tests Passing 10 | 11 | 12 | GitHub Contributors 13 | 14 | 15 | Issues 16 | 17 | 18 | GitHub pull requests 19 | 20 | 21 | Chat Answer is an open source app based on ChatGPT API. 22 | 23 | ## iOS App 24 | 25 | ### Download 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | ### Screen 34 | ![ios-screen-dark](img/ios-screen-dark.png)![ios-screen-light](img/ios-screen-light.png) 35 | 36 | ## Android App 37 | 38 | ### Download 39 | #### Google Play 40 | 41 | 42 | 43 | 44 | #### GitHub 45 | 1. GitHub Latest Release 46 | 2. Download "app-release.apk". 47 | 48 | ### Screen 49 | ![android-screen-dark](img/android-screen-dark.png)![android-screen-light](img/android-screen-light.png) 50 | 51 | ## Roadmap 52 | 53 | - [x] ChatGPT Service 54 | - [x] ChatGPT API Key & API URL setting 55 | - [x] Conversations local storage 56 | - [x] Add or delete conversations 57 | - [x] Messages local storage 58 | - [x] Support markdown 59 | - [x] Support code block highlight 60 | - [x] Support conversation settings 61 | - [x] Support quote message 62 | - [x] Support add prompts 63 | - [x] Support system message 64 | - [ ] Support delete message 65 | - [ ] Add App level settings 66 | - [ ] Support for code replication 67 | - [ ] Support more chat scenarios 68 | - [ ] Support chat stream output 69 | - [ ] Add App level prompts manager 70 | - [ ] Add prompts quick send button on chat page 71 | 72 | ## License 73 | 74 | [GNU General Public License v3.0](./LICENSE) -------------------------------------------------------------------------------- /README.zh-Hans.md: -------------------------------------------------------------------------------- 1 | # 小答 2 | 3 |
4 | English | 中文 5 |
6 |
7 | 8 | 9 | Tests Passing 10 | 11 | 12 | GitHub Contributors 13 | 14 | 15 | Issues 16 | 17 | 18 | GitHub pull requests 19 | 20 | 21 | 小答是一款基于 ChatGPT API 的开源客户端。 22 | 23 | ## iOS App 24 | 25 | ### 下载 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | ### 预览 34 | ![ios-screen-dark](img/ios-screen-dark.png) 35 | ![ios-screen-light](img/ios-screen-light.png) 36 | 37 | ## Android App 38 | 39 | ### 下载 40 | #### Google Play 41 | 42 | 43 | 44 | 45 | #### GitHub 46 | 1. 点击 GitHub Latest Release 47 | 2. 点击页面中的 "app-release.apk" 开始下载。 48 | 49 | ### 预览 50 | ![android-screen-dark](img/android-screen-dark.png)![android-screen-light](img/android-screen-light.png) 51 | 52 | ## Roadmap 53 | 54 | - [x] ChatGPT 服务 55 | - [x] ChatGPT API Key 和 API URL 设置 56 | - [x] 会话本地存储 57 | - [x] 新增及删除会话 58 | - [x] 消息本地存储 59 | - [x] 消息支持 Markdown 60 | - [x] 支持代码块高亮显示 61 | - [x] 支持会话设置 62 | - [x] 支持引用消息设置上下文 63 | - [x] 支持系统角色设置 64 | - [x] 支持系统消息 65 | - [ ] 支持删除消息 66 | - [ ] 应用设置,支持语言、外观切换 67 | - [ ] 代码块的复制按钮 68 | - [ ] 支持更多聊天场景 69 | 70 | ## License 71 | 72 | [GNU General Public License v3.0](./LICENSE) -------------------------------------------------------------------------------- /analysis_options.yaml: -------------------------------------------------------------------------------- 1 | include: package:flutter_lints/flutter.yaml 2 | linter: 3 | rules: 4 | 5 | -------------------------------------------------------------------------------- /android/.gitignore: -------------------------------------------------------------------------------- 1 | gradle-wrapper.jar 2 | /.gradle 3 | /captures/ 4 | /gradlew 5 | /gradlew.bat 6 | /local.properties 7 | GeneratedPluginRegistrant.java 8 | 9 | # Remember to never publicly share your keystore. 10 | # See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app 11 | key.properties 12 | **/*.keystore 13 | **/*.jks 14 | -------------------------------------------------------------------------------- /android/app/build.gradle: -------------------------------------------------------------------------------- 1 | def localProperties = new Properties() 2 | def localPropertiesFile = rootProject.file('local.properties') 3 | if (localPropertiesFile.exists()) { 4 | localPropertiesFile.withReader('UTF-8') { reader -> 5 | localProperties.load(reader) 6 | } 7 | } 8 | 9 | def flutterRoot = localProperties.getProperty('flutter.sdk') 10 | if (flutterRoot == null) { 11 | throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") 12 | } 13 | 14 | def flutterVersionCode = localProperties.getProperty('flutter.versionCode') 15 | if (flutterVersionCode == null) { 16 | flutterVersionCode = '1' 17 | } 18 | 19 | def flutterVersionName = localProperties.getProperty('flutter.versionName') 20 | if (flutterVersionName == null) { 21 | flutterVersionName = '1.0' 22 | } 23 | 24 | apply plugin: 'com.android.application' 25 | apply plugin: 'kotlin-android' 26 | apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" 27 | 28 | android { 29 | signingConfigs { 30 | release { 31 | storeFile file('/Users/zhangminchao/Documents/Google Play/upload-keystore.jks') 32 | storePassword '8Paws.com' 33 | keyAlias 'upload' 34 | keyPassword '8Paws.com' 35 | } 36 | } 37 | compileSdkVersion flutter.compileSdkVersion 38 | ndkVersion flutter.ndkVersion 39 | 40 | compileOptions { 41 | sourceCompatibility JavaVersion.VERSION_1_8 42 | targetCompatibility JavaVersion.VERSION_1_8 43 | } 44 | 45 | defaultConfig { 46 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). 47 | applicationId "com.bapaws.answer" 48 | // You can update the following values to match your application needs. 49 | // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration. 50 | minSdkVersion flutter.minSdkVersion 51 | targetSdkVersion flutter.targetSdkVersion 52 | versionCode flutterVersionCode.toInteger() 53 | versionName flutterVersionName 54 | signingConfig signingConfigs.release 55 | } 56 | 57 | buildTypes { 58 | release { 59 | // TODO: Add your own signing config for the release build. 60 | // Signing with the debug keys for now, so `flutter run --release` works. 61 | signingConfig signingConfigs.release 62 | } 63 | } 64 | } 65 | 66 | flutter { 67 | source '../..' 68 | } 69 | -------------------------------------------------------------------------------- /android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 8 | 16 | 20 | 24 | 25 | 26 | 27 | 28 | 29 | 31 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /android/app/src/main/java/com/bapaws/answer/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.bapaws.answer; 2 | 3 | import io.flutter.embedding.android.FlutterActivity; 4 | 5 | public class MainActivity extends FlutterActivity { 6 | } 7 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-hdpi/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bapaws/answer/6c65fced23f96186f330d7704ff5b49107f83a83/android/app/src/main/res/drawable-hdpi/splash.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-mdpi/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bapaws/answer/6c65fced23f96186f330d7704ff5b49107f83a83/android/app/src/main/res/drawable-mdpi/splash.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-v21/background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bapaws/answer/6c65fced23f96186f330d7704ff5b49107f83a83/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 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-xhdpi/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bapaws/answer/6c65fced23f96186f330d7704ff5b49107f83a83/android/app/src/main/res/drawable-xhdpi/splash.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-xxhdpi/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bapaws/answer/6c65fced23f96186f330d7704ff5b49107f83a83/android/app/src/main/res/drawable-xxhdpi/splash.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-xxxhdpi/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bapaws/answer/6c65fced23f96186f330d7704ff5b49107f83a83/android/app/src/main/res/drawable-xxxhdpi/splash.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable/background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bapaws/answer/6c65fced23f96186f330d7704ff5b49107f83a83/android/app/src/main/res/drawable/background.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-hdpi/launcher_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bapaws/answer/6c65fced23f96186f330d7704ff5b49107f83a83/android/app/src/main/res/mipmap-hdpi/launcher_icon.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-mdpi/launcher_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bapaws/answer/6c65fced23f96186f330d7704ff5b49107f83a83/android/app/src/main/res/mipmap-mdpi/launcher_icon.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xhdpi/launcher_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bapaws/answer/6c65fced23f96186f330d7704ff5b49107f83a83/android/app/src/main/res/mipmap-xhdpi/launcher_icon.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxhdpi/launcher_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bapaws/answer/6c65fced23f96186f330d7704ff5b49107f83a83/android/app/src/main/res/mipmap-xxhdpi/launcher_icon.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxxhdpi/launcher_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bapaws/answer/6c65fced23f96186f330d7704ff5b49107f83a83/android/app/src/main/res/mipmap-xxxhdpi/launcher_icon.png -------------------------------------------------------------------------------- /android/app/src/main/res/values-night/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 13 | 19 | 22 | 23 | -------------------------------------------------------------------------------- /android/app/src/profile/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | ext.kotlin_version = '1.7.10' 3 | repositories { 4 | maven { url 'https://maven.aliyun.com/repository/public/' } 5 | maven { url 'https://maven.aliyun.com/repository/central' } 6 | maven { url 'https://maven.aliyun.com/repository/google' } 7 | maven { url 'https://maven.aliyun.com/repository/gradle-plugin' } 8 | google() 9 | mavenCentral() 10 | } 11 | 12 | dependencies { 13 | classpath 'com.android.tools.build:gradle:7.2.0' 14 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 15 | } 16 | } 17 | 18 | allprojects { 19 | repositories { 20 | maven { url 'https://maven.aliyun.com/repository/public/' } 21 | maven { url 'https://maven.aliyun.com/repository/central' } 22 | maven { url 'https://maven.aliyun.com/repository/google' } 23 | maven { url 'https://maven.aliyun.com/repository/gradle-plugin' } 24 | google() 25 | mavenCentral() 26 | } 27 | } 28 | 29 | rootProject.buildDir = '../build' 30 | subprojects { 31 | project.buildDir = "${rootProject.buildDir}/${project.name}" 32 | } 33 | subprojects { 34 | project.evaluationDependsOn(':app') 35 | } 36 | 37 | task clean(type: Delete) { 38 | delete rootProject.buildDir 39 | } 40 | -------------------------------------------------------------------------------- /android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx1536M 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-7.5-all.zip 6 | -------------------------------------------------------------------------------- /android/settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | 3 | def localPropertiesFile = new File(rootProject.projectDir, "local.properties") 4 | def properties = new Properties() 5 | 6 | assert localPropertiesFile.exists() 7 | localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) } 8 | 9 | def flutterSdkPath = properties.getProperty("flutter.sdk") 10 | assert flutterSdkPath != null, "flutter.sdk not set in local.properties" 11 | apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle" 12 | -------------------------------------------------------------------------------- /assets/files/request_parameters.json: -------------------------------------------------------------------------------- 1 | W3sia2V5IjoibWF4X3Rva2VucyIsInZhbHVlIjoiODAwIiwidmFsdWVfdHlwZSI6MCwidmVuZG9yX2lkIjoib3Blbl9haV9jaGF0X2dwdCJ9LHsia2V5IjoidGltZW91dCIsInZhbHVlIjoiNjAiLCJ2YWx1ZV90eXBlIjowLCJ2ZW5kb3JfaWQiOiJvcGVuX2FpX2NoYXRfZ3B0In1d -------------------------------------------------------------------------------- /assets/files/service_providers.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": "open_ai_chat_gpt", 4 | "name": "Chat GPT", 5 | "avatar": "assets/images/open_ai_chat_gpt.svg", 6 | "group_id": 0, 7 | "official_url": "https://api.openai.com/v1/chat/completions", 8 | "api_url": "", 9 | "help": "Help", 10 | "help_url": "Help", 11 | "tokens": [ 12 | { 13 | "name": "API Key", 14 | "service_provider_id": "open_ai_chat_gpt" 15 | } 16 | ] 17 | }, 18 | { 19 | "id": "open_ai_chat_gpt_secret_api", 20 | "name": "Chat GPT Secret API", 21 | "avatar": "assets/images/open_ai_chat_gpt.svg", 22 | "group_id": 0, 23 | "official_url": "https://api.openai.com/v1/chat/completions", 24 | "api_url": "https://chat.openai.com/chat", 25 | "help": "Help", 26 | "help_url": "Help", 27 | "tokens": [ 28 | { 29 | "name": "Session Token", 30 | "service_provider_id": "open_ai_chat_gpt_secret_api" 31 | } 32 | ] 33 | } 34 | ] -------------------------------------------------------------------------------- /assets/files/service_tokens.json: -------------------------------------------------------------------------------- 1 | W3siaWQiOiJjaGF0X2dwdF9hcGlfa2V5IiwibmFtZSI6IkFQSSBLZXkiLCJzZXJ2aWNlX3Byb3ZpZGVyX2lkIjoib3Blbl9haV9jaGF0X2dwdCJ9XQ== -------------------------------------------------------------------------------- /assets/files/service_vendors.json: -------------------------------------------------------------------------------- 1 | W3siaWQiOiJvcGVuX2FpX2NoYXRfZ3B0IiwibmFtZSI6Ik9wZW5BSSIsImF2YXRhciI6ImFzc2V0cy9pbWFnZXMvb3Blbl9haS5zdmciLCJvZmZpY2lhbF91cmwiOiJodHRwczovL29wZW5haS5jb20vIiwiYXBpX3VybCI6Imh0dHBzOi8vYXBpLm9wZW5haS5jb20vdjEvY2hhdC9jb21wbGV0aW9ucyIsImhlbHAiOiJjaGF0X2dwdF9oZWxwIiwiaGVscF91cmwiOiJodHRwczovL3d3dy5iYXBhd3MuY29tL2Fuc3dlci9oZWxwL2NoYXRfZ3B0Lmh0bWwiLCJoZWxsbyI6Im9wZW5fYWlfaGVsbG8ifV0= -------------------------------------------------------------------------------- /assets/images/ai_avatar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bapaws/answer/6c65fced23f96186f330d7704ff5b49107f83a83/assets/images/ai_avatar.png -------------------------------------------------------------------------------- /assets/images/logo-green.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bapaws/answer/6c65fced23f96186f330d7704ff5b49107f83a83/assets/images/logo-green.png -------------------------------------------------------------------------------- /assets/images/logo-white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bapaws/answer/6c65fced23f96186f330d7704ff5b49107f83a83/assets/images/logo-white.png -------------------------------------------------------------------------------- /assets/images/open_ai.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/images/open_ai_gpt_4.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /img/Icon-App-1024x1024@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bapaws/answer/6c65fced23f96186f330d7704ff5b49107f83a83/img/Icon-App-1024x1024@1x.png -------------------------------------------------------------------------------- /img/android-screen-dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bapaws/answer/6c65fced23f96186f330d7704ff5b49107f83a83/img/android-screen-dark.png -------------------------------------------------------------------------------- /img/android-screen-light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bapaws/answer/6c65fced23f96186f330d7704ff5b49107f83a83/img/android-screen-light.png -------------------------------------------------------------------------------- /img/google-play-badge.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bapaws/answer/6c65fced23f96186f330d7704ff5b49107f83a83/img/google-play-badge.png -------------------------------------------------------------------------------- /img/ios-screen-dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bapaws/answer/6c65fced23f96186f330d7704ff5b49107f83a83/img/ios-screen-dark.png -------------------------------------------------------------------------------- /img/ios-screen-light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bapaws/answer/6c65fced23f96186f330d7704ff5b49107f83a83/img/ios-screen-light.png -------------------------------------------------------------------------------- /img/testflight.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bapaws/answer/6c65fced23f96186f330d7704ff5b49107f83a83/img/testflight.png -------------------------------------------------------------------------------- /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 | 11.0 25 | 26 | 27 | -------------------------------------------------------------------------------- /ios/Flutter/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /ios/Flutter/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /ios/Podfile: -------------------------------------------------------------------------------- 1 | # Uncomment this line to define a global platform for your project 2 | # platform :ios, '11.0' 3 | 4 | # CocoaPods analytics sends network stats synchronously affecting flutter build latency. 5 | ENV['COCOAPODS_DISABLE_STATS'] = 'true' 6 | 7 | project 'Runner', { 8 | 'Debug' => :debug, 9 | 'Profile' => :release, 10 | 'Release' => :release, 11 | } 12 | 13 | def flutter_root 14 | generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__) 15 | unless File.exist?(generated_xcode_build_settings_path) 16 | raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first" 17 | end 18 | 19 | File.foreach(generated_xcode_build_settings_path) do |line| 20 | matches = line.match(/FLUTTER_ROOT\=(.*)/) 21 | return matches[1].strip if matches 22 | end 23 | raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get" 24 | end 25 | 26 | require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) 27 | 28 | flutter_ios_podfile_setup 29 | 30 | target 'Runner' do 31 | use_frameworks! 32 | use_modular_headers! 33 | 34 | flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) 35 | end 36 | 37 | post_install do |installer| 38 | installer.pods_project.targets.each do |target| 39 | flutter_additional_ios_build_settings(target) 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /ios/Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - Flutter (1.0.0) 3 | - flutter_mailer (0.0.1): 4 | - Flutter 5 | - flutter_native_splash (0.0.1): 6 | - Flutter 7 | - fluttertoast (0.0.2): 8 | - Flutter 9 | - Toast 10 | - FMDB (2.7.5): 11 | - FMDB/standard (= 2.7.5) 12 | - FMDB/standard (2.7.5) 13 | - in_app_review (0.2.0): 14 | - Flutter 15 | - path_provider_foundation (0.0.1): 16 | - Flutter 17 | - FlutterMacOS 18 | - share_plus (0.0.1): 19 | - Flutter 20 | - shared_preferences_foundation (0.0.1): 21 | - Flutter 22 | - FlutterMacOS 23 | - sqflite (0.0.2): 24 | - Flutter 25 | - FMDB (>= 2.7.5) 26 | - Toast (4.0.0) 27 | - url_launcher_ios (0.0.1): 28 | - Flutter 29 | 30 | DEPENDENCIES: 31 | - Flutter (from `Flutter`) 32 | - flutter_mailer (from `.symlinks/plugins/flutter_mailer/ios`) 33 | - flutter_native_splash (from `.symlinks/plugins/flutter_native_splash/ios`) 34 | - fluttertoast (from `.symlinks/plugins/fluttertoast/ios`) 35 | - in_app_review (from `.symlinks/plugins/in_app_review/ios`) 36 | - path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/ios`) 37 | - share_plus (from `.symlinks/plugins/share_plus/ios`) 38 | - shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/ios`) 39 | - sqflite (from `.symlinks/plugins/sqflite/ios`) 40 | - url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`) 41 | 42 | SPEC REPOS: 43 | trunk: 44 | - FMDB 45 | - Toast 46 | 47 | EXTERNAL SOURCES: 48 | Flutter: 49 | :path: Flutter 50 | flutter_mailer: 51 | :path: ".symlinks/plugins/flutter_mailer/ios" 52 | flutter_native_splash: 53 | :path: ".symlinks/plugins/flutter_native_splash/ios" 54 | fluttertoast: 55 | :path: ".symlinks/plugins/fluttertoast/ios" 56 | in_app_review: 57 | :path: ".symlinks/plugins/in_app_review/ios" 58 | path_provider_foundation: 59 | :path: ".symlinks/plugins/path_provider_foundation/ios" 60 | share_plus: 61 | :path: ".symlinks/plugins/share_plus/ios" 62 | shared_preferences_foundation: 63 | :path: ".symlinks/plugins/shared_preferences_foundation/ios" 64 | sqflite: 65 | :path: ".symlinks/plugins/sqflite/ios" 66 | url_launcher_ios: 67 | :path: ".symlinks/plugins/url_launcher_ios/ios" 68 | 69 | SPEC CHECKSUMS: 70 | Flutter: f04841e97a9d0b0a8025694d0796dd46242b2854 71 | flutter_mailer: 2ef5a67087bc8c6c4cefd04a178bf1ae2c94cd83 72 | flutter_native_splash: 52501b97d1c0a5f898d687f1646226c1f93c56ef 73 | fluttertoast: eb263d302cc92e04176c053d2385237e9f43fad0 74 | FMDB: 2ce00b547f966261cd18927a3ddb07cb6f3db82a 75 | in_app_review: 318597b3a06c22bb46dc454d56828c85f444f99d 76 | path_provider_foundation: c68054786f1b4f3343858c1e1d0caaded73f0be9 77 | share_plus: 056a1e8ac890df3e33cb503afffaf1e9b4fbae68 78 | shared_preferences_foundation: 986fc17f3d3251412d18b0265f9c64113a8c2472 79 | sqflite: 6d358c025f5b867b29ed92fc697fd34924e11904 80 | Toast: 91b396c56ee72a5790816f40d3a94dd357abc196 81 | url_launcher_ios: 08a3dfac5fb39e8759aeb0abbd5d9480f30fc8b4 82 | 83 | PODFILE CHECKSUM: ef19549a9bc3046e7bb7d2fab4d021637c0c58a3 84 | 85 | COCOAPODS: 1.11.3 86 | -------------------------------------------------------------------------------- /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 | 41 | 42 | 52 | 54 | 60 | 61 | 62 | 63 | 69 | 71 | 77 | 78 | 79 | 80 | 82 | 83 | 86 | 87 | 88 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Runner/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import Flutter 3 | 4 | @UIApplicationMain 5 | @objc class AppDelegate: FlutterAppDelegate { 6 | override func application( 7 | _ application: UIApplication, 8 | didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? 9 | ) -> Bool { 10 | GeneratedPluginRegistrant.register(with: self) 11 | return super.application(application, didFinishLaunchingWithOptions: launchOptions) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "20x20", 5 | "idiom" : "iphone", 6 | "filename" : "Icon-App-20x20@2x.png", 7 | "scale" : "2x" 8 | }, 9 | { 10 | "size" : "20x20", 11 | "idiom" : "iphone", 12 | "filename" : "Icon-App-20x20@3x.png", 13 | "scale" : "3x" 14 | }, 15 | { 16 | "size" : "29x29", 17 | "idiom" : "iphone", 18 | "filename" : "Icon-App-29x29@1x.png", 19 | "scale" : "1x" 20 | }, 21 | { 22 | "size" : "29x29", 23 | "idiom" : "iphone", 24 | "filename" : "Icon-App-29x29@2x.png", 25 | "scale" : "2x" 26 | }, 27 | { 28 | "size" : "29x29", 29 | "idiom" : "iphone", 30 | "filename" : "Icon-App-29x29@3x.png", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "size" : "40x40", 35 | "idiom" : "iphone", 36 | "filename" : "Icon-App-40x40@2x.png", 37 | "scale" : "2x" 38 | }, 39 | { 40 | "size" : "40x40", 41 | "idiom" : "iphone", 42 | "filename" : "Icon-App-40x40@3x.png", 43 | "scale" : "3x" 44 | }, 45 | { 46 | "size" : "60x60", 47 | "idiom" : "iphone", 48 | "filename" : "Icon-App-60x60@2x.png", 49 | "scale" : "2x" 50 | }, 51 | { 52 | "size" : "60x60", 53 | "idiom" : "iphone", 54 | "filename" : "Icon-App-60x60@3x.png", 55 | "scale" : "3x" 56 | }, 57 | { 58 | "size" : "20x20", 59 | "idiom" : "ipad", 60 | "filename" : "Icon-App-20x20@1x.png", 61 | "scale" : "1x" 62 | }, 63 | { 64 | "size" : "20x20", 65 | "idiom" : "ipad", 66 | "filename" : "Icon-App-20x20@2x.png", 67 | "scale" : "2x" 68 | }, 69 | { 70 | "size" : "29x29", 71 | "idiom" : "ipad", 72 | "filename" : "Icon-App-29x29@1x.png", 73 | "scale" : "1x" 74 | }, 75 | { 76 | "size" : "29x29", 77 | "idiom" : "ipad", 78 | "filename" : "Icon-App-29x29@2x.png", 79 | "scale" : "2x" 80 | }, 81 | { 82 | "size" : "40x40", 83 | "idiom" : "ipad", 84 | "filename" : "Icon-App-40x40@1x.png", 85 | "scale" : "1x" 86 | }, 87 | { 88 | "size" : "40x40", 89 | "idiom" : "ipad", 90 | "filename" : "Icon-App-40x40@2x.png", 91 | "scale" : "2x" 92 | }, 93 | { 94 | "size" : "76x76", 95 | "idiom" : "ipad", 96 | "filename" : "Icon-App-76x76@1x.png", 97 | "scale" : "1x" 98 | }, 99 | { 100 | "size" : "76x76", 101 | "idiom" : "ipad", 102 | "filename" : "Icon-App-76x76@2x.png", 103 | "scale" : "2x" 104 | }, 105 | { 106 | "size" : "83.5x83.5", 107 | "idiom" : "ipad", 108 | "filename" : "Icon-App-83.5x83.5@2x.png", 109 | "scale" : "2x" 110 | }, 111 | { 112 | "size" : "1024x1024", 113 | "idiom" : "ios-marketing", 114 | "filename" : "Icon-App-1024x1024@1x.png", 115 | "scale" : "1x" 116 | } 117 | ], 118 | "info" : { 119 | "version" : 1, 120 | "author" : "xcode" 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bapaws/answer/6c65fced23f96186f330d7704ff5b49107f83a83/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/bapaws/answer/6c65fced23f96186f330d7704ff5b49107f83a83/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/bapaws/answer/6c65fced23f96186f330d7704ff5b49107f83a83/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/bapaws/answer/6c65fced23f96186f330d7704ff5b49107f83a83/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/bapaws/answer/6c65fced23f96186f330d7704ff5b49107f83a83/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/bapaws/answer/6c65fced23f96186f330d7704ff5b49107f83a83/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/bapaws/answer/6c65fced23f96186f330d7704ff5b49107f83a83/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/bapaws/answer/6c65fced23f96186f330d7704ff5b49107f83a83/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/bapaws/answer/6c65fced23f96186f330d7704ff5b49107f83a83/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/bapaws/answer/6c65fced23f96186f330d7704ff5b49107f83a83/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/bapaws/answer/6c65fced23f96186f330d7704ff5b49107f83a83/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/bapaws/answer/6c65fced23f96186f330d7704ff5b49107f83a83/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/bapaws/answer/6c65fced23f96186f330d7704ff5b49107f83a83/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/bapaws/answer/6c65fced23f96186f330d7704ff5b49107f83a83/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/bapaws/answer/6c65fced23f96186f330d7704ff5b49107f83a83/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/bapaws/answer/6c65fced23f96186f330d7704ff5b49107f83a83/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/bapaws/answer/6c65fced23f96186f330d7704ff5b49107f83a83/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/bapaws/answer/6c65fced23f96186f330d7704ff5b49107f83a83/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/bapaws/answer/6c65fced23f96186f330d7704ff5b49107f83a83/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/bapaws/answer/6c65fced23f96186f330d7704ff5b49107f83a83/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/bapaws/answer/6c65fced23f96186f330d7704ff5b49107f83a83/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/bapaws/answer/6c65fced23f96186f330d7704ff5b49107f83a83/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/bapaws/answer/6c65fced23f96186f330d7704ff5b49107f83a83/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bapaws/answer/6c65fced23f96186f330d7704ff5b49107f83a83/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bapaws/answer/6c65fced23f96186f330d7704ff5b49107f83a83/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 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /ios/Runner/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleDisplayName 8 | Answer 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | Answer 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 | UIViewControllerBasedStatusBarAppearance 45 | 46 | CADisableMinimumFrameDurationOnPhone 47 | 48 | UIApplicationSupportsIndirectInputEvents 49 | 50 | UIStatusBarHidden 51 | 52 | ITSAppUsesNonExemptEncryption 53 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /ios/Runner/Runner-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | #import "GeneratedPluginRegistrant.h" 2 | -------------------------------------------------------------------------------- /ios/Runner/en.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- 1 | /* 2 | InfoPlist.strings 3 | Runner 4 | 5 | Created by 张敏超 on 2023/3/12. 6 | 7 | */ 8 | 9 | "CFBundleDisplayName" = "Answer"; 10 | "CFBundleName" = "Chat Answer"; 11 | -------------------------------------------------------------------------------- /ios/Runner/zh-Hans.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- 1 | /* 2 | InfoPlist.strings 3 | Runner 4 | 5 | Created by 张敏超 on 2023/3/12. 6 | 7 | */ 8 | 9 | "CFBundleDisplayName" = "小答"; 10 | "CFBundleName" = "小答"; 11 | -------------------------------------------------------------------------------- /lib/app/app.dart: -------------------------------------------------------------------------------- 1 | import 'package:answer/app/core/app/app_theme.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:get/get.dart'; 4 | 5 | import 'app_translation.dart'; 6 | import 'core/app/app_manager.dart'; 7 | import 'core/app/app_progress_hud.dart'; 8 | import 'core/binding/initial_binding.dart'; 9 | import 'routes/app_pages.dart'; 10 | 11 | class App extends StatefulWidget { 12 | const App({Key? key}) : super(key: key); 13 | 14 | @override 15 | State createState() => _AppState(); 16 | } 17 | 18 | class _AppState extends State { 19 | final AppTheme theme = AppTheme(); 20 | 21 | @override 22 | void initState() { 23 | super.initState(); 24 | } 25 | 26 | @override 27 | Widget build(BuildContext context) { 28 | return GetMaterialApp( 29 | theme: theme.light, 30 | darkTheme: theme.dark, 31 | themeMode: AppManager.to.themeMode, 32 | initialRoute: AppPages.initial, 33 | initialBinding: InitialBinding(), 34 | getPages: AppPages.routes, 35 | fallbackLocale: const Locale('en', 'US'), 36 | locale: Get.deviceLocale ?? const Locale('en', 'US'), 37 | // locale: AppManager.to.locale ?? const Locale('zh', 'CN'), 38 | translations: AppTranslation(), 39 | builder: AppProgressHud.init(), 40 | debugShowCheckedModeBanner: false, 41 | ); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /lib/app/core/app/app_controller_mixin.dart: -------------------------------------------------------------------------------- 1 | import 'package:get/get.dart'; 2 | 3 | mixin AppControllerMixin on GetxController { 4 | @override 5 | void onClose() { 6 | super.onClose(); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /lib/app/core/app/app_hive_keys.dart: -------------------------------------------------------------------------------- 1 | abstract class AppHiveKeys { 2 | static const fontSize = '_fontSize'; 3 | static const openAIApiKey = '_openAIApiKey'; 4 | static const plugin = '_pluginKey'; 5 | static const currentConversationIndex = '_currentConversationIndex'; 6 | static const currentGroupIndex = '_currentGroupIndex'; 7 | static const serviceProviderIsSendWelcome = '_serviceProviderIsSendWelcome_'; 8 | static const serviceProviderIsSendHello = '_serviceProviderIsSendHello_'; 9 | static const conversationPromptId = '_conversationPromptId_'; 10 | } 11 | -------------------------------------------------------------------------------- /lib/app/core/app/app_http.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | import 'package:dio/dio.dart'; 4 | import 'package:flutter/foundation.dart'; 5 | import 'package:get/get.dart' hide Response; 6 | import 'package:pretty_dio_logger/pretty_dio_logger.dart'; 7 | 8 | class AppHttp { 9 | late final Dio _dio; 10 | 11 | bool _lock = false; 12 | 13 | static final AppHttp instance = AppHttp._internal(); 14 | AppHttp._internal(); 15 | 16 | static Future instantiate({ 17 | HttpOverrides? httpOverrides, 18 | }) async { 19 | if (instance._lock) return instance; 20 | instance._lock = true; 21 | 22 | if (kDebugMode) HttpOverrides.global = httpOverrides; 23 | 24 | final options = BaseOptions( 25 | connectTimeout: const Duration(seconds: 60), 26 | receiveTimeout: const Duration(seconds: 60), 27 | sendTimeout: const Duration(seconds: 60), 28 | headers: { 29 | 'Content-Type': 'application/json', 30 | }, 31 | ); 32 | instance._dio = Dio(options); 33 | instance._dio.interceptors.add(PrettyDioLogger( 34 | requestHeader: true, 35 | requestBody: true, 36 | )); 37 | 38 | return instance; 39 | } 40 | 41 | static String get networkError => 'network_error'.tr; 42 | 43 | static Future get( 44 | String path, { 45 | Object? data, 46 | Map? queryParameters, 47 | Options? options, 48 | CancelToken? cancelToken, 49 | ProgressCallback? onReceiveProgress, 50 | }) async { 51 | Response res = await instance._dio.get( 52 | path, 53 | data: data, 54 | queryParameters: queryParameters, 55 | options: options, 56 | cancelToken: cancelToken, 57 | onReceiveProgress: onReceiveProgress, 58 | ); 59 | return res.data; 60 | } 61 | 62 | static Future> post( 63 | String path, { 64 | Object? data, 65 | Map? queryParameters, 66 | Options? options, 67 | CancelToken? cancelToken, 68 | ProgressCallback? onSendProgress, 69 | ProgressCallback? onReceiveProgress, 70 | }) async { 71 | return await instance._dio.post( 72 | path, 73 | data: data, 74 | queryParameters: queryParameters, 75 | options: options, 76 | cancelToken: cancelToken, 77 | onSendProgress: onSendProgress, 78 | onReceiveProgress: onReceiveProgress, 79 | ); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /lib/app/core/app/app_http_overrides.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | class AppHttpOverrides extends HttpOverrides { 4 | @override 5 | HttpClient createHttpClient(SecurityContext? context) { 6 | return super.createHttpClient(context) 7 | ..connectionTimeout = const Duration(seconds: 15) 8 | ..maxConnectionsPerHost = 5 9 | ..badCertificateCallback = 10 | ((X509Certificate cert, String host, int port) => true) 11 | // ..findProxy = ((url) => 'PROXY localhost:1081') 12 | ..findProxy = (url) => HttpClient.findProxyFromEnvironment( 13 | url, 14 | environment: { 15 | "http_proxy": "http://127.0.0.1:7890", 16 | "https_proxy": "http://127.0.0.1:7890", 17 | "ALL_PROXY": "socks5://127.0.0.1:7890", 18 | "HTTP_PROXY": "http://127.0.0.1:7890", 19 | "HTTPS_PROXY": "http://127.0.0.1:7890", 20 | }, 21 | ); 22 | // ..badCertificateCallback = 23 | // ((X509Certificate cert, String host, int port) => true); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /lib/app/core/app/app_manager.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter/scheduler.dart'; 3 | import 'package:get/get.dart'; 4 | import 'package:hive_flutter/hive_flutter.dart'; 5 | 6 | import 'app_controller_mixin.dart'; 7 | 8 | class AppManager extends GetxController with AppControllerMixin { 9 | static AppManager get to => Get.find(); 10 | static const String _appManagerBox = 'AppManager'; 11 | 12 | late final Box _box; 13 | 14 | static const _themeModeKey = '_themeMode'; 15 | ThemeMode? _themeMode; 16 | ThemeMode get themeMode => _themeMode ?? ThemeMode.system; 17 | set themeMode(ThemeMode themeMode) { 18 | if (_themeMode == themeMode) return; 19 | _themeMode = themeMode; 20 | Get.changeThemeMode(themeMode); 21 | 22 | _box.put(_themeModeKey, themeMode.index); 23 | } 24 | 25 | bool get isLightMode => 26 | themeMode == ThemeMode.light || 27 | (themeMode == ThemeMode.system && 28 | SchedulerBinding.instance.window.platformBrightness == 29 | Brightness.light); 30 | 31 | static const _localeKey = '_locale'; 32 | Locale? _locale; 33 | Locale? get locale => _locale; 34 | set locale(Locale? locale) { 35 | if (_locale == locale) return; 36 | _locale = locale; 37 | 38 | if (locale == null) { 39 | Get.updateLocale( 40 | Get.deviceLocale ?? const Locale('zh', 'CN'), 41 | ); 42 | _box.delete(_localeKey); 43 | } else { 44 | Get.updateLocale(locale); 45 | _box.put( 46 | _localeKey, 47 | '${locale.languageCode}&${locale.countryCode ?? ''}', 48 | ); 49 | } 50 | } 51 | 52 | final Map _map = {}; 53 | T? get({required String key}) { 54 | final value = _map[key]; 55 | if (value != null) return value; 56 | return _map[key] = _box.get(key); 57 | } 58 | 59 | void set({required String key, T? value}) { 60 | final oldValue = _map[key]; 61 | if (oldValue == value) return; 62 | if (value == null) { 63 | _map.remove(key); 64 | _box.delete(key); 65 | } else { 66 | _map[key] = value; 67 | _box.put(key, value); 68 | } 69 | update(); 70 | } 71 | 72 | AppManager._({required Box box}) : _box = box; 73 | 74 | static Future initialize() async { 75 | await Hive.initFlutter(); 76 | 77 | final box = await Hive.openBox(_appManagerBox); 78 | final manager = AppManager._(box: box); 79 | Get.put(manager); 80 | } 81 | 82 | @override 83 | void onInit() { 84 | final int? index = _box.get(_themeModeKey); 85 | if (index != null) { 86 | _themeMode = ThemeMode.values[index]; 87 | } 88 | 89 | final String? languageCode = _box.get(_localeKey); 90 | if (languageCode != null) { 91 | final codes = languageCode.split('&'); 92 | _locale = Locale.fromSubtags( 93 | languageCode: codes[0], 94 | countryCode: codes[1].isEmpty ? null : codes[1], 95 | ); 96 | } 97 | 98 | super.onInit(); 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /lib/app/core/app/app_model.dart: -------------------------------------------------------------------------------- 1 | class AppModel {} 2 | -------------------------------------------------------------------------------- /lib/app/core/app/app_plugin.dart: -------------------------------------------------------------------------------- 1 | import '../../data/models/message.dart'; 2 | 3 | typedef AppPluginCallback = Future Function(Message message); 4 | 5 | abstract class AppPlugin { 6 | String get key; 7 | String get title; 8 | AppPluginCallback get onReceived; 9 | 10 | Future send({required Message message}); 11 | } 12 | 13 | abstract class AppServicePlugin {} 14 | -------------------------------------------------------------------------------- /lib/app/core/app/app_plugin_manager.dart: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bapaws/answer/6c65fced23f96186f330d7704ff5b49107f83a83/lib/app/core/app/app_plugin_manager.dart -------------------------------------------------------------------------------- /lib/app/core/app/app_progress_hud.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_easyloading/flutter_easyloading.dart'; 3 | 4 | abstract class AppProgressHud { 5 | static TransitionBuilder init() => EasyLoading.init(); 6 | static Future show() => EasyLoading.show( 7 | maskType: EasyLoadingMaskType.clear, 8 | ); 9 | 10 | static Future dismiss() => EasyLoading.dismiss(); 11 | } 12 | -------------------------------------------------------------------------------- /lib/app/core/app/app_toast.dart: -------------------------------------------------------------------------------- 1 | import 'package:fluttertoast/fluttertoast.dart'; 2 | 3 | abstract class AppToast { 4 | static Future show({required String msg}) async { 5 | await Fluttertoast.showToast( 6 | msg: msg, 7 | gravity: ToastGravity.CENTER, 8 | ); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /lib/app/core/app/app_urls.dart: -------------------------------------------------------------------------------- 1 | class AppUrls { 2 | static const chat = '/chat/completions'; 3 | } 4 | -------------------------------------------------------------------------------- /lib/app/core/app/app_view_mixin.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter/services.dart'; 3 | import 'package:fluttertoast/fluttertoast.dart'; 4 | import 'package:get/get.dart'; 5 | 6 | import 'app_controller_mixin.dart'; 7 | 8 | mixin AppViewMixin { 9 | BuildContext get context => Get.context!; 10 | 11 | String? get tag => null; 12 | Controller get controller => GetInstance().find(tag: tag); 13 | 14 | String? get title => null; 15 | Color? get backgroundColor => null; 16 | Color? get statusBarColor => Colors.transparent; 17 | Color? get systemNavigationBarColor => 18 | Theme.of(context).scaffoldBackgroundColor; 19 | 20 | bool get topSafeArea => false; 21 | bool get bottomSafeArea => false; 22 | 23 | Widget buildBody(BuildContext context); 24 | 25 | Widget build(BuildContext context) { 26 | return buildAnnotatedRegion(context); 27 | } 28 | 29 | Widget buildAnnotatedRegion(BuildContext context) { 30 | return AnnotatedRegion( 31 | value: SystemUiOverlayStyle( 32 | statusBarColor: statusBarColor, 33 | systemNavigationBarColor: systemNavigationBarColor, 34 | ), 35 | child: Material( 36 | color: Colors.transparent, 37 | child: buildScaffold(context), 38 | ), 39 | ); 40 | } 41 | 42 | Widget buildScaffold(BuildContext context) { 43 | return GetBuilder( 44 | builder: (controller) => Scaffold( 45 | backgroundColor: backgroundColor, 46 | appBar: buildAppBar(context), 47 | body: buildPage(context), 48 | floatingActionButton: buildFloatingActionButton(context), 49 | bottomNavigationBar: buildBottomNavigationBar(context), 50 | drawer: buildDrawer(), 51 | ), 52 | ); 53 | } 54 | 55 | PreferredSizeWidget? buildAppBar(BuildContext context) { 56 | if (title == null) return null; 57 | 58 | return AppBar(title: Text(title!)); 59 | } 60 | 61 | Widget buildPage(BuildContext context) { 62 | return SafeArea( 63 | top: topSafeArea, 64 | bottom: bottomSafeArea, 65 | child: buildBody(context), 66 | ); 67 | } 68 | 69 | Widget? buildFloatingActionButton(BuildContext context) => null; 70 | Widget? buildBottomNavigationBar(BuildContext context) => null; 71 | 72 | void showErrorSnackBar(String message) { 73 | final snackBar = SnackBar(content: Text(message)); 74 | WidgetsBinding.instance.addPostFrameCallback((timeStamp) { 75 | ScaffoldMessenger.of(Get.context!).showSnackBar(snackBar); 76 | }); 77 | } 78 | 79 | void showToast(String message) { 80 | Fluttertoast.showToast( 81 | msg: message, 82 | toastLength: Toast.LENGTH_SHORT, 83 | timeInSecForIosWeb: 1, 84 | ); 85 | } 86 | 87 | Widget? buildDrawer() { 88 | return null; 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /lib/app/core/binding/initial_binding.dart: -------------------------------------------------------------------------------- 1 | import 'package:answer/app/providers/service_provider_manager.dart'; 2 | import 'package:get/get.dart'; 3 | 4 | class InitialBinding extends Bindings { 5 | @override 6 | void dependencies() { 7 | // Get.lazyPut( 8 | // () => ServiceProviderManager(), 9 | // ); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /lib/app/core/mixin/refresh_mixin.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:get/get.dart'; 3 | 4 | mixin RefreshMixin on GetLifeCycleBase { 5 | ScrollController get scroll; 6 | 7 | @override 8 | void onInit() { 9 | super.onInit(); 10 | scroll.addListener(_listener); 11 | } 12 | 13 | bool canFetchBottom = true; 14 | 15 | bool canFetchTop = true; 16 | 17 | void _listener() { 18 | final position = scroll.position; 19 | if (position.pixels < position.minScrollExtent + 16 || 20 | position.pixels > position.maxScrollExtent - 16) { 21 | _checkIfCanLoadMore(); 22 | } 23 | } 24 | 25 | Future _checkIfCanLoadMore() async { 26 | if (scroll.position.pixels == 0) { 27 | if (!canFetchTop) return; 28 | canFetchTop = false; 29 | await onTopScroll(); 30 | canFetchTop = true; 31 | } else { 32 | if (!canFetchBottom) return; 33 | canFetchBottom = false; 34 | await onEndScroll(); 35 | canFetchBottom = true; 36 | } 37 | } 38 | 39 | Future onEndScroll(); 40 | 41 | Future onTopScroll(); 42 | 43 | @override 44 | void onClose() { 45 | scroll.removeListener(_listener); 46 | super.onClose(); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /lib/app/data/db/app_database.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:answer/app/data/db/prompt_dao.dart'; 4 | import 'package:answer/app/data/db/request_parameters_dao.dart'; 5 | import 'package:answer/app/data/db/service_providers_dao.dart'; 6 | import 'package:answer/app/data/db/service_tokens_dao.dart'; 7 | import 'package:answer/app/data/db/service_vendors_dao.dart'; 8 | import 'package:flutter/foundation.dart'; 9 | import 'package:path/path.dart'; 10 | import 'package:sqflite/sqflite.dart'; 11 | 12 | import 'conversations_dao.dart'; 13 | import 'messages_dao.dart'; 14 | 15 | class AppDatabase { 16 | static const defaultLimit = 16; 17 | 18 | static final AppDatabase instance = AppDatabase._internal(); 19 | AppDatabase._internal(); 20 | 21 | late final Database database; 22 | 23 | late final ConversationsDao conversationsDao = ConversationsDao(database); 24 | late final MessagesDao messagesDao = MessagesDao(database); 25 | late final ServiceProvidersDao serviceProvidersDao = 26 | ServiceProvidersDao(database); 27 | late final ServiceVendorsDao serviceVendorsDao = ServiceVendorsDao(database); 28 | late final ServiceTokensDao serviceTokensDao = ServiceTokensDao(database); 29 | late final PromptDao promptDao = PromptDao(database); 30 | late final RequestParametersDao requestParametersDao = 31 | RequestParametersDao(database); 32 | 33 | static Future initialize({ 34 | required String dbName, 35 | }) async { 36 | var databasesPath = await getDatabasesPath(); 37 | var path = join(databasesPath, dbName); 38 | 39 | if (kDebugMode) { 40 | print(path); 41 | } 42 | 43 | instance.database = await openDatabase( 44 | path, 45 | onCreate: instance._onCreate, 46 | onUpgrade: instance._onUpgrade, 47 | version: 4, 48 | ); 49 | } 50 | 51 | FutureOr _onCreate(Database db, int version) async { 52 | await PromptDao.onCreate(db); 53 | await ServiceTokensDao.onCreate(db); 54 | await ServiceVendorsDao.onCreate(db); 55 | await ServiceProvidersDao.onCreate(db); 56 | await ConversationsDao.onCreate(db); 57 | await RequestParametersDao.onCreate(db); 58 | } 59 | 60 | FutureOr _onUpgrade(Database db, int oldVersion, int newVersion) async { 61 | if (oldVersion < 3) { 62 | await ServiceVendorsDao.onCreate(db); 63 | await PromptDao.onCreate(db); 64 | } 65 | if (oldVersion < 4) { 66 | await RequestParametersDao.onCreate(db); 67 | } else { 68 | await RequestParametersDao.onUpgrade(db, oldVersion, newVersion); 69 | } 70 | await ServiceTokensDao.onUpgrade(db, oldVersion, newVersion); 71 | await ServiceVendorsDao.onUpgrade(db, oldVersion, newVersion); 72 | await PromptDao.onUpgrade(db, oldVersion, newVersion); 73 | await ServiceProvidersDao.onUpgrade(db, oldVersion, newVersion); 74 | await MessagesDao.onUpgrade(db, oldVersion, newVersion); 75 | await ConversationsDao.onUpgrade(db, oldVersion, newVersion); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /lib/app/data/db/app_uuid.dart: -------------------------------------------------------------------------------- 1 | import 'dart:math'; 2 | 3 | import 'package:flutter/foundation.dart'; 4 | 5 | class AppUuid { 6 | AppUuid(); 7 | 8 | @override 9 | String toString() => 10 | shortHash(this) + Random().nextInt(0xFFFFFF).toRadixString(16); 11 | 12 | static String get value => AppUuid().toString(); 13 | } 14 | -------------------------------------------------------------------------------- /lib/app/data/db/conversations_dao.dart: -------------------------------------------------------------------------------- 1 | import 'package:answer/app/data/models/conversation.dart'; 2 | import 'package:sqflite/sqflite.dart'; 3 | 4 | class ConversationsDao { 5 | static const table = 'conversations'; 6 | 7 | final Database db; 8 | ConversationsDao(this.db); 9 | 10 | static Future onCreate(Database db) => db.execute(''' 11 | CREATE TABLE $table ( 12 | id TEXT NOT NULL PRIMARY KEY, 13 | name TEXT, 14 | edit_name TEXT, 15 | group_id INTEGER, 16 | auto_quote INTEGER, 17 | prompt_id String, 18 | max_tokens INTEGER DEFAULT 800, 19 | timeout INTEGER DEFAULT 60, 20 | message_version INTEGER DEFAULT 0 21 | ); 22 | '''); 23 | 24 | static Future onUpgrade( 25 | Database db, 26 | int oldVersion, 27 | int newVersion, 28 | ) async { 29 | // version 3 30 | final result = await db.rawQuery( 31 | 'SELECT * FROM sqlite_master WHERE name="$table" AND sql LIKE "%prompt_id%";', 32 | ); 33 | if (result.isEmpty) { 34 | await db.execute('ALTER TABLE $table ADD COLUMN auto_quote INTEGER;'); 35 | 36 | await db.execute('ALTER TABLE $table ADD COLUMN prompt_id TEXT;'); 37 | await db.execute('ALTER TABLE $table ADD COLUMN max_tokens INTEGER;'); 38 | await db.execute('ALTER TABLE $table ADD COLUMN timeout INTEGER;'); 39 | } 40 | } 41 | 42 | Future> getAll({required int groupId}) async { 43 | return (await db.query( 44 | table, 45 | where: 'group_id = ?', 46 | whereArgs: [groupId], 47 | )) 48 | .map((e) => Conversation.fromJson(e)); 49 | } 50 | 51 | Future create(Conversation chat) async { 52 | return await db.insert( 53 | table, 54 | chat.toJson(), 55 | conflictAlgorithm: ConflictAlgorithm.replace, 56 | ); 57 | } 58 | 59 | Future update(Conversation chat) async { 60 | return await db.update( 61 | table, 62 | chat.toJson(), 63 | where: 'id = ?', 64 | whereArgs: [chat.id], 65 | ); 66 | } 67 | 68 | Future delete(String id) async { 69 | await db.delete(table, where: 'id = ?', whereArgs: [id]); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /lib/app/data/db/groups_dao.dart: -------------------------------------------------------------------------------- 1 | import 'package:answer/app/data/models/group.dart'; 2 | import 'package:sqflite/sqflite.dart'; 3 | 4 | class GroupsDao { 5 | static const table = 'groups'; 6 | 7 | final Database db; 8 | GroupsDao(this.db); 9 | 10 | Future> getAll() async { 11 | return (await db.query(table)).map((e) => Group.fromJson(e)); 12 | } 13 | 14 | Future create(Group chat) async { 15 | return await db.insert(table, chat.toJson()); 16 | } 17 | 18 | Future update(Group chat) async { 19 | return await db.update( 20 | table, 21 | chat.toJson(), 22 | where: 'id = ?', 23 | whereArgs: [chat.id], 24 | ); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /lib/app/data/db/messages_dao.dart: -------------------------------------------------------------------------------- 1 | import 'package:answer/app/data/db/conversations_dao.dart'; 2 | import 'package:sqflite/sqflite.dart'; 3 | 4 | import '../models/message.dart'; 5 | 6 | class MessagesDao { 7 | static const table = 'messages_'; 8 | 9 | final Database db; 10 | MessagesDao(this.db); 11 | 12 | Future onCreate({required String conversationId}) async { 13 | await db.execute(''' 14 | CREATE TABLE IF NOT EXISTS $table$conversationId ( 15 | id TEXT NOT NULL PRIMARY KEY, 16 | type INTEGER, 17 | from_type INTEGER, 18 | service_name TEXT, 19 | service_avatar TEXT, 20 | content TEXT, 21 | create_at DATETIME, 22 | request_message TEXT, 23 | quote_message TEXT, 24 | response_data TEXT, 25 | conversation_id TEXT REFERENCES ${ConversationsDao.table} (id), 26 | service_id TEXT 27 | ); 28 | '''); 29 | } 30 | 31 | static Future onUpgrade( 32 | Database db, 33 | int oldVersion, 34 | int newVersion, 35 | ) async { 36 | // new version is 3 37 | final tables = await db.rawQuery( 38 | 'SELECT * FROM sqlite_master WHERE name LIKE "$table%";', 39 | ); 40 | for (final item in tables) { 41 | final tableName = item['name']; 42 | final quoteMessage = await db.rawQuery( 43 | 'SELECT * FROM sqlite_master WHERE name LIKE "$tableName" AND sql LIKE "%quote_message%";', 44 | ); 45 | if (quoteMessage.isEmpty) { 46 | await db.execute( 47 | 'ALTER TABLE $tableName ADD COLUMN quote_message TEXT;', 48 | ); 49 | } 50 | } 51 | } 52 | 53 | Future dropTable({required String conversationId}) { 54 | return db.execute('DROP TABLE $table$conversationId'); 55 | } 56 | 57 | Future> get({ 58 | required String conversationId, 59 | String? serviceId, 60 | MessageType? type, 61 | int offset = 0, 62 | int limit = 16, 63 | }) async { 64 | return (await db.rawQuery( 65 | 'SELECT * FROM $table$conversationId ${_buildWhere( 66 | serviceId: serviceId, 67 | type: type, 68 | )}ORDER BY create_at DESC LIMIT $limit OFFSET $offset', 69 | )) 70 | .map((e) => Message.fromJson(e)); 71 | } 72 | 73 | String _buildWhere({ 74 | String? serviceId, 75 | MessageType? type, 76 | }) { 77 | List where = []; 78 | if (serviceId != null) { 79 | where.add('service_id = "$serviceId"'); 80 | } 81 | if (type != null) { 82 | where.add('type = ${type.index}'); 83 | } 84 | return where.isEmpty ? '' : 'WHERE ${where.join(' AND ')} '; 85 | } 86 | 87 | Future create(Message message) async { 88 | return await db.insert('$table${message.conversationId}', message.toJson()); 89 | } 90 | 91 | Future delete(Message message) async { 92 | return await db.delete( 93 | '$table${message.conversationId}', 94 | where: 'id = ?', 95 | whereArgs: [message.id], 96 | ); 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /lib/app/data/db/prompt_dao.dart: -------------------------------------------------------------------------------- 1 | import 'package:answer/app/data/models/prompt.dart'; 2 | import 'package:collection/collection.dart'; 3 | import 'package:sqflite/sqflite.dart'; 4 | 5 | class PromptDao { 6 | static const table = 'prompts'; 7 | 8 | final Database db; 9 | PromptDao(this.db); 10 | 11 | static Future onCreate(Database db) async { 12 | await db.execute(''' 13 | CREATE TABLE IF NOT EXISTS ${PromptDao.table} ( 14 | id TEXT NOT NULL PRIMARY KEY, 15 | title TEXT, 16 | content TEXT 17 | ); 18 | '''); 19 | } 20 | 21 | static Future onUpgrade( 22 | Database db, 23 | int oldVersion, 24 | int newVersion, 25 | ) async {} 26 | 27 | Future> getAll() async { 28 | List> list = await db.rawQuery( 29 | 'SELECT * FROM $table', 30 | ); 31 | return list.map((e) => Prompt.fromJson(e)); 32 | } 33 | 34 | Future get({required String id}) async { 35 | List> list = await db.rawQuery( 36 | 'SELECT * FROM $table WHERE id = ?', 37 | [id], 38 | ); 39 | return list.map((e) => Prompt.fromJson(e)).firstOrNull; 40 | } 41 | 42 | Future create(Prompt object) async { 43 | return await db.insert( 44 | table, 45 | object.toJson(), 46 | conflictAlgorithm: ConflictAlgorithm.replace, 47 | ); 48 | } 49 | 50 | Future delete(Prompt object) async { 51 | return await db.delete( 52 | table, 53 | where: 'id = ?', 54 | whereArgs: [object.id], 55 | ); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /lib/app/data/db/request_parameters_dao.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | 3 | import 'package:answer/app/data/models/request_parameter.dart'; 4 | import 'package:flutter/services.dart'; 5 | import 'package:sqflite/sqflite.dart'; 6 | 7 | class RequestParametersDao { 8 | static const table = 'request_parameters'; 9 | 10 | final Database db; 11 | RequestParametersDao(this.db); 12 | 13 | static Future onCreate(Database db) async { 14 | await db.execute(''' 15 | CREATE TABLE IF NOT EXISTS ${RequestParametersDao.table} ( 16 | key TEXT NOT NULL, 17 | value TEXT, 18 | value_type INTEGER, 19 | choices TEXT, 20 | desc TEXT, 21 | required INTEGER DEFAULT 0, 22 | vendor_id TEXT, 23 | PRIMARY KEY (key, vendor_id) 24 | ); 25 | '''); 26 | 27 | // format data 28 | final string = await rootBundle.loadString( 29 | 'assets/files/request_parameters.json', 30 | ); 31 | final List list = json.decode(utf8.decode(base64.decode(string))); 32 | final Batch batch = db.batch(); 33 | for (final map in list) { 34 | batch.insert( 35 | table, 36 | map, 37 | conflictAlgorithm: ConflictAlgorithm.ignore, 38 | ); 39 | } 40 | await batch.commit(); 41 | } 42 | 43 | static Future onUpgrade( 44 | Database db, 45 | int oldVersion, 46 | int newVersion, 47 | ) async { 48 | final string = await rootBundle.loadString( 49 | 'assets/files/request_parameters.json', 50 | ); 51 | final List list = json.decode(utf8.decode(base64.decode(string))); 52 | final Batch batch = db.batch(); 53 | for (final map in list) { 54 | batch.insert( 55 | table, 56 | map, 57 | conflictAlgorithm: ConflictAlgorithm.ignore, 58 | ); 59 | } 60 | await batch.commit(); 61 | } 62 | 63 | Future> getAll() async { 64 | List> list = await db.rawQuery( 65 | 'SELECT * FROM $table', 66 | ); 67 | return list.map((e) => RequestParameter.fromJson(e)); 68 | } 69 | 70 | Future create(RequestParameter parameter) async { 71 | return await db.insert( 72 | table, 73 | parameter.toJson(), 74 | conflictAlgorithm: ConflictAlgorithm.replace, 75 | ); 76 | } 77 | 78 | Future delete(RequestParameter parameter) async { 79 | return await db.delete( 80 | table, 81 | where: 'key = ? AND vendor_id = ?', 82 | whereArgs: [parameter.key, parameter.vendorId], 83 | ); 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /lib/app/data/db/service_providers_dao.dart: -------------------------------------------------------------------------------- 1 | import 'package:collection/collection.dart'; 2 | import 'package:sqflite/sqflite.dart'; 3 | 4 | import '../../providers/service_provider.dart'; 5 | 6 | class ServiceProvidersDao { 7 | static const table = 'service_providers'; 8 | 9 | final Database db; 10 | ServiceProvidersDao(this.db); 11 | 12 | static Future onCreate(Database db) async { 13 | await db.execute(''' 14 | CREATE TABLE IF NOT EXISTS ${ServiceProvidersDao.table} ( 15 | id TEXT NOT NULL PRIMARY KEY, 16 | name TEXT, 17 | vendor_id TEXT, 18 | avatar TEXT, 19 | desc TEXT, 20 | group_id INTEGER, 21 | hello TEXT, 22 | block INTEGER DEFAULT 0 23 | ); 24 | '''); 25 | } 26 | 27 | static Future onUpgrade( 28 | Database db, 29 | int oldVersion, 30 | int newVersion, 31 | ) async { 32 | // new version is 2 33 | final hello = await db.rawQuery( 34 | 'SELECT * FROM sqlite_master WHERE name="$table" AND sql LIKE "%hello%";', 35 | ); 36 | if (hello.isEmpty) { 37 | await db.execute('ALTER TABLE $table ADD COLUMN hello TEXT;'); 38 | } 39 | final desc = await db.rawQuery( 40 | 'SELECT * FROM sqlite_master WHERE name="$table" AND sql LIKE "%desc%";', 41 | ); 42 | if (desc.isEmpty) { 43 | await db.execute('ALTER TABLE $table ADD COLUMN desc TEXT;'); 44 | } 45 | // VERSION 3 46 | final vendorId = await db.rawQuery( 47 | 'SELECT * FROM sqlite_master WHERE name="$table" AND sql LIKE "%vendor_id%";', 48 | ); 49 | if (vendorId.isEmpty) { 50 | await db.execute('ALTER TABLE $table ADD COLUMN vendor_id TEXT;'); 51 | await db.execute('ALTER TABLE $table ADD COLUMN model TEXT;'); 52 | } 53 | } 54 | 55 | Future>> getAll({required int groupId}) async { 56 | return (await db.query(table, where: 'group_id = ?', whereArgs: [groupId])); 57 | } 58 | 59 | Future?> get({required String id}) async { 60 | return (await db.query(table, where: 'id = ?', whereArgs: [id])) 61 | .firstOrNull; 62 | } 63 | 64 | Future create(ServiceProvider provider) async { 65 | final json = provider.toJson(); 66 | json.remove('tokens'); 67 | await db.insert( 68 | table, 69 | json, 70 | conflictAlgorithm: ConflictAlgorithm.replace, 71 | ); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /lib/app/data/db/service_tokens_dao.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | 3 | import 'package:collection/collection.dart'; 4 | import 'package:flutter/services.dart'; 5 | import 'package:sqflite/sqflite.dart'; 6 | 7 | import '../models/service_token.dart'; 8 | 9 | class ServiceTokensDao { 10 | static const table = 'service_tokens'; 11 | 12 | final Database db; 13 | ServiceTokensDao(this.db); 14 | 15 | static Future onCreate(Database db) async { 16 | await db.execute(''' 17 | CREATE TABLE IF NOT EXISTS ${ServiceTokensDao.table} ( 18 | id TEXT NOT NULL PRIMARY KEY, 19 | name TEXT, 20 | value TEXT, 21 | service_provider_id TEXT 22 | ); 23 | '''); 24 | 25 | // format data 26 | final string = await rootBundle.loadString( 27 | 'assets/files/service_tokens.json', 28 | ); 29 | final List list = json.decode(utf8.decode(base64.decode(string))); 30 | final Batch batch = db.batch(); 31 | for (final map in list) { 32 | batch.insert( 33 | table, 34 | map, 35 | conflictAlgorithm: ConflictAlgorithm.ignore, 36 | ); 37 | } 38 | await batch.commit(); 39 | } 40 | 41 | static Future onUpgrade( 42 | Database db, 43 | int oldVersion, 44 | int newVersion, 45 | ) async { 46 | final string = await rootBundle.loadString( 47 | 'assets/files/service_tokens.json', 48 | ); 49 | final List list = json.decode(utf8.decode(base64.decode(string))); 50 | final Batch batch = db.batch(); 51 | for (final map in list) { 52 | batch.insert( 53 | table, 54 | map, 55 | conflictAlgorithm: ConflictAlgorithm.ignore, 56 | ); 57 | } 58 | await batch.commit(); 59 | } 60 | 61 | Future> getAll() async { 62 | List> list = await db.rawQuery( 63 | 'SELECT * FROM $table', 64 | ); 65 | return list.map((e) => ServiceToken.fromJson(e)); 66 | } 67 | 68 | Future getValue({ 69 | required String id, 70 | }) async { 71 | List> list = await db.rawQuery( 72 | 'SELECT value FROM $table WHERE id = ?', 73 | [id], 74 | ); 75 | final value = list.firstOrNull?['value']; 76 | if (value is String) return value; 77 | 78 | return null; 79 | } 80 | 81 | Future create(ServiceToken token) async { 82 | return await db.insert( 83 | table, 84 | token.toJson(), 85 | conflictAlgorithm: ConflictAlgorithm.replace, 86 | ); 87 | } 88 | 89 | Future delete(ServiceToken token) async { 90 | return await db.delete( 91 | table, 92 | where: 'id = ?', 93 | whereArgs: [token.id], 94 | ); 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /lib/app/data/db/service_vendors_dao.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | 3 | import 'package:answer/app/data/db/service_providers_dao.dart'; 4 | import 'package:collection/collection.dart'; 5 | import 'package:flutter/services.dart'; 6 | import 'package:sqflite/sqflite.dart'; 7 | 8 | import '../../providers/service_vendor.dart'; 9 | 10 | class ServiceVendorsDao { 11 | static const table = 'service_vendors'; 12 | 13 | final Database db; 14 | ServiceVendorsDao(this.db); 15 | 16 | static Future onCreate(Database db) async { 17 | await db.execute(''' 18 | CREATE TABLE IF NOT EXISTS ${ServiceVendorsDao.table} ( 19 | id TEXT NOT NULL PRIMARY KEY, 20 | name TEXT, 21 | avatar TEXT, 22 | api_url TEXT, 23 | edit_api_url TEXT, 24 | official_url TEXT, 25 | help TEXT, 26 | help_url TEXT, 27 | hello TEXT, 28 | timeout INTEGER, 29 | block INTEGER DEFAULT 0 30 | ); 31 | '''); 32 | 33 | // format data 34 | final string = await rootBundle.loadString( 35 | 'assets/files/service_vendors.json', 36 | ); 37 | final List list = json.decode(utf8.decode(base64.decode(string))); 38 | for (final map in list) { 39 | await db.insert( 40 | table, 41 | map, 42 | conflictAlgorithm: ConflictAlgorithm.ignore, 43 | ); 44 | } 45 | } 46 | 47 | static Future onUpgrade( 48 | Database db, 49 | int oldVersion, 50 | int newVersion, 51 | ) async { 52 | final string = await rootBundle.loadString( 53 | 'assets/files/service_vendors.json', 54 | ); 55 | final List list = json.decode(utf8.decode(base64.decode(string))); 56 | for (final map in list) { 57 | if (oldVersion < 3) { 58 | final first = (await db.rawQuery( 59 | 'SELECT id, edit_api_url FROM ${ServiceProvidersDao.table} WHERE id = ?', 60 | [map['id']], 61 | )) 62 | .firstOrNull; 63 | if (first != null) { 64 | map['edit_api_url'] = first['edit_api_url']; 65 | } 66 | } 67 | await db.insert( 68 | table, 69 | map, 70 | conflictAlgorithm: ConflictAlgorithm.ignore, 71 | ); 72 | } 73 | } 74 | 75 | Future> getAll() async { 76 | return (await db.query(table)).map(ServiceVendor.fromJson); 77 | } 78 | 79 | Future get({required String id}) async { 80 | return (await db.query(table, where: 'id = ?', whereArgs: [id])) 81 | .map(ServiceVendor.fromJson) 82 | .firstOrNull; 83 | } 84 | 85 | Future create(ServiceVendor object) async { 86 | final json = object.toJson(); 87 | json.remove('tokens'); 88 | await db.insert( 89 | table, 90 | json, 91 | conflictAlgorithm: ConflictAlgorithm.replace, 92 | ); 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /lib/app/data/models/group.dart: -------------------------------------------------------------------------------- 1 | // { 2 | // "id": 0, 3 | // "name": "Chat", 4 | // "avatar": "open_ai_chat_gpt.svg", 5 | // "title": "open_ai_chat_gpt.svg" 6 | // } 7 | 8 | import 'dart:convert'; 9 | 10 | class Group { 11 | Group({ 12 | required this.id, 13 | required this.name, 14 | required this.avatar, 15 | required this.title, 16 | }); 17 | 18 | final int id; 19 | final String name; 20 | final String avatar; 21 | final String title; 22 | 23 | Group copyWith({ 24 | int? id, 25 | String? name, 26 | String? avatar, 27 | String? title, 28 | }) => 29 | Group( 30 | id: id ?? this.id, 31 | name: name ?? this.name, 32 | avatar: avatar ?? this.avatar, 33 | title: title ?? this.title, 34 | ); 35 | 36 | factory Group.fromRawJson(String str) => Group.fromJson(json.decode(str)); 37 | 38 | String toRawJson() => json.encode(toJson()); 39 | 40 | factory Group.fromJson(Map json) => Group( 41 | id: json["id"], 42 | name: json["name"], 43 | avatar: json["avatar"], 44 | title: json["title"], 45 | ); 46 | 47 | Map toJson() => { 48 | "id": id, 49 | "name": name, 50 | "avatar": avatar, 51 | "title": title, 52 | }; 53 | } 54 | -------------------------------------------------------------------------------- /lib/app/data/models/prompt.dart: -------------------------------------------------------------------------------- 1 | // { 2 | // "id": 0, 3 | // "title": "", 4 | // "content": "" 5 | // } 6 | 7 | import 'dart:convert'; 8 | 9 | class Prompt { 10 | Prompt({ 11 | this.id, 12 | this.title, 13 | this.content, 14 | }); 15 | 16 | final String? id; 17 | final String? title; 18 | final String? content; 19 | 20 | Prompt copyWith({ 21 | String? id, 22 | String? title, 23 | String? content, 24 | }) => 25 | Prompt( 26 | id: id ?? this.id, 27 | title: title ?? this.title, 28 | content: content ?? this.content, 29 | ); 30 | 31 | factory Prompt.fromRawJson(String str) => Prompt.fromJson(json.decode(str)); 32 | 33 | String toRawJson() => json.encode(toJson()); 34 | 35 | factory Prompt.fromJson(Map json) => Prompt( 36 | id: json["id"], 37 | title: json["title"], 38 | content: json["content"], 39 | ); 40 | 41 | Map toJson() => { 42 | "id": id, 43 | "title": title, 44 | "content": content, 45 | }; 46 | } 47 | -------------------------------------------------------------------------------- /lib/app/data/models/request_parameter.dart: -------------------------------------------------------------------------------- 1 | // { 2 | // "key": "", 3 | // "value": "", 4 | // "value_type": 0, 5 | // "choices": [ 6 | // "value1", 7 | // "value2" 8 | // ], 9 | // "desc": "", 10 | // "required": false 11 | // } 12 | 13 | import 'dart:convert'; 14 | 15 | enum ServiceParameterValueType { integer, string, choices, boolean } 16 | 17 | class RequestParameter { 18 | RequestParameter({ 19 | this.key, 20 | this.value, 21 | this.valueType = ServiceParameterValueType.string, 22 | this.desc, 23 | this.required = false, 24 | this.vendorId, 25 | }); 26 | 27 | final String? key; 28 | final String? value; 29 | final ServiceParameterValueType valueType; 30 | final String? desc; 31 | final bool? required; 32 | final String? vendorId; 33 | 34 | RequestParameter copyWith({ 35 | String? key, 36 | String? value, 37 | ServiceParameterValueType? valueType, 38 | List? choices, 39 | String? desc, 40 | bool? required, 41 | String? vendorId, 42 | }) => 43 | RequestParameter( 44 | key: key ?? this.key, 45 | value: value ?? this.value, 46 | valueType: valueType ?? this.valueType, 47 | desc: desc ?? this.desc, 48 | required: required ?? this.required, 49 | vendorId: vendorId ?? this.vendorId, 50 | ); 51 | 52 | factory RequestParameter.fromRawJson(String str) => 53 | RequestParameter.fromJson(json.decode(str)); 54 | 55 | String toRawJson() => json.encode(toJson()); 56 | 57 | factory RequestParameter.fromJson(Map json) => 58 | RequestParameter( 59 | key: json["key"], 60 | value: json["value"], 61 | valueType: ServiceParameterValueType.values[json["value_type"] ?? 0], 62 | desc: json["desc"], 63 | required: json["required"] == 1, 64 | vendorId: json["vendor_id"], 65 | ); 66 | 67 | Map toJson() => { 68 | "key": key, 69 | "value": value, 70 | "value_type": valueType.index, 71 | "desc": desc, 72 | "required": required == true ? 1 : 0, 73 | "vendor_id": vendorId, 74 | }; 75 | } 76 | -------------------------------------------------------------------------------- /lib/app/data/models/service_token.dart: -------------------------------------------------------------------------------- 1 | // { 2 | // "id": "api-key", 3 | // "name": "API Key", 4 | // "value": "", 5 | // "service_provider_id": 0 6 | // } 7 | 8 | import 'dart:convert'; 9 | 10 | class ServiceToken { 11 | ServiceToken({ 12 | required this.id, 13 | required this.name, 14 | this.value = '', 15 | required this.vendorId, 16 | }); 17 | 18 | final String id; 19 | final String name; 20 | final String value; 21 | final String vendorId; 22 | 23 | ServiceToken copyWith({ 24 | String? id, 25 | String? name, 26 | String? value, 27 | String? vendorId, 28 | }) => 29 | ServiceToken( 30 | id: id ?? this.id, 31 | name: name ?? this.name, 32 | value: value ?? this.value, 33 | vendorId: vendorId ?? this.vendorId, 34 | ); 35 | 36 | factory ServiceToken.fromRawJson(String str) => 37 | ServiceToken.fromJson(json.decode(str)); 38 | 39 | String toRawJson() => json.encode(toJson()); 40 | 41 | factory ServiceToken.fromJson(Map json) => ServiceToken( 42 | id: json["id"], 43 | name: json["name"], 44 | value: json["value"] ?? '', 45 | vendorId: json["service_provider_id"], 46 | ); 47 | 48 | Map toJson() => { 49 | "id": id, 50 | "name": name, 51 | "value": value, 52 | "service_provider_id": vendorId, 53 | }; 54 | } 55 | -------------------------------------------------------------------------------- /lib/app/data/models/value_serializer.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/foundation.dart'; 2 | 3 | class ValueSerializer { 4 | final bool serializeDateTimeValuesAsString; 5 | 6 | const ValueSerializer({this.serializeDateTimeValuesAsString = false}); 7 | 8 | T fromJson(dynamic json) { 9 | if (json == null) { 10 | return null as T; 11 | } 12 | 13 | final typeList = []; 14 | 15 | if (typeList is List) { 16 | if (json is int) { 17 | return DateTime.fromMillisecondsSinceEpoch(json) as T; 18 | } else { 19 | return DateTime.parse(json.toString()) as T; 20 | } 21 | } 22 | 23 | if (typeList is List && json is int) { 24 | return json.toDouble() as T; 25 | } 26 | 27 | // blobs are encoded as a regular json array, so we manually convert that to 28 | // a Uint8List 29 | if (typeList is List && json is! Uint8List) { 30 | final asList = (json as List).cast(); 31 | return Uint8List.fromList(asList) as T; 32 | } 33 | 34 | return json as T; 35 | } 36 | 37 | dynamic toJson(T value) { 38 | if (value is DateTime) { 39 | return serializeDateTimeValuesAsString 40 | ? value.toIso8601String() 41 | : value.millisecondsSinceEpoch; 42 | } 43 | 44 | return value; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /lib/app/modules/conversation/bindings/conversation_binding.dart: -------------------------------------------------------------------------------- 1 | import 'package:get/get.dart'; 2 | 3 | import '../controllers/conversation_controller.dart'; 4 | 5 | class ConversationBinding extends Bindings { 6 | @override 7 | void dependencies() { 8 | Get.lazyPut( 9 | () => ConversationController(), 10 | ); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /lib/app/modules/conversation/controllers/conversation_controller.dart: -------------------------------------------------------------------------------- 1 | import 'package:answer/app/core/app/app_controller_mixin.dart'; 2 | import 'package:answer/app/data/models/conversation.dart'; 3 | import 'package:answer/app/data/models/group.dart'; 4 | import 'package:answer/app/data/models/prompt.dart'; 5 | import 'package:answer/app/modules/home/controllers/home_controller.dart'; 6 | import 'package:answer/app/providers/service_provider.dart'; 7 | import 'package:answer/app/providers/service_provider_manager.dart'; 8 | import 'package:flutter/cupertino.dart'; 9 | import 'package:get/get.dart'; 10 | 11 | import '../../../core/app/app_toast.dart'; 12 | import '../../../data/db/app_database.dart'; 13 | import '../../../routes/app_pages.dart'; 14 | 15 | class ConversationController extends GetxController with AppControllerMixin { 16 | late Group group = Get.arguments['group']; 17 | late Conversation conversation = Get.arguments['conversation']; 18 | late final nameTextEditing = TextEditingController( 19 | text: conversation.name, 20 | ); 21 | late final FocusNode nameFocusNode = FocusNode(); 22 | 23 | late final List providers = 24 | ServiceProviderManager.instance.providers; 25 | 26 | late final maxTokensTextEditing = TextEditingController(); 27 | late final timeoutTextEditing = TextEditingController(); 28 | 29 | Prompt? prompt; 30 | 31 | bool editing = false; 32 | 33 | @override 34 | Future onInit() async { 35 | super.onInit(); 36 | } 37 | 38 | @override 39 | Future onReady() async { 40 | maxTokensTextEditing.text = conversation.maxTokens.toString(); 41 | timeoutTextEditing.text = conversation.timeout.toString(); 42 | 43 | if (conversation.promptId != null) { 44 | prompt = await AppDatabase.instance.promptDao.get( 45 | id: conversation.promptId!, 46 | ); 47 | } 48 | 49 | update(); 50 | super.onReady(); 51 | } 52 | 53 | void onEdited() { 54 | editing = true; 55 | update(); 56 | // delay is working 57 | Future.delayed(const Duration(milliseconds: 100), () { 58 | nameFocusNode.requestFocus(); 59 | }); 60 | } 61 | 62 | void onBlocked(bool value, ServiceProvider serviceProvider) {} 63 | 64 | Future onQuoted(bool value) async { 65 | conversation = conversation.copyWith(autoQuote: value ? 10 : 0); 66 | update(); 67 | _updateHome(); 68 | 69 | await AppDatabase.instance.conversationsDao.create( 70 | conversation, 71 | ); 72 | } 73 | 74 | Future onPrompted() async { 75 | final result = await Get.toNamed(Routes.prompt); 76 | if (result == null) return; 77 | 78 | prompt = result; 79 | conversation = conversation.copyWith(promptId: prompt!.id); 80 | 81 | update(); 82 | _updateHome(); 83 | 84 | await AppDatabase.instance.conversationsDao.create( 85 | conversation, 86 | ); 87 | } 88 | 89 | Future onSaved() async { 90 | editing = false; 91 | 92 | conversation = conversation.copyWith( 93 | name: nameTextEditing.text, 94 | maxTokens: int.tryParse(maxTokensTextEditing.text), 95 | timeout: int.tryParse(timeoutTextEditing.text), 96 | promptId: prompt?.id, 97 | ); 98 | 99 | update(); 100 | 101 | await AppDatabase.instance.conversationsDao.create(conversation); 102 | 103 | AppToast.show(msg: 'saved_successfully'.tr); 104 | 105 | _updateHome(); 106 | } 107 | 108 | Future onDeleted() async { 109 | await HomeController.to.deleteConversation(conversation); 110 | Get.back(); 111 | } 112 | 113 | void _updateHome() { 114 | final home = HomeController.to; 115 | home.conversations[home.currentConversationIndex] = conversation; 116 | home.update(); 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /lib/app/modules/conversation/views/services_item_view.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | import '../../../providers/service_provider.dart'; 4 | 5 | class ServicesItemView extends StatelessWidget { 6 | final List providers; 7 | const ServicesItemView({ 8 | Key? key, 9 | required this.providers, 10 | }) : super(key: key); 11 | 12 | @override 13 | Widget build(BuildContext context) { 14 | return const Placeholder(); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /lib/app/modules/home/bindings/home_binding.dart: -------------------------------------------------------------------------------- 1 | import 'package:get/get.dart'; 2 | 3 | import '../controllers/home_controller.dart'; 4 | 5 | class HomeBinding extends Bindings { 6 | @override 7 | void dependencies() { 8 | Get.lazyPut( 9 | () => HomeController(), 10 | ); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /lib/app/modules/home/views/home_bar.dart: -------------------------------------------------------------------------------- 1 | // class HomeBar extends StatelessWidget { 2 | // final VoidCallback? onAddedChat; 3 | // final String title; 4 | // const HomeBar({ 5 | // Key? key, 6 | // this.onAddedChat, 7 | // required this.title, 8 | // }) : super(key: key); 9 | // 10 | // @override 11 | // Widget build(BuildContext context) { 12 | // return ; 13 | // } 14 | // } 15 | -------------------------------------------------------------------------------- /lib/app/modules/home/views/home_body.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class HomeBody extends StatelessWidget { 4 | const HomeBody({Key? key}) : super(key: key); 5 | 6 | @override 7 | Widget build(BuildContext context) { 8 | return Container(); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /lib/app/modules/home/views/home_view.dart: -------------------------------------------------------------------------------- 1 | import 'package:answer/app/core/app/app_view_mixin.dart'; 2 | import 'package:answer/app/modules/home/views/home_drawer.dart'; 3 | import 'package:answer/app/views/chat_input.dart'; 4 | import 'package:answer/app/views/chat_view.dart'; 5 | import 'package:flutter/material.dart'; 6 | import 'package:get/get.dart'; 7 | 8 | import '../controllers/home_controller.dart'; 9 | 10 | class HomeView extends StatelessWidget with AppViewMixin { 11 | @override 12 | bool get bottomSafeArea => true; 13 | 14 | @override 15 | Color? get systemNavigationBarColor => 16 | Theme.of(context).appBarTheme.backgroundColor; 17 | 18 | const HomeView({Key? key}) : super(key: key); 19 | 20 | @override 21 | PreferredSizeWidget? buildAppBar(BuildContext context) { 22 | return AppBar( 23 | bottom: PreferredSize( 24 | preferredSize: const Size.fromHeight(0), 25 | child: Container( 26 | height: 2, 27 | ), 28 | ), 29 | elevation: 0, 30 | leading: Builder(builder: (context) { 31 | return IconButton( 32 | onPressed: () { 33 | Scaffold.of(context).openDrawer(); 34 | controller.focusNode.unfocus(); 35 | }, 36 | icon: const Icon(Icons.menu), 37 | ); 38 | }), 39 | title: Text( 40 | controller.currentConversation?.displayName ?? 'new_chat'.tr, 41 | overflow: TextOverflow.ellipsis, 42 | maxLines: 1, 43 | ), 44 | centerTitle: true, 45 | titleSpacing: 0, 46 | actions: [ 47 | IconButton( 48 | onPressed: controller.toConversation, 49 | icon: const Icon( 50 | Icons.more_horiz, 51 | size: 25, 52 | ), 53 | ), 54 | const SizedBox(width: 16), 55 | ], 56 | ); 57 | } 58 | 59 | @override 60 | Widget buildBody(BuildContext context) => Column( 61 | children: [ 62 | Expanded( 63 | child: ChatView( 64 | messages: controller.messages, 65 | controller: controller.scroll, 66 | onRetried: controller.onRetried, 67 | onAvatarClicked: controller.onAvatarClicked, 68 | onQuoted: controller.onQuoted, 69 | ), 70 | ), 71 | ChatInput( 72 | focusNode: controller.focusNode, 73 | controller: controller.textEditing, 74 | onSubmitted: controller.onSubmitted, 75 | quoteMessage: controller.currentQuotedMessage, 76 | onCleared: controller.onCleared, 77 | ), 78 | ], 79 | ); 80 | 81 | @override 82 | Widget? buildDrawer() => const HomeDrawer(); 83 | } 84 | -------------------------------------------------------------------------------- /lib/app/modules/new_prompt/bindings/new_prompt_binding.dart: -------------------------------------------------------------------------------- 1 | import 'package:get/get.dart'; 2 | 3 | import '../controllers/new_prompt_controller.dart'; 4 | 5 | class NewPromptBinding extends Bindings { 6 | @override 7 | void dependencies() { 8 | Get.lazyPut( 9 | () => NewPromptController(), 10 | ); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /lib/app/modules/new_prompt/controllers/new_prompt_controller.dart: -------------------------------------------------------------------------------- 1 | import 'package:answer/app/core/app/app_controller_mixin.dart'; 2 | import 'package:answer/app/data/db/app_database.dart'; 3 | import 'package:answer/app/data/db/app_uuid.dart'; 4 | import 'package:answer/app/data/models/prompt.dart'; 5 | import 'package:flutter/cupertino.dart'; 6 | import 'package:get/get.dart'; 7 | 8 | class NewPromptController extends GetxController with AppControllerMixin { 9 | final titleTextEditing = TextEditingController(); 10 | final contentTextEditing = TextEditingController(); 11 | 12 | Prompt? prompt = Get.arguments; 13 | 14 | @override 15 | void onInit() { 16 | super.onInit(); 17 | if (prompt != null) { 18 | titleTextEditing.text = prompt!.title ?? ''; 19 | contentTextEditing.text = prompt!.content ?? ''; 20 | } 21 | } 22 | 23 | Future onCreated() async { 24 | final prompt = Prompt( 25 | id: AppUuid.value, 26 | title: titleTextEditing.text, 27 | content: contentTextEditing.text, 28 | ); 29 | await AppDatabase.instance.promptDao.create( 30 | prompt, 31 | ); 32 | Get.back(result: prompt); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /lib/app/modules/new_prompt/views/new_prompt_view.dart: -------------------------------------------------------------------------------- 1 | import 'package:answer/app/core/app/app_view_mixin.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:get/get.dart'; 4 | 5 | import '../controllers/new_prompt_controller.dart'; 6 | 7 | class NewPromptView extends StatelessWidget 8 | with AppViewMixin { 9 | const NewPromptView({Key? key}) : super(key: key); 10 | 11 | @override 12 | PreferredSizeWidget? buildAppBar(BuildContext context) { 13 | return AppBar( 14 | leading: IconButton( 15 | onPressed: Get.back, 16 | icon: const Icon(Icons.close), 17 | ), 18 | title: Text('new'.tr), 19 | actions: [ 20 | IconButton( 21 | onPressed: controller.onCreated, 22 | icon: const Icon(Icons.check), 23 | ) 24 | ], 25 | ); 26 | } 27 | 28 | @override 29 | Widget buildBody(BuildContext context) { 30 | return Column( 31 | children: [ 32 | Container( 33 | padding: const EdgeInsets.only( 34 | top: 8, 35 | bottom: 8, 36 | left: 16, 37 | ), 38 | color: Theme.of(context).cardColor, 39 | height: 54, 40 | child: Row( 41 | children: [ 42 | Expanded( 43 | child: TextField( 44 | autofocus: true, 45 | controller: controller.titleTextEditing, 46 | decoration: InputDecoration.collapsed( 47 | hintText: 'typing_title'.tr, 48 | ), 49 | ), 50 | ), 51 | IconButton( 52 | onPressed: () { 53 | controller.titleTextEditing.clear(); 54 | }, 55 | icon: const Icon(Icons.clear), 56 | iconSize: 18, 57 | padding: EdgeInsets.zero, 58 | ), 59 | ], 60 | ), 61 | ), 62 | const SizedBox( 63 | height: 16, 64 | ), 65 | Container( 66 | padding: const EdgeInsets.symmetric( 67 | vertical: 16, 68 | horizontal: 16, 69 | ), 70 | color: Theme.of(context).cardColor, 71 | height: 200, 72 | child: TextField( 73 | controller: controller.contentTextEditing, 74 | decoration: InputDecoration.collapsed( 75 | hintText: 'typing_content'.tr, 76 | ), 77 | maxLines: 20, 78 | ), 79 | ), 80 | ], 81 | ); 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /lib/app/modules/prompt/bindings/prompt_binding.dart: -------------------------------------------------------------------------------- 1 | import 'package:get/get.dart'; 2 | 3 | import '../controllers/prompt_controller.dart'; 4 | 5 | class PromptBinding extends Bindings { 6 | @override 7 | void dependencies() { 8 | Get.lazyPut( 9 | () => PromptController(), 10 | ); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /lib/app/modules/prompt/controllers/prompt_controller.dart: -------------------------------------------------------------------------------- 1 | import 'package:answer/app/core/app/app_controller_mixin.dart'; 2 | import 'package:answer/app/data/db/app_database.dart'; 3 | import 'package:get/get.dart'; 4 | 5 | import '../../../data/models/prompt.dart'; 6 | import '../../../routes/app_pages.dart'; 7 | 8 | class PromptController extends GetxController with AppControllerMixin { 9 | final List prompts = []; 10 | 11 | @override 12 | Future onReady() async { 13 | prompts.addAll( 14 | await AppDatabase.instance.promptDao.getAll(), 15 | ); 16 | 17 | update(); 18 | super.onReady(); 19 | } 20 | 21 | Future onNew() async { 22 | final prompt = await Get.toNamed(Routes.newPrompt); 23 | if (prompt != null) { 24 | prompts.add(prompt); 25 | update(); 26 | } 27 | } 28 | 29 | Future onEdited(Prompt prompt) async { 30 | final result = await Get.toNamed( 31 | Routes.newPrompt, 32 | arguments: prompt, 33 | ); 34 | if (result != null) { 35 | final index = prompts.indexWhere((element) => element.id == result.id); 36 | prompts[index] = result; 37 | update(); 38 | } 39 | } 40 | 41 | Future onDeleted(Prompt prompt) async { 42 | prompts.removeWhere((element) => element.id == prompt.id); 43 | update(); 44 | 45 | await AppDatabase.instance.promptDao.delete(prompt); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /lib/app/modules/service/bindings/service_binding.dart: -------------------------------------------------------------------------------- 1 | import 'package:get/get.dart'; 2 | 3 | import '../controllers/service_controller.dart'; 4 | 5 | class ServiceBinding extends Bindings { 6 | @override 7 | void dependencies() { 8 | Get.lazyPut( 9 | () => ServiceController(), 10 | ); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /lib/app/modules/service/controllers/service_controller.dart: -------------------------------------------------------------------------------- 1 | import 'package:answer/app/core/app/app_toast.dart'; 2 | import 'package:answer/app/data/db/app_database.dart'; 3 | import 'package:answer/app/providers/service_provider_manager.dart'; 4 | import 'package:answer/app/providers/service_vendor.dart'; 5 | import 'package:flutter/material.dart'; 6 | import 'package:get/get.dart'; 7 | 8 | import '../../../core/app/app_controller_mixin.dart'; 9 | import '../../../providers/service_provider.dart'; 10 | 11 | class ServiceController extends GetxController with AppControllerMixin { 12 | late final String serviceId = Get.arguments ?? Get.parameters['service_id']; 13 | ServiceProvider? provider; 14 | 15 | ServiceVendor? vendor; 16 | 17 | late final modelFocusNode = FocusNode(); 18 | late final modelTextEditing = TextEditingController( 19 | text: provider?.model, 20 | ); 21 | 22 | bool editing = false; 23 | 24 | @override 25 | Future onReady() async { 26 | provider = await ServiceProviderManager.instance.get( 27 | id: serviceId, 28 | ); 29 | vendor = ServiceProviderManager.instance.vendors.firstWhereOrNull( 30 | (element) => element.id == provider?.vendorId, 31 | ); 32 | update(); 33 | 34 | super.onReady(); 35 | } 36 | 37 | void onEdited() { 38 | editing = true; 39 | update(); 40 | // delay is working 41 | Future.delayed(const Duration(milliseconds: 100), () { 42 | modelFocusNode.requestFocus(); 43 | }); 44 | } 45 | 46 | Future onSaved() async { 47 | if (provider == null && vendor == null) return; 48 | 49 | if (modelTextEditing.text != provider?.model) { 50 | provider = provider?.copyWith(model: modelTextEditing.text); 51 | await AppDatabase.instance.serviceProvidersDao.create(provider!); 52 | } 53 | 54 | editing = false; 55 | 56 | update(); 57 | 58 | AppToast.show(msg: 'saved_successfully'.tr); 59 | } 60 | 61 | Future onBlocked(bool value) async { 62 | await ServiceProviderManager.instance.block(provider, value); 63 | update(); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /lib/app/modules/service/views/service_info.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | import '../../../providers/service_provider.dart'; 4 | import '../../../views/chat_avatar.dart'; 5 | 6 | class ServiceInfo extends StatelessWidget { 7 | const ServiceInfo({ 8 | super.key, 9 | required this.provider, 10 | }); 11 | 12 | final ServiceProvider provider; 13 | 14 | @override 15 | Widget build(BuildContext context) { 16 | return Container( 17 | color: Theme.of(context).cardColor, 18 | padding: const EdgeInsets.all(16), 19 | child: Row( 20 | crossAxisAlignment: CrossAxisAlignment.start, 21 | children: [ 22 | ChatAvatar( 23 | path: provider.avatar, 24 | radius: const Radius.circular(8), 25 | ), 26 | const SizedBox( 27 | width: 8, 28 | ), 29 | Column( 30 | crossAxisAlignment: CrossAxisAlignment.start, 31 | children: [ 32 | Text( 33 | provider.name, 34 | style: Theme.of(context).textTheme.titleMedium, 35 | ), 36 | const SizedBox( 37 | height: 4, 38 | ), 39 | Text( 40 | 'ID: ${provider.id}', 41 | style: Theme.of(context).textTheme.bodySmall, 42 | ), 43 | ], 44 | ), 45 | ], 46 | ), 47 | ); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /lib/app/modules/service/views/service_token_item_view.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:get/get.dart'; 3 | 4 | import '../../../data/models/service_token.dart'; 5 | import '../../../views/app_cell.dart'; 6 | 7 | class ServiceTokenItemView extends StatelessWidget { 8 | const ServiceTokenItemView({ 9 | super.key, 10 | required this.item, 11 | required this.enabled, 12 | required this.textEditingController, 13 | required this.onObscured, 14 | required this.obscured, 15 | this.onSubmitted, 16 | required this.focusNode, 17 | this.textInputAction, 18 | }); 19 | 20 | final ServiceToken item; 21 | final bool enabled; 22 | final FocusNode focusNode; 23 | final TextEditingController textEditingController; 24 | final VoidCallback onObscured; 25 | final bool obscured; 26 | final ValueChanged? onSubmitted; 27 | final TextInputAction? textInputAction; 28 | 29 | @override 30 | Widget build(BuildContext context) { 31 | return AppCell( 32 | title: SizedBox( 33 | width: 80, 34 | child: Text( 35 | item.name, 36 | style: Theme.of(context).textTheme.titleMedium, 37 | ), 38 | ), 39 | detail: TextField( 40 | controller: textEditingController, 41 | focusNode: focusNode, 42 | minLines: 1, 43 | maxLines: 3, 44 | enabled: enabled, 45 | style: Theme.of(context).textTheme.bodyMedium, 46 | decoration: InputDecoration.collapsed( 47 | hintText: 'type_your_tokens'.trParams({'name': item.name}), 48 | ), 49 | onSubmitted: onSubmitted, 50 | textInputAction: textInputAction ?? TextInputAction.done, 51 | ), 52 | trailing: IconButton( 53 | padding: EdgeInsets.zero, 54 | onPressed: onObscured, 55 | icon: obscured 56 | ? const Icon(Icons.visibility_off) 57 | : const Icon(Icons.visibility), 58 | ), 59 | ); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /lib/app/modules/service/views/service_view.dart: -------------------------------------------------------------------------------- 1 | import 'package:answer/app/providers/open_ai/chat_gpt.dart'; 2 | import 'package:answer/app/routes/app_pages.dart'; 3 | import 'package:answer/app/views/app_cell.dart'; 4 | import 'package:answer/app/views/app_section.dart'; 5 | import 'package:answer/app/views/chat_avatar.dart'; 6 | import 'package:flutter/material.dart'; 7 | import 'package:get/get.dart'; 8 | 9 | import '../../../core/app/app_view_mixin.dart'; 10 | import '../controllers/service_controller.dart'; 11 | import 'service_info.dart'; 12 | 13 | class ServiceView extends StatelessWidget with AppViewMixin { 14 | static const titleWidth = 80.0; 15 | const ServiceView({ 16 | Key? key, 17 | }) : super(key: key); 18 | 19 | @override 20 | PreferredSizeWidget? buildAppBar(BuildContext context) { 21 | return AppBar( 22 | actions: controller.editing 23 | ? [ 24 | TextButton( 25 | onPressed: controller.onSaved, 26 | child: Text( 27 | 'save'.tr, 28 | style: Theme.of(context).textTheme.titleMedium, 29 | ), 30 | ), 31 | ] 32 | : [ 33 | IconButton( 34 | onPressed: controller.onEdited, 35 | icon: const Icon(Icons.edit), 36 | ), 37 | ], 38 | ); 39 | } 40 | 41 | @override 42 | Widget buildBody(BuildContext context) { 43 | if (controller.provider == null) return Container(); 44 | 45 | return ListView( 46 | keyboardDismissBehavior: ScrollViewKeyboardDismissBehavior.onDrag, 47 | children: [ 48 | ServiceInfo( 49 | provider: controller.provider!, 50 | ), 51 | if (controller.provider?.desc != null) 52 | AppCell( 53 | title: Text( 54 | controller.provider!.desc!, 55 | style: Theme.of(context).textTheme.bodySmall, 56 | ), 57 | ), 58 | const SizedBox( 59 | height: 8, 60 | ), 61 | AppCell.switchTile( 62 | title: SizedBox( 63 | width: titleWidth, 64 | child: Text( 65 | 'block'.tr, 66 | style: Theme.of(context).textTheme.titleMedium, 67 | ), 68 | ), 69 | initialValue: controller.provider?.block ?? false, 70 | hiddenDivider: true, 71 | maxHeight: 54, 72 | onToggle: (value) { 73 | controller.onBlocked(value); 74 | }, 75 | ), 76 | const SizedBox( 77 | height: 8, 78 | ), 79 | if (controller.provider is ChatGpt) 80 | AppCell.textFieldTile( 81 | title: SizedBox( 82 | width: 80, 83 | child: Text( 84 | 'model'.tr, 85 | style: Theme.of(context).textTheme.titleMedium, 86 | ), 87 | ), 88 | controller: controller.modelTextEditing, 89 | enabled: controller.editing, 90 | hintText: (controller.provider as ChatGpt).model, 91 | hiddenDivider: true, 92 | ), 93 | const SizedBox( 94 | height: 8, 95 | ), 96 | AppSection(title: Text('vendor'.tr)), 97 | AppCell.navigation( 98 | leading: ChatAvatar( 99 | path: controller.vendor?.avatar, 100 | ), 101 | title: SizedBox( 102 | width: 80, 103 | child: Text( 104 | controller.vendor?.name ?? '', 105 | style: Theme.of(context).textTheme.titleMedium, 106 | ), 107 | ), 108 | hiddenDivider: true, 109 | onPressed: () { 110 | Get.toNamed( 111 | Routes.vendor, 112 | arguments: controller.vendor?.id, 113 | ); 114 | }, 115 | ), 116 | ], 117 | ); 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /lib/app/modules/settings/bindings/settings_binding.dart: -------------------------------------------------------------------------------- 1 | import 'package:get/get.dart'; 2 | 3 | import '../controllers/settings_controller.dart'; 4 | 5 | class SettingsBinding extends Bindings { 6 | @override 7 | void dependencies() { 8 | Get.lazyPut( 9 | () => SettingsController(), 10 | ); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /lib/app/modules/settings/controllers/settings_controller.dart: -------------------------------------------------------------------------------- 1 | import 'package:get/get.dart'; 2 | 3 | class SettingsController extends GetxController { 4 | //TODO: Implement SettingsController 5 | 6 | final count = 0.obs; 7 | @override 8 | void onInit() { 9 | super.onInit(); 10 | } 11 | 12 | @override 13 | void onReady() { 14 | super.onReady(); 15 | } 16 | 17 | @override 18 | void onClose() { 19 | super.onClose(); 20 | } 21 | 22 | void increment() => count.value++; 23 | } 24 | -------------------------------------------------------------------------------- /lib/app/modules/settings/views/settings_view.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | import 'package:get/get.dart'; 4 | 5 | import '../controllers/settings_controller.dart'; 6 | 7 | class SettingsView extends GetView { 8 | const SettingsView({Key? key}) : super(key: key); 9 | @override 10 | Widget build(BuildContext context) { 11 | return Scaffold( 12 | appBar: AppBar( 13 | title: const Text('SettingsView'), 14 | centerTitle: true, 15 | ), 16 | body: Center( 17 | child: Text( 18 | 'SettingsView is working', 19 | style: TextStyle(fontSize: 20), 20 | ), 21 | ), 22 | ); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /lib/app/modules/vendor/bindings/vendor_binding.dart: -------------------------------------------------------------------------------- 1 | import 'package:get/get.dart'; 2 | 3 | import '../controllers/vendor_controller.dart'; 4 | 5 | class VendorBinding extends Bindings { 6 | @override 7 | void dependencies() { 8 | Get.lazyPut( 9 | () => VendorController(), 10 | ); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /lib/app/providers/open_ai/chat_gpt_3.dart: -------------------------------------------------------------------------------- 1 | import 'package:answer/app/providers/open_ai/chat_gpt.dart'; 2 | 3 | class ChatGpt3 extends ChatGpt { 4 | ChatGpt3({ 5 | super.model = 'gpt-3.5-turbo-0301', 6 | super.id = 'open_ai_chat_gpt', 7 | super.name = 'ChatAI', 8 | super.avatar = 'assets/images/ai_avatar.png', 9 | }); 10 | } 11 | -------------------------------------------------------------------------------- /lib/app/providers/open_ai/chat_gpt_4.dart: -------------------------------------------------------------------------------- 1 | import 'package:answer/app/providers/open_ai/chat_gpt.dart'; 2 | 3 | class ChatGpt4 extends ChatGpt { 4 | ChatGpt4({ 5 | super.model = 'gpt-4-0314', 6 | super.id = 'open_ai_chat_gpt_4', 7 | super.name = 'GPT-4', 8 | super.avatar = 'assets/images/open_ai_gpt_4.svg', 9 | super.block = true, 10 | }); 11 | } 12 | -------------------------------------------------------------------------------- /lib/app/providers/service_vendor.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | 3 | class ServiceVendor { 4 | String id; 5 | String name; 6 | String? avatar; 7 | String? officialUrl; 8 | String? apiUrl; 9 | String? editApiUrl; 10 | String? hello; 11 | String? help; 12 | String? helpUrl; 13 | 14 | String? get url => editApiUrl ?? apiUrl; 15 | 16 | ServiceVendor({ 17 | required this.id, 18 | required this.name, 19 | required this.avatar, 20 | required this.officialUrl, 21 | required this.apiUrl, 22 | required this.hello, 23 | this.editApiUrl, 24 | required this.help, 25 | required this.helpUrl, 26 | }); 27 | 28 | factory ServiceVendor.fromRawJson(String str) => 29 | ServiceVendor.fromJson(json.decode(str)); 30 | 31 | String toRawJson() => json.encode(toJson()); 32 | 33 | ServiceVendor copyWith({ 34 | String? id, 35 | String? name, 36 | String? avatar, 37 | String? officialUrl, 38 | String? apiUrl, 39 | String? help, 40 | String? helpUrl, 41 | String? hello, 42 | String? editApiUrl, 43 | Map? map, 44 | }) => 45 | ServiceVendor( 46 | id: id ?? map?['id'] ?? this.id, 47 | name: name ?? map?['name'] ?? this.name, 48 | avatar: avatar ?? map?['avatar'] ?? this.avatar, 49 | officialUrl: officialUrl ?? map?['official_url'] ?? this.officialUrl, 50 | apiUrl: apiUrl ?? map?['api_url'] ?? this.apiUrl, 51 | help: help ?? map?['help'] ?? this.help, 52 | hello: hello ?? map?['hello'] ?? this.hello, 53 | helpUrl: helpUrl ?? map?['help_url'] ?? this.helpUrl, 54 | editApiUrl: editApiUrl ?? map?['edit_api_url'] ?? this.editApiUrl, 55 | ); 56 | 57 | factory ServiceVendor.fromJson(Map json) { 58 | return ServiceVendor( 59 | id: json["id"], 60 | name: json["name"], 61 | avatar: json["avatar"], 62 | officialUrl: json["official_url"], 63 | apiUrl: json["api_url"], 64 | editApiUrl: json['edit_api_url'], 65 | hello: json['hello'], 66 | help: json["help"], 67 | helpUrl: json["help_url"], 68 | ); 69 | } 70 | 71 | Map toJson() => { 72 | "id": id, 73 | "name": name, 74 | "avatar": avatar, 75 | "official_url": officialUrl, 76 | "api_url": apiUrl, 77 | "hello": hello, 78 | "help": help, 79 | "help_url": helpUrl, 80 | "edit_api_url": editApiUrl, 81 | }; 82 | } 83 | -------------------------------------------------------------------------------- /lib/app/routes/app_pages.dart: -------------------------------------------------------------------------------- 1 | import 'package:get/get.dart'; 2 | 3 | import '../modules/conversation/bindings/conversation_binding.dart'; 4 | import '../modules/conversation/views/conversation_view.dart'; 5 | import '../modules/home/bindings/home_binding.dart'; 6 | import '../modules/home/views/home_view.dart'; 7 | import '../modules/new_prompt/bindings/new_prompt_binding.dart'; 8 | import '../modules/new_prompt/views/new_prompt_view.dart'; 9 | import '../modules/prompt/bindings/prompt_binding.dart'; 10 | import '../modules/prompt/views/prompt_view.dart'; 11 | import '../modules/service/bindings/service_binding.dart'; 12 | import '../modules/service/views/service_view.dart'; 13 | import '../modules/settings/bindings/settings_binding.dart'; 14 | import '../modules/settings/views/settings_view.dart'; 15 | import '../modules/vendor/bindings/vendor_binding.dart'; 16 | import '../modules/vendor/views/vendor_view.dart'; 17 | 18 | part 'app_routes.dart'; 19 | 20 | class AppPages { 21 | AppPages._(); 22 | 23 | static const initial = Routes.home; 24 | 25 | static final routes = [ 26 | GetPage( 27 | name: _Paths.home, 28 | page: () => const HomeView(), 29 | binding: HomeBinding(), 30 | ), 31 | GetPage( 32 | name: _Paths.settings, 33 | page: () => const SettingsView(), 34 | binding: SettingsBinding(), 35 | ), 36 | GetPage( 37 | name: _Paths.service, 38 | page: () => const ServiceView(), 39 | binding: ServiceBinding(), 40 | ), 41 | GetPage( 42 | name: _Paths.conversation, 43 | page: () => const ConversationView(), 44 | binding: ConversationBinding(), 45 | ), 46 | GetPage( 47 | name: _Paths.prompt, 48 | page: () => const PromptView(), 49 | binding: PromptBinding(), 50 | ), 51 | GetPage( 52 | name: _Paths.newPrompt, 53 | page: () => const NewPromptView(), 54 | binding: NewPromptBinding(), 55 | fullscreenDialog: true, 56 | ), 57 | GetPage( 58 | name: _Paths.vendor, 59 | page: () => const VendorView(), 60 | binding: VendorBinding(), 61 | ), 62 | ]; 63 | } 64 | -------------------------------------------------------------------------------- /lib/app/routes/app_routes.dart: -------------------------------------------------------------------------------- 1 | part of 'app_pages.dart'; 2 | // DO NOT EDIT. This is code generated via package:get_cli/get_cli.dart 3 | 4 | abstract class Routes { 5 | Routes._(); 6 | static const home = _Paths.home; 7 | static const settings = _Paths.settings; 8 | static const service = _Paths.service; 9 | static const conversation = _Paths.conversation; 10 | static const prompt = _Paths.prompt; 11 | static const newPrompt = _Paths.newPrompt; 12 | static const vendor = _Paths.vendor; 13 | } 14 | 15 | abstract class _Paths { 16 | _Paths._(); 17 | static const home = '/'; 18 | static const settings = '/settings'; 19 | static const service = '/service'; 20 | static const conversation = '/conversation'; 21 | static const prompt = '/prompt'; 22 | static const newPrompt = '/new-prompt'; 23 | static const vendor = '/vendor'; 24 | } 25 | -------------------------------------------------------------------------------- /lib/app/views/app_section.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | 3 | class AppSection extends StatelessWidget { 4 | final Widget title; 5 | final List? actions; 6 | final EdgeInsetsGeometry? padding; 7 | const AppSection({ 8 | Key? key, 9 | required this.title, 10 | this.actions, 11 | this.padding, 12 | }) : super(key: key); 13 | 14 | @override 15 | Widget build(BuildContext context) { 16 | return Container( 17 | padding: padding ?? 18 | const EdgeInsets.only( 19 | top: 16, 20 | left: 16, 21 | bottom: 8, 22 | right: 16, 23 | ), 24 | child: Row( 25 | children: [ 26 | Expanded(child: title), 27 | if (actions != null) ...actions!, 28 | ], 29 | ), 30 | ); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /lib/app/views/chat_avatar.dart: -------------------------------------------------------------------------------- 1 | import 'dart:math'; 2 | 3 | import 'package:cached_network_image/cached_network_image.dart'; 4 | import 'package:flutter/material.dart'; 5 | import 'package:flutter_svg/flutter_svg.dart'; 6 | 7 | class ChatAvatar extends StatelessWidget { 8 | final String? path; 9 | final double width; 10 | final double height; 11 | final Radius? radius; 12 | final BoxFit? fit; 13 | final EdgeInsetsGeometry? padding; 14 | final Color? backgroundColor; 15 | final Color? color; 16 | const ChatAvatar({ 17 | Key? key, 18 | required this.path, 19 | this.width = 44, 20 | this.height = 44, 21 | this.radius = const Radius.circular(8), 22 | this.fit = BoxFit.cover, 23 | this.padding, 24 | this.backgroundColor, 25 | this.color, 26 | }) : super(key: key); 27 | 28 | @override 29 | Widget build(BuildContext context) { 30 | Widget? widget; 31 | if (path != null && path!.startsWith('assets/images/')) { 32 | if (path!.endsWith('.svg')) { 33 | widget = SvgPicture.asset( 34 | path!, 35 | width: width - (padding?.horizontal ?? 0), 36 | height: height - (padding?.vertical ?? 0), 37 | colorFilter: 38 | color != null ? ColorFilter.mode(color!, BlendMode.srcIn) : null, 39 | placeholderBuilder: (context) => _buildPlaceholder(context), 40 | ); 41 | } else { 42 | widget = Image.asset(path!); 43 | } 44 | } else if (path != null && path!.startsWith('http')) { 45 | widget = CachedNetworkImage( 46 | imageUrl: path!, 47 | fit: fit, 48 | width: width, 49 | height: height, 50 | placeholder: (context, url) => _buildPlaceholder(context), 51 | errorWidget: (context, url, error) => _buildPlaceholder(context), 52 | ); 53 | } 54 | widget ??= _buildPlaceholder(context); 55 | 56 | return Container( 57 | width: width, 58 | height: height, 59 | padding: padding, 60 | clipBehavior: Clip.hardEdge, 61 | decoration: BoxDecoration( 62 | borderRadius: radius == null ? null : BorderRadius.all(radius!), 63 | color: backgroundColor, 64 | ), 65 | child: widget, 66 | ); 67 | } 68 | 69 | Widget _buildPlaceholder(BuildContext context) { 70 | return Container( 71 | width: width, 72 | height: height, 73 | color: Theme.of(context).scaffoldBackgroundColor, 74 | child: Center( 75 | child: Icon( 76 | Icons.image_outlined, 77 | size: min(width, height) - 16, 78 | color: Colors.white70, 79 | ), 80 | ), 81 | ); 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /lib/app/views/chat_loading_item_view.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | import 'chat_base_item_view.dart'; 4 | 5 | class ChatLoadingItemView extends ChatBaseItemView { 6 | const ChatLoadingItemView({ 7 | super.key, 8 | required super.message, 9 | required super.onAvatarClicked, 10 | }) : super(); 11 | 12 | @override 13 | Widget buildContent(BuildContext context) => SizedBox( 14 | width: 22, 15 | height: 22, 16 | child: CircularProgressIndicator( 17 | color: Theme.of(context).primaryColor, 18 | strokeWidth: 2, 19 | ), 20 | ); 21 | } 22 | -------------------------------------------------------------------------------- /lib/app/views/chat_text_receive_item_view.dart: -------------------------------------------------------------------------------- 1 | import 'package:answer/app/routes/app_pages.dart'; 2 | import 'package:answer/app/views/chat_base_item_view.dart'; 3 | import 'package:flutter/material.dart'; 4 | import 'package:flutter_markdown/flutter_markdown.dart'; 5 | import 'package:get/get.dart'; 6 | import 'package:markdown/markdown.dart' as md; 7 | import 'package:url_launcher/url_launcher_string.dart'; 8 | 9 | import 'code_element_builder.dart'; 10 | 11 | class ChatTextItemView extends ChatBaseItemView { 12 | const ChatTextItemView({ 13 | super.key, 14 | required super.message, 15 | required super.onRetried, 16 | required super.onAvatarClicked, 17 | required super.onQuoted, 18 | }) : super(); 19 | 20 | // @override 21 | // Widget buildContent(BuildContext context) => SelectableText( 22 | // message.content ?? '', 23 | // style: Theme.of(context).textTheme.titleMedium, 24 | // ); 25 | 26 | @override 27 | Widget buildContent(BuildContext context) => MarkdownBody( 28 | data: message.content ?? '', 29 | selectable: true, 30 | extensionSet: md.ExtensionSet.gitHubWeb, 31 | builders: { 32 | 'code': CodeElementBuilder(), 33 | }, 34 | onTapLink: (text, href, title) { 35 | if (href == null) return; 36 | if (href.startsWith('http')) { 37 | launchUrlString(href); 38 | } else if (AppPages.routes.firstWhereOrNull( 39 | (element) => href.startsWith(element.name)) != 40 | null) { 41 | Get.toNamed(href); 42 | } 43 | }, 44 | // md.ExtensionSet( 45 | // md.ExtensionSet.gitHubFlavored.blockSyntaxes, 46 | // [md.EmojiSyntax(), ...md.ExtensionSet.gitHubFlavored.inlineSyntaxes], 47 | // ), 48 | ); 49 | } 50 | -------------------------------------------------------------------------------- /lib/app/views/chat_view.dart: -------------------------------------------------------------------------------- 1 | import 'package:answer/app/views/chat_loading_item_view.dart'; 2 | import 'package:answer/app/views/chat_text_receive_item_view.dart'; 3 | import 'package:flutter/material.dart'; 4 | 5 | import '../data/models/message.dart'; 6 | 7 | class ChatView extends StatelessWidget { 8 | final List messages; 9 | final ScrollController controller; 10 | final ValueChanged onRetried; 11 | final ValueChanged onAvatarClicked; 12 | final ValueChanged? onQuoted; 13 | 14 | const ChatView({ 15 | Key? key, 16 | required this.messages, 17 | required this.controller, 18 | required this.onRetried, 19 | required this.onAvatarClicked, 20 | required this.onQuoted, 21 | }) : super(key: key); 22 | 23 | @override 24 | Widget build(BuildContext context) { 25 | return Scrollbar( 26 | controller: controller, 27 | child: Align( 28 | alignment: Alignment.topCenter, 29 | child: ListView.builder( 30 | reverse: true, 31 | shrinkWrap: true, 32 | controller: controller, 33 | itemCount: messages.length, 34 | itemBuilder: (context, index) { 35 | switch (messages[index].type) { 36 | case MessageType.loading: 37 | return ChatLoadingItemView( 38 | message: messages[index], 39 | onAvatarClicked: onAvatarClicked, 40 | ); 41 | default: 42 | return ChatTextItemView( 43 | message: messages[index], 44 | onRetried: onRetried, 45 | onAvatarClicked: onAvatarClicked, 46 | onQuoted: onQuoted, 47 | ); 48 | } 49 | }, 50 | keyboardDismissBehavior: ScrollViewKeyboardDismissBehavior.onDrag, 51 | ), 52 | ), 53 | ); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /lib/app/views/code_element_builder.dart: -------------------------------------------------------------------------------- 1 | import 'package:answer/app/core/app/app_manager.dart'; 2 | import 'package:answer/app/views/code_highlight_view.dart'; 3 | import 'package:flutter/material.dart'; 4 | import 'package:flutter/widgets.dart'; 5 | import 'package:flutter_highlight/themes/atom-one-dark.dart'; 6 | import 'package:flutter_highlight/themes/atom-one-light.dart'; 7 | import 'package:flutter_markdown/flutter_markdown.dart'; 8 | import 'package:markdown/markdown.dart' as md; 9 | 10 | class CodeElementBuilder extends MarkdownElementBuilder { 11 | @override 12 | Widget? visitElementAfter(md.Element element, TextStyle? preferredStyle) { 13 | var language = ''; 14 | 15 | if (element.attributes['class'] != null) { 16 | String lg = element.attributes['class'] as String; 17 | language = lg.substring(9); 18 | } 19 | final theme = 20 | AppManager.to.isLightMode ? atomOneLightTheme : atomOneDarkTheme; 21 | return CodeHighlightView( 22 | code: element.textContent, 23 | language: language, 24 | theme: theme, 25 | padding: const EdgeInsets.all(8), 26 | ); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /lib/app/views/code_highlight_view.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:highlight/highlight.dart' show highlight, Node; 3 | 4 | class CodeHighlightView extends StatelessWidget { 5 | /// The original code to be highlighted 6 | final String source; 7 | 8 | /// Highlight language 9 | /// 10 | /// It is recommended to give it a value for performance 11 | /// 12 | /// [All available languages](https://github.com/pd4d10/highlight/tree/master/highlight/lib/languages) 13 | final String? language; 14 | 15 | /// Highlight theme 16 | /// 17 | /// [All available themes](https://github.com/pd4d10/highlight/blob/master/flutter_highlight/lib/themes) 18 | final Map theme; 19 | 20 | /// Padding 21 | final EdgeInsetsGeometry? padding; 22 | 23 | CodeHighlightView({ 24 | super.key, 25 | required String code, 26 | this.language, 27 | this.theme = const {}, 28 | this.padding, 29 | int tabSize = 8, // TODO: https://github.com/flutter/flutter/issues/50087 30 | }) : source = code.replaceAll('\t', ' ' * tabSize); 31 | 32 | List _convert(List nodes) { 33 | List spans = []; 34 | var currentSpans = spans; 35 | List> stack = []; 36 | 37 | _traverse(Node node) { 38 | if (node.value != null) { 39 | currentSpans.add(node.className == null 40 | ? TextSpan(text: node.value) 41 | : TextSpan(text: node.value, style: theme[node.className!])); 42 | } else if (node.children != null) { 43 | List tmp = []; 44 | currentSpans 45 | .add(TextSpan(children: tmp, style: theme[node.className!])); 46 | stack.add(currentSpans); 47 | currentSpans = tmp; 48 | 49 | for (var n in node.children!) { 50 | _traverse(n); 51 | if (n == node.children!.last) { 52 | currentSpans = stack.isEmpty ? spans : stack.removeLast(); 53 | } 54 | } 55 | } 56 | } 57 | 58 | for (var node in nodes) { 59 | _traverse(node); 60 | } 61 | 62 | return spans; 63 | } 64 | 65 | static const _rootKey = 'root'; 66 | static const _defaultFontColor = Color(0xff000000); 67 | static const _defaultBackgroundColor = Color(0xffffffff); 68 | 69 | // TODO: dart:io is not available at web platform currently 70 | // See: https://github.com/flutter/flutter/issues/39998 71 | // So we just use monospace here for now 72 | static const _defaultFontFamily = 'monospace'; 73 | 74 | @override 75 | Widget build(BuildContext context) { 76 | final result = highlight.parse(source, language: language); 77 | return Container( 78 | decoration: BoxDecoration( 79 | borderRadius: BorderRadius.circular(8), 80 | color: theme[_rootKey]?.backgroundColor ?? _defaultBackgroundColor, 81 | ), 82 | width: MediaQuery.of(context).size.width, 83 | padding: padding, 84 | child: SingleChildScrollView( 85 | scrollDirection: Axis.horizontal, 86 | child: RichText( 87 | text: TextSpan( 88 | style: TextStyle( 89 | fontFamily: _defaultFontFamily, 90 | color: theme[_rootKey]?.color ?? _defaultFontColor, 91 | ), 92 | children: _convert(result.nodes!), 93 | ), 94 | ), 95 | ), 96 | ); 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /lib/flavors/build_config.dart: -------------------------------------------------------------------------------- 1 | import 'package:answer/app/core/app/app_http.dart'; 2 | import 'package:answer/app/data/db/app_database.dart'; 3 | import 'package:answer/app/providers/service_provider_manager.dart'; 4 | 5 | import '../app/core/app/app_manager.dart'; 6 | import 'env_config.dart'; 7 | import 'environment.dart'; 8 | 9 | class BuildConfig { 10 | late final Environment environment; 11 | late final EnvConfig config; 12 | bool _lock = false; 13 | 14 | static final BuildConfig instance = BuildConfig._internal(); 15 | 16 | BuildConfig._internal(); 17 | 18 | static Future initialize({ 19 | required Environment envType, 20 | required EnvConfig envConfig, 21 | }) async { 22 | if (instance._lock) return; 23 | 24 | instance.environment = envType; 25 | instance.config = envConfig; 26 | instance._lock = true; 27 | 28 | // if (envConfig.firebaseOptions != null && 29 | // instance.environment == Environment.dev) { 30 | // await Firebase.initializeApp( 31 | // options: envConfig.firebaseOptions, 32 | // ); 33 | // } 34 | await AppHttp.instantiate( 35 | httpOverrides: envConfig.httpOverrides, 36 | ); 37 | await AppManager.initialize(); 38 | await AppDatabase.initialize( 39 | dbName: envConfig.dbName, 40 | ); 41 | await ServiceProviderManager.initialize(); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /lib/flavors/env_config.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | class EnvConfig { 4 | final String appName; 5 | final String dbName; 6 | final bool shouldCollectCrashLog; 7 | final HttpOverrides? httpOverrides; 8 | final String? openAIUrl; 9 | final String? openAIApiKey; 10 | 11 | EnvConfig({ 12 | required this.appName, 13 | required this.dbName, 14 | this.shouldCollectCrashLog = false, 15 | this.httpOverrides, 16 | this.openAIUrl, 17 | this.openAIApiKey, 18 | }); 19 | } 20 | -------------------------------------------------------------------------------- /lib/flavors/environment.dart: -------------------------------------------------------------------------------- 1 | enum Environment { dev, prod } 2 | -------------------------------------------------------------------------------- /lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | import 'package:flutter/material.dart'; 4 | import 'package:flutter/services.dart'; 5 | import 'package:flutter_native_splash/flutter_native_splash.dart'; 6 | 7 | import '/app/app.dart'; 8 | import '/flavors/build_config.dart'; 9 | import '/flavors/env_config.dart'; 10 | import '/flavors/environment.dart'; 11 | 12 | Future main() async { 13 | WidgetsBinding widgetsBinding = WidgetsFlutterBinding.ensureInitialized(); 14 | FlutterNativeSplash.preserve(widgetsBinding: widgetsBinding); 15 | 16 | if (Platform.isAndroid) { 17 | SystemUiOverlayStyle systemUiOverlayStyle = const SystemUiOverlayStyle( 18 | statusBarColor: Colors.transparent, 19 | ); 20 | SystemChrome.setSystemUIOverlayStyle(systemUiOverlayStyle); 21 | } 22 | 23 | EnvConfig prodConfig = EnvConfig( 24 | appName: 'Chat Answer', 25 | dbName: 'db.sqlite', 26 | shouldCollectCrashLog: false, 27 | ); 28 | 29 | await BuildConfig.initialize( 30 | envType: Environment.prod, 31 | envConfig: prodConfig, 32 | ); 33 | 34 | runApp(const App()); 35 | } 36 | -------------------------------------------------------------------------------- /linux/.gitignore: -------------------------------------------------------------------------------- 1 | flutter/ephemeral 2 | -------------------------------------------------------------------------------- /linux/flutter/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # This file controls Flutter-level build steps. It should not be edited. 2 | cmake_minimum_required(VERSION 3.10) 3 | 4 | set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral") 5 | 6 | # Configuration provided via flutter tool. 7 | include(${EPHEMERAL_DIR}/generated_config.cmake) 8 | 9 | # TODO: Move the rest of this into files in ephemeral. See 10 | # https://github.com/flutter/flutter/issues/57146. 11 | 12 | # Serves the same purpose as list(TRANSFORM ... PREPEND ...), 13 | # which isn't available in 3.10. 14 | function(list_prepend LIST_NAME PREFIX) 15 | set(NEW_LIST "") 16 | foreach(element ${${LIST_NAME}}) 17 | list(APPEND NEW_LIST "${PREFIX}${element}") 18 | endforeach(element) 19 | set(${LIST_NAME} "${NEW_LIST}" PARENT_SCOPE) 20 | endfunction() 21 | 22 | # === Flutter Library === 23 | # System-level dependencies. 24 | find_package(PkgConfig REQUIRED) 25 | pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0) 26 | pkg_check_modules(GLIB REQUIRED IMPORTED_TARGET glib-2.0) 27 | pkg_check_modules(GIO REQUIRED IMPORTED_TARGET gio-2.0) 28 | 29 | set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/libflutter_linux_gtk.so") 30 | 31 | # Published to parent scope for install step. 32 | set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE) 33 | set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE) 34 | set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE) 35 | set(AOT_LIBRARY "${PROJECT_DIR}/build/lib/libapp.so" PARENT_SCOPE) 36 | 37 | list(APPEND FLUTTER_LIBRARY_HEADERS 38 | "fl_basic_message_channel.h" 39 | "fl_binary_codec.h" 40 | "fl_binary_messenger.h" 41 | "fl_dart_project.h" 42 | "fl_engine.h" 43 | "fl_json_message_codec.h" 44 | "fl_json_method_codec.h" 45 | "fl_message_codec.h" 46 | "fl_method_call.h" 47 | "fl_method_channel.h" 48 | "fl_method_codec.h" 49 | "fl_method_response.h" 50 | "fl_plugin_registrar.h" 51 | "fl_plugin_registry.h" 52 | "fl_standard_message_codec.h" 53 | "fl_standard_method_codec.h" 54 | "fl_string_codec.h" 55 | "fl_value.h" 56 | "fl_view.h" 57 | "flutter_linux.h" 58 | ) 59 | list_prepend(FLUTTER_LIBRARY_HEADERS "${EPHEMERAL_DIR}/flutter_linux/") 60 | add_library(flutter INTERFACE) 61 | target_include_directories(flutter INTERFACE 62 | "${EPHEMERAL_DIR}" 63 | ) 64 | target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}") 65 | target_link_libraries(flutter INTERFACE 66 | PkgConfig::GTK 67 | PkgConfig::GLIB 68 | PkgConfig::GIO 69 | ) 70 | add_dependencies(flutter flutter_assemble) 71 | 72 | # === Flutter tool backend === 73 | # _phony_ is a non-existent file to force this command to run every time, 74 | # since currently there's no way to get a full input/output list from the 75 | # flutter tool. 76 | add_custom_command( 77 | OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS} 78 | ${CMAKE_CURRENT_BINARY_DIR}/_phony_ 79 | COMMAND ${CMAKE_COMMAND} -E env 80 | ${FLUTTER_TOOL_ENVIRONMENT} 81 | "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.sh" 82 | ${FLUTTER_TARGET_PLATFORM} ${CMAKE_BUILD_TYPE} 83 | VERBATIM 84 | ) 85 | add_custom_target(flutter_assemble DEPENDS 86 | "${FLUTTER_LIBRARY}" 87 | ${FLUTTER_LIBRARY_HEADERS} 88 | ) 89 | -------------------------------------------------------------------------------- /linux/flutter/generated_plugin_registrant.cc: -------------------------------------------------------------------------------- 1 | // 2 | // Generated file. Do not edit. 3 | // 4 | 5 | // clang-format off 6 | 7 | #include "generated_plugin_registrant.h" 8 | 9 | #include 10 | 11 | void fl_register_plugins(FlPluginRegistry* registry) { 12 | g_autoptr(FlPluginRegistrar) url_launcher_linux_registrar = 13 | fl_plugin_registry_get_registrar_for_plugin(registry, "UrlLauncherPlugin"); 14 | url_launcher_plugin_register_with_registrar(url_launcher_linux_registrar); 15 | } 16 | -------------------------------------------------------------------------------- /linux/flutter/generated_plugin_registrant.h: -------------------------------------------------------------------------------- 1 | // 2 | // Generated file. Do not edit. 3 | // 4 | 5 | // clang-format off 6 | 7 | #ifndef GENERATED_PLUGIN_REGISTRANT_ 8 | #define GENERATED_PLUGIN_REGISTRANT_ 9 | 10 | #include 11 | 12 | // Registers Flutter plugins. 13 | void fl_register_plugins(FlPluginRegistry* registry); 14 | 15 | #endif // GENERATED_PLUGIN_REGISTRANT_ 16 | -------------------------------------------------------------------------------- /linux/flutter/generated_plugins.cmake: -------------------------------------------------------------------------------- 1 | # 2 | # Generated file, do not edit. 3 | # 4 | 5 | list(APPEND FLUTTER_PLUGIN_LIST 6 | url_launcher_linux 7 | ) 8 | 9 | list(APPEND FLUTTER_FFI_PLUGIN_LIST 10 | ) 11 | 12 | set(PLUGIN_BUNDLED_LIBRARIES) 13 | 14 | foreach(plugin ${FLUTTER_PLUGIN_LIST}) 15 | add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/linux plugins/${plugin}) 16 | target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) 17 | list(APPEND PLUGIN_BUNDLED_LIBRARIES $) 18 | list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) 19 | endforeach(plugin) 20 | 21 | foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) 22 | add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/linux plugins/${ffi_plugin}) 23 | list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) 24 | endforeach(ffi_plugin) 25 | -------------------------------------------------------------------------------- /linux/main.cc: -------------------------------------------------------------------------------- 1 | #include "my_application.h" 2 | 3 | int main(int argc, char** argv) { 4 | g_autoptr(MyApplication) app = my_application_new(); 5 | return g_application_run(G_APPLICATION(app), argc, argv); 6 | } 7 | -------------------------------------------------------------------------------- /linux/my_application.h: -------------------------------------------------------------------------------- 1 | #ifndef FLUTTER_MY_APPLICATION_H_ 2 | #define FLUTTER_MY_APPLICATION_H_ 3 | 4 | #include 5 | 6 | G_DECLARE_FINAL_TYPE(MyApplication, my_application, MY, APPLICATION, 7 | GtkApplication) 8 | 9 | /** 10 | * my_application_new: 11 | * 12 | * Creates a new Flutter-based application. 13 | * 14 | * Returns: a new #MyApplication. 15 | */ 16 | MyApplication* my_application_new(); 17 | 18 | #endif // FLUTTER_MY_APPLICATION_H_ 19 | -------------------------------------------------------------------------------- /macos/.gitignore: -------------------------------------------------------------------------------- 1 | # Flutter-related 2 | **/Flutter/ephemeral/ 3 | **/Pods/ 4 | 5 | # Xcode-related 6 | **/dgph 7 | **/xcuserdata/ 8 | -------------------------------------------------------------------------------- /macos/Flutter/Flutter-Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" 2 | #include "ephemeral/Flutter-Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /macos/Flutter/Flutter-Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" 2 | #include "ephemeral/Flutter-Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /macos/Flutter/GeneratedPluginRegistrant.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Generated file. Do not edit. 3 | // 4 | 5 | import FlutterMacOS 6 | import Foundation 7 | 8 | import in_app_review 9 | import path_provider_foundation 10 | import share_plus 11 | import shared_preferences_foundation 12 | import sqflite 13 | import url_launcher_macos 14 | 15 | func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { 16 | InAppReviewPlugin.register(with: registry.registrar(forPlugin: "InAppReviewPlugin")) 17 | PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) 18 | SharePlusMacosPlugin.register(with: registry.registrar(forPlugin: "SharePlusMacosPlugin")) 19 | SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) 20 | SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin")) 21 | UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin")) 22 | } 23 | -------------------------------------------------------------------------------- /macos/Podfile: -------------------------------------------------------------------------------- 1 | platform :osx, '10.11' 2 | 3 | # CocoaPods analytics sends network stats synchronously affecting flutter build latency. 4 | ENV['COCOAPODS_DISABLE_STATS'] = 'true' 5 | 6 | project 'Runner', { 7 | 'Debug' => :debug, 8 | 'Profile' => :release, 9 | 'Release' => :release, 10 | } 11 | 12 | def flutter_root 13 | generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'ephemeral', 'Flutter-Generated.xcconfig'), __FILE__) 14 | unless File.exist?(generated_xcode_build_settings_path) 15 | raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure \"flutter pub get\" is executed first" 16 | end 17 | 18 | File.foreach(generated_xcode_build_settings_path) do |line| 19 | matches = line.match(/FLUTTER_ROOT\=(.*)/) 20 | return matches[1].strip if matches 21 | end 22 | raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Flutter-Generated.xcconfig, then run \"flutter pub get\"" 23 | end 24 | 25 | require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) 26 | 27 | flutter_macos_podfile_setup 28 | 29 | target 'Runner' do 30 | use_frameworks! 31 | use_modular_headers! 32 | 33 | flutter_install_all_macos_pods File.dirname(File.realpath(__FILE__)) 34 | end 35 | 36 | post_install do |installer| 37 | installer.pods_project.targets.each do |target| 38 | flutter_additional_macos_build_settings(target) 39 | end 40 | end 41 | -------------------------------------------------------------------------------- /macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 37 | 38 | 39 | 40 | 41 | 42 | 52 | 54 | 60 | 61 | 62 | 63 | 69 | 71 | 77 | 78 | 79 | 80 | 82 | 83 | 86 | 87 | 88 | -------------------------------------------------------------------------------- /macos/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /macos/Runner/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import Cocoa 2 | import FlutterMacOS 3 | 4 | @NSApplicationMain 5 | class AppDelegate: FlutterAppDelegate { 6 | override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { 7 | return true 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info": { 3 | "version": 1, 4 | "author": "xcode" 5 | }, 6 | "images": [ 7 | { 8 | "size": "16x16", 9 | "idiom": "mac", 10 | "filename": "app_icon_16.png", 11 | "scale": "1x" 12 | }, 13 | { 14 | "size": "16x16", 15 | "idiom": "mac", 16 | "filename": "app_icon_32.png", 17 | "scale": "2x" 18 | }, 19 | { 20 | "size": "32x32", 21 | "idiom": "mac", 22 | "filename": "app_icon_32.png", 23 | "scale": "1x" 24 | }, 25 | { 26 | "size": "32x32", 27 | "idiom": "mac", 28 | "filename": "app_icon_64.png", 29 | "scale": "2x" 30 | }, 31 | { 32 | "size": "128x128", 33 | "idiom": "mac", 34 | "filename": "app_icon_128.png", 35 | "scale": "1x" 36 | }, 37 | { 38 | "size": "128x128", 39 | "idiom": "mac", 40 | "filename": "app_icon_256.png", 41 | "scale": "2x" 42 | }, 43 | { 44 | "size": "256x256", 45 | "idiom": "mac", 46 | "filename": "app_icon_256.png", 47 | "scale": "1x" 48 | }, 49 | { 50 | "size": "256x256", 51 | "idiom": "mac", 52 | "filename": "app_icon_512.png", 53 | "scale": "2x" 54 | }, 55 | { 56 | "size": "512x512", 57 | "idiom": "mac", 58 | "filename": "app_icon_512.png", 59 | "scale": "1x" 60 | }, 61 | { 62 | "size": "512x512", 63 | "idiom": "mac", 64 | "filename": "app_icon_1024.png", 65 | "scale": "2x" 66 | } 67 | ] 68 | } -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bapaws/answer/6c65fced23f96186f330d7704ff5b49107f83a83/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bapaws/answer/6c65fced23f96186f330d7704ff5b49107f83a83/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bapaws/answer/6c65fced23f96186f330d7704ff5b49107f83a83/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bapaws/answer/6c65fced23f96186f330d7704ff5b49107f83a83/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bapaws/answer/6c65fced23f96186f330d7704ff5b49107f83a83/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bapaws/answer/6c65fced23f96186f330d7704ff5b49107f83a83/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bapaws/answer/6c65fced23f96186f330d7704ff5b49107f83a83/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png -------------------------------------------------------------------------------- /macos/Runner/Configs/AppInfo.xcconfig: -------------------------------------------------------------------------------- 1 | // Application-level settings for the Runner target. 2 | // 3 | // This may be replaced with something auto-generated from metadata (e.g., pubspec.yaml) in the 4 | // future. If not, the values below would default to using the project name when this becomes a 5 | // 'flutter create' template. 6 | 7 | // The application's name. By default this is also the title of the Flutter window. 8 | PRODUCT_NAME = Answer; 9 | 10 | // The application's bundle identifier 11 | PRODUCT_BUNDLE_IDENTIFIER = com.bapaws.answer; 12 | 13 | // The copyright displayed in application information 14 | PRODUCT_COPYRIGHT = Copyright © 2023 com.bapaws. All rights reserved. 15 | -------------------------------------------------------------------------------- /macos/Runner/Configs/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include "../../Flutter/Flutter-Debug.xcconfig" 2 | #include "Warnings.xcconfig" 3 | -------------------------------------------------------------------------------- /macos/Runner/Configs/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include "../../Flutter/Flutter-Release.xcconfig" 2 | #include "Warnings.xcconfig" 3 | -------------------------------------------------------------------------------- /macos/Runner/Configs/Warnings.xcconfig: -------------------------------------------------------------------------------- 1 | WARNING_CFLAGS = -Wall -Wconditional-uninitialized -Wnullable-to-nonnull-conversion -Wmissing-method-return-type -Woverlength-strings 2 | GCC_WARN_UNDECLARED_SELECTOR = YES 3 | CLANG_UNDEFINED_BEHAVIOR_SANITIZER_NULLABILITY = YES 4 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE 5 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES 6 | CLANG_WARN_PRAGMA_PACK = YES 7 | CLANG_WARN_STRICT_PROTOTYPES = YES 8 | CLANG_WARN_COMMA = YES 9 | GCC_WARN_STRICT_SELECTOR_MATCH = YES 10 | CLANG_WARN_OBJC_REPEATED_USE_OF_WEAK = YES 11 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES 12 | GCC_WARN_SHADOW = YES 13 | CLANG_WARN_UNREACHABLE_CODE = YES 14 | -------------------------------------------------------------------------------- /macos/Runner/DebugProfile.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.app-sandbox 6 | 7 | com.apple.security.cs.allow-jit 8 | 9 | com.apple.security.network.server 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /macos/Runner/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIconFile 10 | 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | $(FLUTTER_BUILD_NAME) 21 | CFBundleVersion 22 | $(FLUTTER_BUILD_NUMBER) 23 | LSMinimumSystemVersion 24 | $(MACOSX_DEPLOYMENT_TARGET) 25 | NSHumanReadableCopyright 26 | $(PRODUCT_COPYRIGHT) 27 | NSMainNibFile 28 | MainMenu 29 | NSPrincipalClass 30 | NSApplication 31 | 32 | 33 | -------------------------------------------------------------------------------- /macos/Runner/MainFlutterWindow.swift: -------------------------------------------------------------------------------- 1 | import Cocoa 2 | import FlutterMacOS 3 | 4 | class MainFlutterWindow: NSWindow { 5 | override func awakeFromNib() { 6 | let flutterViewController = FlutterViewController.init() 7 | let windowFrame = self.frame 8 | self.contentViewController = flutterViewController 9 | self.setFrame(windowFrame, display: true) 10 | 11 | RegisterGeneratedPlugins(registry: flutterViewController) 12 | 13 | super.awakeFromNib() 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /macos/Runner/Release.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.app-sandbox 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /macos/firebase_app_id_file.json: -------------------------------------------------------------------------------- 1 | { 2 | "file_generated_by": "FlutterFire CLI", 3 | "purpose": "FirebaseAppID & ProjectID for this Firebase app in this directory", 4 | "GOOGLE_APP_ID": "1:900023574858:ios:fc3b19ce2abf49928161b3", 5 | "FIREBASE_PROJECT_ID": "free-ai", 6 | "GCM_SENDER_ID": "900023574858" 7 | } -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: answer 2 | version: 1.1.1+16 3 | publish_to: none 4 | description: A new Flutter project. 5 | environment: 6 | sdk: '>=2.18.1 <3.0.0' 7 | 8 | dependencies: 9 | cached_network_image: ^3.2.3 10 | chat_gpt_sdk: ^2.0.1 11 | collection: ^1.16.0 12 | cupertino_icons: ^1.0.2 13 | dio: ^5.0.1 14 | flutter: 15 | sdk: flutter 16 | flutter_cache_manager: ^3.3.0 17 | flutter_easyloading: ^3.0.5 18 | flutter_highlight: ^0.7.0 19 | flutter_launcher_icons: ^0.12.0 20 | flutter_mailer: ^2.0.2 21 | flutter_markdown: ^0.6.14 22 | flutter_native_splash: ^2.2.18 23 | flutter_svg: ^2.0.2 24 | fluttertoast: ^8.2.1 25 | get: ^4.6.5 26 | highlight: ^0.7.0 27 | hive_flutter: ^1.1.0 28 | http: ^0.13.5 29 | in_app_review: ^2.0.6 30 | intl: ^0.18.0 31 | markdown: ^7.0.1 32 | path: ^1.8.2 33 | path_provider: ^2.0.13 34 | pretty_dio_logger: ^1.3.1 35 | share_plus: ^6.3.1 36 | sqflite: ^2.2.5 37 | url_launcher: ^6.1.10 38 | 39 | dev_dependencies: 40 | build_runner: ^2.3.3 41 | change_app_package_name: ^1.1.0 42 | drift_dev: ^2.4.1 43 | flutter_lints: 2.0.1 44 | flutter_test: 45 | sdk: flutter 46 | 47 | flutter: 48 | uses-material-design: true 49 | 50 | assets: 51 | - assets/images/ 52 | - assets/files/ 53 | 54 | flutter_native_splash: 55 | color: "#ffffff" 56 | image: assets/images/logo-white.png 57 | 58 | flutter_icons: 59 | android: "launcher_icon" 60 | ios: true 61 | remove_alpha_ios: true 62 | image_path: "assets/images/logo-green.png" 63 | min_sdk_android: 21 # android min sdk min:16, default 21 64 | web: 65 | generate: true 66 | image_path: "assets/images/logo-green.png" 67 | background_color: "#hexcode" 68 | theme_color: "#hexcode" 69 | windows: 70 | generate: true 71 | image_path: "assets/images/logo-green.png" 72 | icon_size: 48 # min:48, max:256, default: 48 73 | macos: 74 | generate: true 75 | image_path: "assets/images/logo-green.png" -------------------------------------------------------------------------------- /test/widget_test.dart: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bapaws/answer/6c65fced23f96186f330d7704ff5b49107f83a83/test/widget_test.dart -------------------------------------------------------------------------------- /web/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bapaws/answer/6c65fced23f96186f330d7704ff5b49107f83a83/web/favicon.png -------------------------------------------------------------------------------- /web/icons/Icon-192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bapaws/answer/6c65fced23f96186f330d7704ff5b49107f83a83/web/icons/Icon-192.png -------------------------------------------------------------------------------- /web/icons/Icon-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bapaws/answer/6c65fced23f96186f330d7704ff5b49107f83a83/web/icons/Icon-512.png -------------------------------------------------------------------------------- /web/icons/Icon-maskable-192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bapaws/answer/6c65fced23f96186f330d7704ff5b49107f83a83/web/icons/Icon-maskable-192.png -------------------------------------------------------------------------------- /web/icons/Icon-maskable-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bapaws/answer/6c65fced23f96186f330d7704ff5b49107f83a83/web/icons/Icon-maskable-512.png -------------------------------------------------------------------------------- /web/index.html: -------------------------------------------------------------------------------- 1 | 2 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | Answer 31 | 32 | 33 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 62 | 63 | 64 | -------------------------------------------------------------------------------- /web/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "answer", 3 | "short_name": "answer", 4 | "start_url": ".", 5 | "display": "standalone", 6 | "background_color": "#hexcode", 7 | "theme_color": "#hexcode", 8 | "description": "A new Flutter project.", 9 | "orientation": "portrait-primary", 10 | "prefer_related_applications": false, 11 | "icons": [ 12 | { 13 | "src": "icons/Icon-192.png", 14 | "sizes": "192x192", 15 | "type": "image/png" 16 | }, 17 | { 18 | "src": "icons/Icon-512.png", 19 | "sizes": "512x512", 20 | "type": "image/png" 21 | }, 22 | { 23 | "src": "icons/Icon-maskable-192.png", 24 | "sizes": "192x192", 25 | "type": "image/png", 26 | "purpose": "maskable" 27 | }, 28 | { 29 | "src": "icons/Icon-maskable-512.png", 30 | "sizes": "512x512", 31 | "type": "image/png", 32 | "purpose": "maskable" 33 | } 34 | ] 35 | } -------------------------------------------------------------------------------- /web/splash/img/dark-1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bapaws/answer/6c65fced23f96186f330d7704ff5b49107f83a83/web/splash/img/dark-1x.png -------------------------------------------------------------------------------- /web/splash/img/dark-2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bapaws/answer/6c65fced23f96186f330d7704ff5b49107f83a83/web/splash/img/dark-2x.png -------------------------------------------------------------------------------- /web/splash/img/dark-3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bapaws/answer/6c65fced23f96186f330d7704ff5b49107f83a83/web/splash/img/dark-3x.png -------------------------------------------------------------------------------- /web/splash/img/dark-4x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bapaws/answer/6c65fced23f96186f330d7704ff5b49107f83a83/web/splash/img/dark-4x.png -------------------------------------------------------------------------------- /web/splash/img/light-1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bapaws/answer/6c65fced23f96186f330d7704ff5b49107f83a83/web/splash/img/light-1x.png -------------------------------------------------------------------------------- /web/splash/img/light-2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bapaws/answer/6c65fced23f96186f330d7704ff5b49107f83a83/web/splash/img/light-2x.png -------------------------------------------------------------------------------- /web/splash/img/light-3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bapaws/answer/6c65fced23f96186f330d7704ff5b49107f83a83/web/splash/img/light-3x.png -------------------------------------------------------------------------------- /web/splash/img/light-4x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bapaws/answer/6c65fced23f96186f330d7704ff5b49107f83a83/web/splash/img/light-4x.png -------------------------------------------------------------------------------- /web/splash/splash.js: -------------------------------------------------------------------------------- 1 | function removeSplashFromWeb() { 2 | document.getElementById("splash")?.remove(); 3 | document.getElementById("splash-branding")?.remove(); 4 | document.body.style.background = "transparent"; 5 | } 6 | -------------------------------------------------------------------------------- /web/splash/style.css: -------------------------------------------------------------------------------- 1 | html { 2 | height: 100% 3 | } 4 | 5 | body { 6 | margin: 0; 7 | min-height: 100%; 8 | background-color: #ffffff; 9 | background-size: 100% 100%; 10 | } 11 | 12 | .center { 13 | margin: 0; 14 | position: absolute; 15 | top: 50%; 16 | left: 50%; 17 | -ms-transform: translate(-50%, -50%); 18 | transform: translate(-50%, -50%); 19 | } 20 | 21 | .contain { 22 | display:block; 23 | width:100%; height:100%; 24 | object-fit: contain; 25 | } 26 | 27 | .stretch { 28 | display:block; 29 | width:100%; height:100%; 30 | } 31 | 32 | .cover { 33 | display:block; 34 | width:100%; height:100%; 35 | object-fit: cover; 36 | } 37 | 38 | .bottom { 39 | position: absolute; 40 | bottom: 0; 41 | left: 50%; 42 | -ms-transform: translate(-50%, 0); 43 | transform: translate(-50%, 0); 44 | } 45 | 46 | .bottomLeft { 47 | position: absolute; 48 | bottom: 0; 49 | left: 0; 50 | } 51 | 52 | .bottomRight { 53 | position: absolute; 54 | bottom: 0; 55 | right: 0; 56 | } 57 | -------------------------------------------------------------------------------- /windows/.gitignore: -------------------------------------------------------------------------------- 1 | flutter/ephemeral/ 2 | 3 | # Visual Studio user-specific files. 4 | *.suo 5 | *.user 6 | *.userosscache 7 | *.sln.docstates 8 | 9 | # Visual Studio build-related files. 10 | x64/ 11 | x86/ 12 | 13 | # Visual Studio cache files 14 | # files ending in .cache can be ignored 15 | *.[Cc]ache 16 | # but keep track of directories ending in .cache 17 | !*.[Cc]ache/ 18 | -------------------------------------------------------------------------------- /windows/flutter/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # This file controls Flutter-level build steps. It should not be edited. 2 | cmake_minimum_required(VERSION 3.14) 3 | 4 | set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral") 5 | 6 | # Configuration provided via flutter tool. 7 | include(${EPHEMERAL_DIR}/generated_config.cmake) 8 | 9 | # TODO: Move the rest of this into files in ephemeral. See 10 | # https://github.com/flutter/flutter/issues/57146. 11 | set(WRAPPER_ROOT "${EPHEMERAL_DIR}/cpp_client_wrapper") 12 | 13 | # === Flutter Library === 14 | set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/flutter_windows.dll") 15 | 16 | # Published to parent scope for install step. 17 | set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE) 18 | set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE) 19 | set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE) 20 | set(AOT_LIBRARY "${PROJECT_DIR}/build/windows/app.so" PARENT_SCOPE) 21 | 22 | list(APPEND FLUTTER_LIBRARY_HEADERS 23 | "flutter_export.h" 24 | "flutter_windows.h" 25 | "flutter_messenger.h" 26 | "flutter_plugin_registrar.h" 27 | "flutter_texture_registrar.h" 28 | ) 29 | list(TRANSFORM FLUTTER_LIBRARY_HEADERS PREPEND "${EPHEMERAL_DIR}/") 30 | add_library(flutter INTERFACE) 31 | target_include_directories(flutter INTERFACE 32 | "${EPHEMERAL_DIR}" 33 | ) 34 | target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}.lib") 35 | add_dependencies(flutter flutter_assemble) 36 | 37 | # === Wrapper === 38 | list(APPEND CPP_WRAPPER_SOURCES_CORE 39 | "core_implementations.cc" 40 | "standard_codec.cc" 41 | ) 42 | list(TRANSFORM CPP_WRAPPER_SOURCES_CORE PREPEND "${WRAPPER_ROOT}/") 43 | list(APPEND CPP_WRAPPER_SOURCES_PLUGIN 44 | "plugin_registrar.cc" 45 | ) 46 | list(TRANSFORM CPP_WRAPPER_SOURCES_PLUGIN PREPEND "${WRAPPER_ROOT}/") 47 | list(APPEND CPP_WRAPPER_SOURCES_APP 48 | "flutter_engine.cc" 49 | "flutter_view_controller.cc" 50 | ) 51 | list(TRANSFORM CPP_WRAPPER_SOURCES_APP PREPEND "${WRAPPER_ROOT}/") 52 | 53 | # Wrapper sources needed for a plugin. 54 | add_library(flutter_wrapper_plugin STATIC 55 | ${CPP_WRAPPER_SOURCES_CORE} 56 | ${CPP_WRAPPER_SOURCES_PLUGIN} 57 | ) 58 | apply_standard_settings(flutter_wrapper_plugin) 59 | set_target_properties(flutter_wrapper_plugin PROPERTIES 60 | POSITION_INDEPENDENT_CODE ON) 61 | set_target_properties(flutter_wrapper_plugin PROPERTIES 62 | CXX_VISIBILITY_PRESET hidden) 63 | target_link_libraries(flutter_wrapper_plugin PUBLIC flutter) 64 | target_include_directories(flutter_wrapper_plugin PUBLIC 65 | "${WRAPPER_ROOT}/include" 66 | ) 67 | add_dependencies(flutter_wrapper_plugin flutter_assemble) 68 | 69 | # Wrapper sources needed for the runner. 70 | add_library(flutter_wrapper_app STATIC 71 | ${CPP_WRAPPER_SOURCES_CORE} 72 | ${CPP_WRAPPER_SOURCES_APP} 73 | ) 74 | apply_standard_settings(flutter_wrapper_app) 75 | target_link_libraries(flutter_wrapper_app PUBLIC flutter) 76 | target_include_directories(flutter_wrapper_app PUBLIC 77 | "${WRAPPER_ROOT}/include" 78 | ) 79 | add_dependencies(flutter_wrapper_app flutter_assemble) 80 | 81 | # === Flutter tool backend === 82 | # _phony_ is a non-existent file to force this command to run every time, 83 | # since currently there's no way to get a full input/output list from the 84 | # flutter tool. 85 | set(PHONY_OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/_phony_") 86 | set_source_files_properties("${PHONY_OUTPUT}" PROPERTIES SYMBOLIC TRUE) 87 | add_custom_command( 88 | OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS} 89 | ${CPP_WRAPPER_SOURCES_CORE} ${CPP_WRAPPER_SOURCES_PLUGIN} 90 | ${CPP_WRAPPER_SOURCES_APP} 91 | ${PHONY_OUTPUT} 92 | COMMAND ${CMAKE_COMMAND} -E env 93 | ${FLUTTER_TOOL_ENVIRONMENT} 94 | "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.bat" 95 | windows-x64 $ 96 | VERBATIM 97 | ) 98 | add_custom_target(flutter_assemble DEPENDS 99 | "${FLUTTER_LIBRARY}" 100 | ${FLUTTER_LIBRARY_HEADERS} 101 | ${CPP_WRAPPER_SOURCES_CORE} 102 | ${CPP_WRAPPER_SOURCES_PLUGIN} 103 | ${CPP_WRAPPER_SOURCES_APP} 104 | ) 105 | -------------------------------------------------------------------------------- /windows/flutter/generated_plugin_registrant.cc: -------------------------------------------------------------------------------- 1 | // 2 | // Generated file. Do not edit. 3 | // 4 | 5 | // clang-format off 6 | 7 | #include "generated_plugin_registrant.h" 8 | 9 | #include 10 | #include 11 | 12 | void RegisterPlugins(flutter::PluginRegistry* registry) { 13 | SharePlusWindowsPluginCApiRegisterWithRegistrar( 14 | registry->GetRegistrarForPlugin("SharePlusWindowsPluginCApi")); 15 | UrlLauncherWindowsRegisterWithRegistrar( 16 | registry->GetRegistrarForPlugin("UrlLauncherWindows")); 17 | } 18 | -------------------------------------------------------------------------------- /windows/flutter/generated_plugin_registrant.h: -------------------------------------------------------------------------------- 1 | // 2 | // Generated file. Do not edit. 3 | // 4 | 5 | // clang-format off 6 | 7 | #ifndef GENERATED_PLUGIN_REGISTRANT_ 8 | #define GENERATED_PLUGIN_REGISTRANT_ 9 | 10 | #include 11 | 12 | // Registers Flutter plugins. 13 | void RegisterPlugins(flutter::PluginRegistry* registry); 14 | 15 | #endif // GENERATED_PLUGIN_REGISTRANT_ 16 | -------------------------------------------------------------------------------- /windows/flutter/generated_plugins.cmake: -------------------------------------------------------------------------------- 1 | # 2 | # Generated file, do not edit. 3 | # 4 | 5 | list(APPEND FLUTTER_PLUGIN_LIST 6 | share_plus 7 | url_launcher_windows 8 | ) 9 | 10 | list(APPEND FLUTTER_FFI_PLUGIN_LIST 11 | ) 12 | 13 | set(PLUGIN_BUNDLED_LIBRARIES) 14 | 15 | foreach(plugin ${FLUTTER_PLUGIN_LIST}) 16 | add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/windows plugins/${plugin}) 17 | target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) 18 | list(APPEND PLUGIN_BUNDLED_LIBRARIES $) 19 | list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) 20 | endforeach(plugin) 21 | 22 | foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) 23 | add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/windows plugins/${ffi_plugin}) 24 | list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) 25 | endforeach(ffi_plugin) 26 | -------------------------------------------------------------------------------- /windows/runner/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.14) 2 | project(runner LANGUAGES CXX) 3 | 4 | # Define the application target. To change its name, change BINARY_NAME in the 5 | # top-level CMakeLists.txt, not the value here, or `flutter run` will no longer 6 | # work. 7 | # 8 | # Any new source files that you add to the application should be added here. 9 | add_executable(${BINARY_NAME} WIN32 10 | "flutter_window.cpp" 11 | "main.cpp" 12 | "utils.cpp" 13 | "win32_window.cpp" 14 | "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" 15 | "Runner.rc" 16 | "runner.exe.manifest" 17 | ) 18 | 19 | # Apply the standard set of build settings. This can be removed for applications 20 | # that need different build settings. 21 | apply_standard_settings(${BINARY_NAME}) 22 | 23 | # Add preprocessor definitions for the build version. 24 | target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION=\"${FLUTTER_VERSION}\"") 25 | target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MAJOR=${FLUTTER_VERSION_MAJOR}") 26 | target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MINOR=${FLUTTER_VERSION_MINOR}") 27 | target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_PATCH=${FLUTTER_VERSION_PATCH}") 28 | target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_BUILD=${FLUTTER_VERSION_BUILD}") 29 | 30 | # Disable Windows macros that collide with C++ standard library functions. 31 | target_compile_definitions(${BINARY_NAME} PRIVATE "NOMINMAX") 32 | 33 | # Add dependency libraries and include directories. Add any application-specific 34 | # dependencies here. 35 | target_link_libraries(${BINARY_NAME} PRIVATE flutter flutter_wrapper_app) 36 | target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}") 37 | 38 | # Run the Flutter tool portions of the build. This must not be removed. 39 | add_dependencies(${BINARY_NAME} flutter_assemble) 40 | -------------------------------------------------------------------------------- /windows/runner/Runner.rc: -------------------------------------------------------------------------------- 1 | // Microsoft Visual C++ generated resource script. 2 | // 3 | #pragma code_page(65001) 4 | #include "resource.h" 5 | 6 | #define APSTUDIO_READONLY_SYMBOLS 7 | ///////////////////////////////////////////////////////////////////////////// 8 | // 9 | // Generated from the TEXTINCLUDE 2 resource. 10 | // 11 | #include "winres.h" 12 | 13 | ///////////////////////////////////////////////////////////////////////////// 14 | #undef APSTUDIO_READONLY_SYMBOLS 15 | 16 | ///////////////////////////////////////////////////////////////////////////// 17 | // English (United States) resources 18 | 19 | #if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) 20 | LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US 21 | 22 | #ifdef APSTUDIO_INVOKED 23 | ///////////////////////////////////////////////////////////////////////////// 24 | // 25 | // TEXTINCLUDE 26 | // 27 | 28 | 1 TEXTINCLUDE 29 | BEGIN 30 | "resource.h\0" 31 | END 32 | 33 | 2 TEXTINCLUDE 34 | BEGIN 35 | "#include ""winres.h""\r\n" 36 | "\0" 37 | END 38 | 39 | 3 TEXTINCLUDE 40 | BEGIN 41 | "\r\n" 42 | "\0" 43 | END 44 | 45 | #endif // APSTUDIO_INVOKED 46 | 47 | 48 | ///////////////////////////////////////////////////////////////////////////// 49 | // 50 | // Icon 51 | // 52 | 53 | // Icon with lowest ID value placed first to ensure application icon 54 | // remains consistent on all systems. 55 | IDI_APP_ICON ICON "resources\\app_icon.ico" 56 | 57 | 58 | ///////////////////////////////////////////////////////////////////////////// 59 | // 60 | // Version 61 | // 62 | 63 | #if defined(FLUTTER_VERSION_MAJOR) && defined(FLUTTER_VERSION_MINOR) && defined(FLUTTER_VERSION_PATCH) && defined(FLUTTER_VERSION_BUILD) 64 | #define VERSION_AS_NUMBER FLUTTER_VERSION_MAJOR,FLUTTER_VERSION_MINOR,FLUTTER_VERSION_PATCH,FLUTTER_VERSION_BUILD 65 | #else 66 | #define VERSION_AS_NUMBER 1,0,0,0 67 | #endif 68 | 69 | #if defined(FLUTTER_VERSION) 70 | #define VERSION_AS_STRING FLUTTER_VERSION 71 | #else 72 | #define VERSION_AS_STRING "1.0.0" 73 | #endif 74 | 75 | VS_VERSION_INFO VERSIONINFO 76 | FILEVERSION VERSION_AS_NUMBER 77 | PRODUCTVERSION VERSION_AS_NUMBER 78 | FILEFLAGSMASK VS_FFI_FILEFLAGSMASK 79 | #ifdef _DEBUG 80 | FILEFLAGS VS_FF_DEBUG 81 | #else 82 | FILEFLAGS 0x0L 83 | #endif 84 | FILEOS VOS__WINDOWS32 85 | FILETYPE VFT_APP 86 | FILESUBTYPE 0x0L 87 | BEGIN 88 | BLOCK "StringFileInfo" 89 | BEGIN 90 | BLOCK "040904e4" 91 | BEGIN 92 | VALUE "CompanyName", "com.bapaws" "\0" 93 | VALUE "FileDescription", "Answer" "\0" 94 | VALUE "FileVersion", VERSION_AS_STRING "\0" 95 | VALUE "InternalName", "Answer" "\0" 96 | VALUE "LegalCopyright", "Copyright (C) 2023 com.bapaws. All rights reserved." "\0" 97 | VALUE "OriginalFilename", "answer.exe" "\0" 98 | VALUE "ProductName", "Answer" "\0" 99 | VALUE "ProductVersion", VERSION_AS_STRING "\0" 100 | END 101 | END 102 | BLOCK "VarFileInfo" 103 | BEGIN 104 | VALUE "Translation", 0x409, 1252 105 | END 106 | END 107 | 108 | #endif // English (United States) resources 109 | ///////////////////////////////////////////////////////////////////////////// 110 | 111 | 112 | 113 | #ifndef APSTUDIO_INVOKED 114 | ///////////////////////////////////////////////////////////////////////////// 115 | // 116 | // Generated from the TEXTINCLUDE 3 resource. 117 | // 118 | 119 | 120 | ///////////////////////////////////////////////////////////////////////////// 121 | #endif // not APSTUDIO_INVOKED 122 | -------------------------------------------------------------------------------- /windows/runner/flutter_window.cpp: -------------------------------------------------------------------------------- 1 | #include "flutter_window.h" 2 | 3 | #include 4 | 5 | #include "flutter/generated_plugin_registrant.h" 6 | 7 | FlutterWindow::FlutterWindow(const flutter::DartProject& project) 8 | : project_(project) {} 9 | 10 | FlutterWindow::~FlutterWindow() {} 11 | 12 | bool FlutterWindow::OnCreate() { 13 | if (!Win32Window::OnCreate()) { 14 | return false; 15 | } 16 | 17 | RECT frame = GetClientArea(); 18 | 19 | // The size here must match the window dimensions to avoid unnecessary surface 20 | // creation / destruction in the startup path. 21 | flutter_controller_ = std::make_unique( 22 | frame.right - frame.left, frame.bottom - frame.top, project_); 23 | // Ensure that basic setup of the controller was successful. 24 | if (!flutter_controller_->engine() || !flutter_controller_->view()) { 25 | return false; 26 | } 27 | RegisterPlugins(flutter_controller_->engine()); 28 | SetChildContent(flutter_controller_->view()->GetNativeWindow()); 29 | return true; 30 | } 31 | 32 | void FlutterWindow::OnDestroy() { 33 | if (flutter_controller_) { 34 | flutter_controller_ = nullptr; 35 | } 36 | 37 | Win32Window::OnDestroy(); 38 | } 39 | 40 | LRESULT 41 | FlutterWindow::MessageHandler(HWND hwnd, UINT const message, 42 | WPARAM const wparam, 43 | LPARAM const lparam) noexcept { 44 | // Give Flutter, including plugins, an opportunity to handle window messages. 45 | if (flutter_controller_) { 46 | std::optional result = 47 | flutter_controller_->HandleTopLevelWindowProc(hwnd, message, wparam, 48 | lparam); 49 | if (result) { 50 | return *result; 51 | } 52 | } 53 | 54 | switch (message) { 55 | case WM_FONTCHANGE: 56 | flutter_controller_->engine()->ReloadSystemFonts(); 57 | break; 58 | } 59 | 60 | return Win32Window::MessageHandler(hwnd, message, wparam, lparam); 61 | } 62 | -------------------------------------------------------------------------------- /windows/runner/flutter_window.h: -------------------------------------------------------------------------------- 1 | #ifndef RUNNER_FLUTTER_WINDOW_H_ 2 | #define RUNNER_FLUTTER_WINDOW_H_ 3 | 4 | #include 5 | #include 6 | 7 | #include 8 | 9 | #include "win32_window.h" 10 | 11 | // A window that does nothing but host a Flutter view. 12 | class FlutterWindow : public Win32Window { 13 | public: 14 | // Creates a new FlutterWindow hosting a Flutter view running |project|. 15 | explicit FlutterWindow(const flutter::DartProject& project); 16 | virtual ~FlutterWindow(); 17 | 18 | protected: 19 | // Win32Window: 20 | bool OnCreate() override; 21 | void OnDestroy() override; 22 | LRESULT MessageHandler(HWND window, UINT const message, WPARAM const wparam, 23 | LPARAM const lparam) noexcept override; 24 | 25 | private: 26 | // The project to run. 27 | flutter::DartProject project_; 28 | 29 | // The Flutter instance hosted by this window. 30 | std::unique_ptr flutter_controller_; 31 | }; 32 | 33 | #endif // RUNNER_FLUTTER_WINDOW_H_ 34 | -------------------------------------------------------------------------------- /windows/runner/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "flutter_window.h" 6 | #include "utils.h" 7 | 8 | int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev, 9 | _In_ wchar_t *command_line, _In_ int show_command) { 10 | // Attach to console when present (e.g., 'flutter run') or create a 11 | // new console when running with a debugger. 12 | if (!::AttachConsole(ATTACH_PARENT_PROCESS) && ::IsDebuggerPresent()) { 13 | CreateAndAttachConsole(); 14 | } 15 | 16 | // Initialize COM, so that it is available for use in the library and/or 17 | // plugins. 18 | ::CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED); 19 | 20 | flutter::DartProject project(L"data"); 21 | 22 | std::vector command_line_arguments = 23 | GetCommandLineArguments(); 24 | 25 | project.set_dart_entrypoint_arguments(std::move(command_line_arguments)); 26 | 27 | FlutterWindow window(project); 28 | Win32Window::Point origin(10, 10); 29 | Win32Window::Size size(1280, 720); 30 | if (!window.CreateAndShow(L"Answer", origin, size)) { 31 | return EXIT_FAILURE; 32 | } 33 | window.SetQuitOnClose(true); 34 | 35 | ::MSG msg; 36 | while (::GetMessage(&msg, nullptr, 0, 0)) { 37 | ::TranslateMessage(&msg); 38 | ::DispatchMessage(&msg); 39 | } 40 | 41 | ::CoUninitialize(); 42 | return EXIT_SUCCESS; 43 | } 44 | -------------------------------------------------------------------------------- /windows/runner/resource.h: -------------------------------------------------------------------------------- 1 | //{{NO_DEPENDENCIES}} 2 | // Microsoft Visual C++ generated include file. 3 | // Used by Runner.rc 4 | // 5 | #define IDI_APP_ICON 101 6 | 7 | // Next default values for new objects 8 | // 9 | #ifdef APSTUDIO_INVOKED 10 | #ifndef APSTUDIO_READONLY_SYMBOLS 11 | #define _APS_NEXT_RESOURCE_VALUE 102 12 | #define _APS_NEXT_COMMAND_VALUE 40001 13 | #define _APS_NEXT_CONTROL_VALUE 1001 14 | #define _APS_NEXT_SYMED_VALUE 101 15 | #endif 16 | #endif 17 | -------------------------------------------------------------------------------- /windows/runner/resources/app_icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bapaws/answer/6c65fced23f96186f330d7704ff5b49107f83a83/windows/runner/resources/app_icon.ico -------------------------------------------------------------------------------- /windows/runner/runner.exe.manifest: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PerMonitorV2 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /windows/runner/utils.cpp: -------------------------------------------------------------------------------- 1 | #include "utils.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | 10 | void CreateAndAttachConsole() { 11 | if (::AllocConsole()) { 12 | FILE *unused; 13 | if (freopen_s(&unused, "CONOUT$", "w", stdout)) { 14 | _dup2(_fileno(stdout), 1); 15 | } 16 | if (freopen_s(&unused, "CONOUT$", "w", stderr)) { 17 | _dup2(_fileno(stdout), 2); 18 | } 19 | std::ios::sync_with_stdio(); 20 | FlutterDesktopResyncOutputStreams(); 21 | } 22 | } 23 | 24 | std::vector GetCommandLineArguments() { 25 | // Convert the UTF-16 command line arguments to UTF-8 for the Engine to use. 26 | int argc; 27 | wchar_t** argv = ::CommandLineToArgvW(::GetCommandLineW(), &argc); 28 | if (argv == nullptr) { 29 | return std::vector(); 30 | } 31 | 32 | std::vector command_line_arguments; 33 | 34 | // Skip the first argument as it's the binary name. 35 | for (int i = 1; i < argc; i++) { 36 | command_line_arguments.push_back(Utf8FromUtf16(argv[i])); 37 | } 38 | 39 | ::LocalFree(argv); 40 | 41 | return command_line_arguments; 42 | } 43 | 44 | std::string Utf8FromUtf16(const wchar_t* utf16_string) { 45 | if (utf16_string == nullptr) { 46 | return std::string(); 47 | } 48 | int target_length = ::WideCharToMultiByte( 49 | CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, 50 | -1, nullptr, 0, nullptr, nullptr); 51 | std::string utf8_string; 52 | if (target_length == 0 || target_length > utf8_string.max_size()) { 53 | return utf8_string; 54 | } 55 | utf8_string.resize(target_length); 56 | int converted_length = ::WideCharToMultiByte( 57 | CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, 58 | -1, utf8_string.data(), 59 | target_length, nullptr, nullptr); 60 | if (converted_length == 0) { 61 | return std::string(); 62 | } 63 | return utf8_string; 64 | } 65 | -------------------------------------------------------------------------------- /windows/runner/utils.h: -------------------------------------------------------------------------------- 1 | #ifndef RUNNER_UTILS_H_ 2 | #define RUNNER_UTILS_H_ 3 | 4 | #include 5 | #include 6 | 7 | // Creates a console for the process, and redirects stdout and stderr to 8 | // it for both the runner and the Flutter library. 9 | void CreateAndAttachConsole(); 10 | 11 | // Takes a null-terminated wchar_t* encoded in UTF-16 and returns a std::string 12 | // encoded in UTF-8. Returns an empty std::string on failure. 13 | std::string Utf8FromUtf16(const wchar_t* utf16_string); 14 | 15 | // Gets the command line arguments passed in as a std::vector, 16 | // encoded in UTF-8. Returns an empty std::vector on failure. 17 | std::vector GetCommandLineArguments(); 18 | 19 | #endif // RUNNER_UTILS_H_ 20 | -------------------------------------------------------------------------------- /windows/runner/win32_window.h: -------------------------------------------------------------------------------- 1 | #ifndef RUNNER_WIN32_WINDOW_H_ 2 | #define RUNNER_WIN32_WINDOW_H_ 3 | 4 | #include 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | // A class abstraction for a high DPI-aware Win32 Window. Intended to be 11 | // inherited from by classes that wish to specialize with custom 12 | // rendering and input handling 13 | class Win32Window { 14 | public: 15 | struct Point { 16 | unsigned int x; 17 | unsigned int y; 18 | Point(unsigned int x, unsigned int y) : x(x), y(y) {} 19 | }; 20 | 21 | struct Size { 22 | unsigned int width; 23 | unsigned int height; 24 | Size(unsigned int width, unsigned int height) 25 | : width(width), height(height) {} 26 | }; 27 | 28 | Win32Window(); 29 | virtual ~Win32Window(); 30 | 31 | // Creates and shows a win32 window with |title| and position and size using 32 | // |origin| and |size|. New windows are created on the default monitor. Window 33 | // sizes are specified to the OS in physical pixels, hence to ensure a 34 | // consistent size to will treat the width height passed in to this function 35 | // as logical pixels and scale to appropriate for the default monitor. Returns 36 | // true if the window was created successfully. 37 | bool CreateAndShow(const std::wstring& title, 38 | const Point& origin, 39 | const Size& size); 40 | 41 | // Release OS resources associated with window. 42 | void Destroy(); 43 | 44 | // Inserts |content| into the window tree. 45 | void SetChildContent(HWND content); 46 | 47 | // Returns the backing Window handle to enable clients to set icon and other 48 | // window properties. Returns nullptr if the window has been destroyed. 49 | HWND GetHandle(); 50 | 51 | // If true, closing this window will quit the application. 52 | void SetQuitOnClose(bool quit_on_close); 53 | 54 | // Return a RECT representing the bounds of the current client area. 55 | RECT GetClientArea(); 56 | 57 | protected: 58 | // Processes and route salient window messages for mouse handling, 59 | // size change and DPI. Delegates handling of these to member overloads that 60 | // inheriting classes can handle. 61 | virtual LRESULT MessageHandler(HWND window, 62 | UINT const message, 63 | WPARAM const wparam, 64 | LPARAM const lparam) noexcept; 65 | 66 | // Called when CreateAndShow is called, allowing subclass window-related 67 | // setup. Subclasses should return false if setup fails. 68 | virtual bool OnCreate(); 69 | 70 | // Called when Destroy is called. 71 | virtual void OnDestroy(); 72 | 73 | private: 74 | friend class WindowClassRegistrar; 75 | 76 | // OS callback called by message pump. Handles the WM_NCCREATE message which 77 | // is passed when the non-client area is being created and enables automatic 78 | // non-client DPI scaling so that the non-client area automatically 79 | // responsponds to changes in DPI. All other messages are handled by 80 | // MessageHandler. 81 | static LRESULT CALLBACK WndProc(HWND const window, 82 | UINT const message, 83 | WPARAM const wparam, 84 | LPARAM const lparam) noexcept; 85 | 86 | // Retrieves a class instance pointer for |window| 87 | static Win32Window* GetThisFromHandle(HWND const window) noexcept; 88 | 89 | bool quit_on_close_ = false; 90 | 91 | // window handle for top level window. 92 | HWND window_handle_ = nullptr; 93 | 94 | // window handle for hosted content. 95 | HWND child_content_ = nullptr; 96 | }; 97 | 98 | #endif // RUNNER_WIN32_WINDOW_H_ 99 | --------------------------------------------------------------------------------