├── .github └── logo.png ├── .gitignore ├── .metadata ├── README.md ├── analysis_options.yaml ├── android ├── .gitignore ├── app │ ├── build.gradle │ └── src │ │ ├── debug │ │ └── AndroidManifest.xml │ │ ├── main │ │ ├── AndroidManifest.xml │ │ ├── kotlin │ │ │ └── com │ │ │ │ └── cershy │ │ │ │ └── linyu_mobile │ │ │ │ └── MainActivity.kt │ │ └── res │ │ │ ├── drawable-v21 │ │ │ └── launch_background.xml │ │ │ ├── drawable │ │ │ └── launch_background.xml │ │ │ ├── mipmap-hdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-mdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xhdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xxhdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xxxhdpi │ │ │ └── ic_launcher.png │ │ │ ├── values-night │ │ │ └── styles.xml │ │ │ ├── values │ │ │ └── styles.xml │ │ │ └── xml │ │ │ └── network_security_config.xml │ │ └── profile │ │ └── AndroidManifest.xml ├── build.gradle ├── gradle.properties ├── gradle │ └── wrapper │ │ └── gradle-wrapper.properties └── settings.gradle ├── assets ├── iconfont │ └── iconfont.ttf ├── images │ ├── call.png │ ├── chat-blue.png │ ├── chat-empty.png │ ├── chat-pink.png │ ├── default-portrait.jpeg │ ├── edit.png │ ├── empty-bg.png │ ├── file-blue.png │ ├── file-pink.png │ ├── file-white.png │ ├── flutter.png │ ├── group.png │ ├── icon-circle-of-friends.png │ ├── linyu.png │ ├── logo-about.png │ ├── logo-login-blue.png │ ├── logo-login-pink.png │ ├── logo-qr-blue.png │ ├── logo-qr-pink.png │ ├── logo.png │ ├── mine-about.png │ ├── mine-blue.png │ ├── mine-empty.png │ ├── mine-notify-blue.png │ ├── mine-notify-pink.png │ ├── mine-password.png │ ├── mine-pink.png │ ├── mine-set.png │ ├── mine-talk-blue.png │ ├── mine-talk-pink.png │ ├── more.png │ ├── qr-affirm.png │ ├── signature.png │ ├── talk-blue.png │ ├── talk-empty.png │ ├── talk-pink.png │ ├── user-blue.png │ ├── user-empty.png │ └── user-pink.png └── sounds │ └── success.mp3 ├── ios ├── .gitignore ├── Flutter │ ├── AppFrameworkInfo.plist │ ├── Debug.xcconfig │ └── Release.xcconfig ├── Runner.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ ├── IDEWorkspaceChecks.plist │ │ │ └── WorkspaceSettings.xcsettings │ └── xcshareddata │ │ └── xcschemes │ │ └── Runner.xcscheme ├── Runner.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ ├── IDEWorkspaceChecks.plist │ │ └── WorkspaceSettings.xcsettings ├── Runner │ ├── AppDelegate.swift │ ├── Assets.xcassets │ │ ├── AppIcon.appiconset │ │ │ ├── Contents.json │ │ │ ├── Icon-App-1024x1024@1x.png │ │ │ ├── Icon-App-20x20@1x.png │ │ │ ├── Icon-App-20x20@2x.png │ │ │ ├── Icon-App-20x20@3x.png │ │ │ ├── Icon-App-29x29@1x.png │ │ │ ├── Icon-App-29x29@2x.png │ │ │ ├── Icon-App-29x29@3x.png │ │ │ ├── Icon-App-40x40@1x.png │ │ │ ├── Icon-App-40x40@2x.png │ │ │ ├── Icon-App-40x40@3x.png │ │ │ ├── Icon-App-60x60@2x.png │ │ │ ├── Icon-App-60x60@3x.png │ │ │ ├── Icon-App-76x76@1x.png │ │ │ ├── Icon-App-76x76@2x.png │ │ │ └── Icon-App-83.5x83.5@2x.png │ │ └── LaunchImage.imageset │ │ │ ├── Contents.json │ │ │ ├── LaunchImage.png │ │ │ ├── LaunchImage@2x.png │ │ │ ├── LaunchImage@3x.png │ │ │ └── README.md │ ├── Base.lproj │ │ ├── LaunchScreen.storyboard │ │ └── Main.storyboard │ ├── Info.plist │ └── Runner-Bridging-Header.h └── RunnerTests │ └── RunnerTests.swift ├── lib ├── api │ ├── Http.dart │ ├── chat_group_api.dart │ ├── chat_group_member.dart │ ├── chat_group_notice_api.dart │ ├── chat_list_api.dart │ ├── friend_api.dart │ ├── group_api.dart │ ├── msg_api.dart │ ├── notify_api.dart │ ├── qr_api.dart │ ├── talk_api.dart │ ├── talk_comment_api.dart │ ├── talk_like_api.dart │ ├── user_api.dart │ └── video_api.dart ├── components │ ├── CustomDialog │ │ └── index.dart │ ├── app_bar_title │ │ └── index.dart │ ├── custom_animated_dots_text │ │ └── index.dart │ ├── custom_audio │ │ └── index.dart │ ├── custom_badge │ │ └── index.dart │ ├── custom_button │ │ └── index.dart │ ├── custom_drop_menu │ │ └── index.dart │ ├── custom_flutter_toast │ │ └── index.dart │ ├── custom_gradient_line │ │ └── index.dart │ ├── custom_icon_button │ │ └── index.dart │ ├── custom_image │ │ └── index.dart │ ├── custom_image_group │ │ └── index.dart │ ├── custom_label_value │ │ └── index.dart │ ├── custom_label_value_button │ │ └── index.dart │ ├── custom_least_button │ │ └── index.dart │ ├── custom_material_button │ │ └── index.dart │ ├── custom_portrait │ │ └── index.dart │ ├── custom_search_box │ │ └── index.dart │ ├── custom_shadow_text │ │ └── index.dart │ ├── custom_sound_icon │ │ └── index.dart │ ├── custom_text_button │ │ └── index.dart │ ├── custom_text_field │ │ └── index.dart │ ├── custom_tip │ │ └── index.dart │ ├── custom_update_portrait │ │ └── index.dart │ └── custom_voice_record_buttom │ │ └── index.dart ├── main.dart ├── pages │ ├── add_friend │ │ ├── friend_info │ │ │ ├── index.dart │ │ │ └── logic.dart │ │ ├── friend_request │ │ │ ├── index.dart │ │ │ └── logic.dart │ │ ├── index.dart │ │ └── logic.dart │ ├── chat_frame │ │ ├── chat_content │ │ │ ├── call.dart │ │ │ ├── file.dart │ │ │ ├── image.dart │ │ │ ├── msg.dart │ │ │ ├── retraction.dart │ │ │ ├── system.dart │ │ │ ├── text.dart │ │ │ ├── time.dart │ │ │ └── voice.dart │ │ ├── chat_setting │ │ │ ├── index.dart │ │ │ └── logic.dart │ │ ├── index.dart │ │ └── logic.dart │ ├── chat_list │ │ ├── index.dart │ │ └── logic.dart │ ├── contacts │ │ ├── chat_group_information │ │ │ ├── chat_group_member │ │ │ │ ├── index.dart │ │ │ │ └── logic.dart │ │ │ ├── chat_group_notice │ │ │ │ ├── add_chat_group_notice │ │ │ │ │ ├── index.dart │ │ │ │ │ └── logic.dart │ │ │ │ ├── index.dart │ │ │ │ └── logic.dart │ │ │ ├── index.dart │ │ │ ├── logic.dart │ │ │ ├── set_group_name │ │ │ │ ├── index.dart │ │ │ │ └── logic.dart │ │ │ ├── set_group_nickname │ │ │ │ ├── index.dart │ │ │ │ └── logic.dart │ │ │ └── set_group_remark │ │ │ │ ├── index.dart │ │ │ │ └── logic.dart │ │ ├── create_chat_group │ │ │ ├── index.dart │ │ │ ├── logic.dart │ │ │ └── select_user │ │ │ │ ├── index.dart │ │ │ │ └── logic.dart │ │ ├── friend_information │ │ │ ├── index.dart │ │ │ ├── logic.dart │ │ │ ├── set_group │ │ │ │ ├── index.dart │ │ │ │ └── logic.dart │ │ │ ├── set_remark │ │ │ │ ├── index.dart │ │ │ │ └── logic.dart │ │ │ └── signature_info │ │ │ │ ├── index.dart │ │ │ │ └── logic.dart │ │ ├── index.dart │ │ ├── logic.dart │ │ └── user_select │ │ │ ├── index.dart │ │ │ └── logic.dart │ ├── file_details │ │ ├── index.dart │ │ └── logic.dart │ ├── image_viewer │ │ ├── image_viewer_update │ │ │ ├── index.dart │ │ │ └── logic.dart │ │ ├── index.dart │ │ └── logic.dart │ ├── login │ │ ├── index.dart │ │ └── logic.dart │ ├── mine │ │ ├── about │ │ │ ├── index.dart │ │ │ └── logic.dart │ │ ├── edit │ │ │ ├── index.dart │ │ │ └── logic.dart │ │ ├── index.dart │ │ ├── logic.dart │ │ ├── mine_qr_code │ │ │ ├── index.dart │ │ │ └── logic.dart │ │ └── system_notify │ │ │ ├── index.dart │ │ │ └── logic.dart │ ├── navigation │ │ ├── index.dart │ │ └── logic.dart │ ├── password │ │ ├── retrieve │ │ │ ├── index.dart │ │ │ └── logic.dart │ │ └── update │ │ │ ├── index.dart │ │ │ └── logic.dart │ ├── qr_code_scan │ │ ├── index.dart │ │ ├── logic.dart │ │ ├── qr_friend_affirm │ │ │ ├── index.dart │ │ │ └── logic.dart │ │ ├── qr_login_affirm │ │ │ ├── index.dart │ │ │ └── logic.dart │ │ └── qr_other_result │ │ │ ├── index.dart │ │ │ └── logic.dart │ ├── re_forward │ │ ├── index.dart │ │ └── logic.dart │ ├── register │ │ ├── index.dart │ │ └── logic.dart │ ├── talk │ │ ├── index.dart │ │ ├── logic.dart │ │ ├── talk_create │ │ │ ├── index.dart │ │ │ └── logic.dart │ │ └── talk_details │ │ │ ├── index.dart │ │ │ └── logic.dart │ └── video_chat │ │ ├── index.dart │ │ └── logic.dart └── utils │ ├── String.dart │ ├── app_badger.dart │ ├── cropPicture.dart │ ├── date.dart │ ├── emoji.dart │ ├── encrypt.dart │ ├── extension.dart │ ├── getx_config │ ├── ControllerBinding.dart │ ├── GlobalData.dart │ ├── GlobalThemeConfig.dart │ ├── config.dart │ └── route.dart │ ├── linyu_msg.dart │ ├── notification.dart │ ├── permission_handler.dart │ └── web_socket.dart ├── pubspec.yaml └── test └── widget_test.dart /.github/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DWHengr/linyu_mobile/a207b3ce7dfeb3ed5aa5d2bc2f9c899926b61287/.github/logo.png -------------------------------------------------------------------------------- /.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 | *.lock 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 | .pub-cache/ 31 | .pub/ 32 | /build/ 33 | 34 | # Symbolication related 35 | app.*.symbols 36 | 37 | # Obfuscation related 38 | app.*.map.json 39 | 40 | # Android Studio will place build artifacts here 41 | /android/app/debug 42 | /android/app/profile 43 | /android/app/release 44 | -------------------------------------------------------------------------------- /.metadata: -------------------------------------------------------------------------------- 1 | # This file tracks properties of this Flutter project. 2 | # Used by Flutter tool to assess capabilities and perform upgrades etc. 3 | # 4 | # This file should be version controlled and should not be manually edited. 5 | 6 | version: 7 | revision: "80c2e84975bbd28ecf5f8d4bd4ca5a2490bfc819" 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: 80c2e84975bbd28ecf5f8d4bd4ca5a2490bfc819 17 | base_revision: 80c2e84975bbd28ecf5f8d4bd4ca5a2490bfc819 18 | - platform: android 19 | create_revision: 80c2e84975bbd28ecf5f8d4bd4ca5a2490bfc819 20 | base_revision: 80c2e84975bbd28ecf5f8d4bd4ca5a2490bfc819 21 | - platform: ios 22 | create_revision: 80c2e84975bbd28ecf5f8d4bd4ca5a2490bfc819 23 | base_revision: 80c2e84975bbd28ecf5f8d4bd4ca5a2490bfc819 24 | 25 | # User provided section 26 | 27 | # List of Local paths (relative to this file) that should be 28 | # ignored by the migrate tool. 29 | # 30 | # Files that are not part of the templates will be ignored by default. 31 | unmanaged_files: 32 | - 'lib/main.dart' 33 | - 'ios/Runner.xcodeproj/project.pbxproj' 34 | -------------------------------------------------------------------------------- /analysis_options.yaml: -------------------------------------------------------------------------------- 1 | # This file configures the analyzer, which statically analyzes Dart code to 2 | # check for errors, warnings, and lints. 3 | # 4 | # The issues identified by the analyzer are surfaced in the UI of Dart-enabled 5 | # IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be 6 | # invoked from the command line by running `flutter analyze`. 7 | 8 | # The following line activates a set of recommended lints for Flutter apps, 9 | # packages, and plugins designed to encourage good coding practices. 10 | analyzer: 11 | errors: 12 | avoid_function_literals_in_foreach_calls: ignore 13 | curly_braces_in_flow_control_structures: ignore 14 | unnecessary_new: ignore 15 | 16 | 17 | include: package:flutter_lints/flutter.yaml 18 | 19 | linter: 20 | # The lint rules applied to this project can be customized in the 21 | # section below to disable rules from the `package:flutter_lints/flutter.yaml` 22 | # included above or to enable additional rules. A list of all available lints 23 | # and their documentation is published at https://dart.dev/lints. 24 | # 25 | # Instead of disabling a lint rule for the entire project in the 26 | # section below, it can also be suppressed for a single line of code 27 | # or a specific dart file by using the `// ignore: name_of_lint` and 28 | # `// ignore_for_file: name_of_lint` syntax on the line or in the file 29 | # producing the lint. 30 | rules: 31 | # avoid_print: false # Uncomment to disable the `avoid_print` rule 32 | # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule 33 | 34 | # Additional information about this file can be found at 35 | # https://dart.dev/guides/language/analysis-options 36 | -------------------------------------------------------------------------------- /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/to/reference-keystore 11 | key.properties 12 | **/*.keystore 13 | **/*.jks 14 | -------------------------------------------------------------------------------- /android/app/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id "com.android.application" 3 | id "kotlin-android" 4 | // The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins. 5 | id "dev.flutter.flutter-gradle-plugin" 6 | } 7 | 8 | android { 9 | namespace = "com.cershy.linyu_mobile" 10 | compileSdk = flutter.compileSdkVersion 11 | ndkVersion = flutter.ndkVersion 12 | 13 | compileOptions { 14 | sourceCompatibility = JavaVersion.VERSION_1_8 15 | targetCompatibility = JavaVersion.VERSION_1_8 16 | } 17 | 18 | kotlinOptions { 19 | jvmTarget = JavaVersion.VERSION_1_8 20 | } 21 | 22 | defaultConfig { 23 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). 24 | applicationId = "com.cershy.linyu_mobile" 25 | // You can update the following values to match your application needs. 26 | // For more information, see: https://flutter.dev/to/review-gradle-config. 27 | minSdk = flutter.minSdkVersion 28 | targetSdk = flutter.targetSdkVersion 29 | versionCode = flutter.versionCode 30 | versionName = flutter.versionName 31 | } 32 | 33 | buildTypes { 34 | release { 35 | // TODO: Add your own signing config for the release build. 36 | // Signing with the debug keys for now, so `flutter run --release` works. 37 | signingConfig = signingConfigs.debug 38 | } 39 | } 40 | } 41 | 42 | flutter { 43 | source = "../.." 44 | } 45 | -------------------------------------------------------------------------------- /android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 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 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | -------------------------------------------------------------------------------- /android/app/src/main/kotlin/com/cershy/linyu_mobile/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.cershy.linyu_mobile 2 | 3 | import io.flutter.embedding.android.FlutterActivity 4 | 5 | class MainActivity: FlutterActivity() 6 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-v21/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DWHengr/linyu_mobile/a207b3ce7dfeb3ed5aa5d2bc2f9c899926b61287/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DWHengr/linyu_mobile/a207b3ce7dfeb3ed5aa5d2bc2f9c899926b61287/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DWHengr/linyu_mobile/a207b3ce7dfeb3ed5aa5d2bc2f9c899926b61287/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DWHengr/linyu_mobile/a207b3ce7dfeb3ed5aa5d2bc2f9c899926b61287/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DWHengr/linyu_mobile/a207b3ce7dfeb3ed5aa5d2bc2f9c899926b61287/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.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 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /android/app/src/main/res/xml/network_security_config.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /android/app/src/profile/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 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 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | allprojects { 2 | repositories { 3 | google() 4 | mavenCentral() 5 | } 6 | } 7 | 8 | rootProject.buildDir = "../build" 9 | subprojects { 10 | project.buildDir = "${rootProject.buildDir}/${project.name}" 11 | } 12 | subprojects { 13 | project.evaluationDependsOn(":app") 14 | } 15 | 16 | tasks.register("clean", Delete) { 17 | delete rootProject.buildDir 18 | } 19 | -------------------------------------------------------------------------------- /android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx4G -XX:MaxMetaspaceSize=2G -XX:+HeapDumpOnOutOfMemoryError 2 | android.useAndroidX=true 3 | android.enableJetifier=true 4 | -------------------------------------------------------------------------------- /android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | zipStoreBase=GRADLE_USER_HOME 4 | zipStorePath=wrapper/dists 5 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.3-all.zip 6 | -------------------------------------------------------------------------------- /android/settings.gradle: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | def flutterSdkPath = { 3 | def properties = new Properties() 4 | file("local.properties").withInputStream { properties.load(it) } 5 | def flutterSdkPath = properties.getProperty("flutter.sdk") 6 | assert flutterSdkPath != null, "flutter.sdk not set in local.properties" 7 | return flutterSdkPath 8 | }() 9 | 10 | includeBuild("$flutterSdkPath/packages/flutter_tools/gradle") 11 | 12 | repositories { 13 | google() 14 | mavenCentral() 15 | gradlePluginPortal() 16 | } 17 | } 18 | 19 | plugins { 20 | id "dev.flutter.flutter-plugin-loader" version "1.0.0" 21 | id "com.android.application" version "7.3.0" apply false 22 | id "org.jetbrains.kotlin.android" version "1.9.10" apply false 23 | } 24 | 25 | include ":app" 26 | -------------------------------------------------------------------------------- /assets/iconfont/iconfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DWHengr/linyu_mobile/a207b3ce7dfeb3ed5aa5d2bc2f9c899926b61287/assets/iconfont/iconfont.ttf -------------------------------------------------------------------------------- /assets/images/call.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DWHengr/linyu_mobile/a207b3ce7dfeb3ed5aa5d2bc2f9c899926b61287/assets/images/call.png -------------------------------------------------------------------------------- /assets/images/chat-blue.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DWHengr/linyu_mobile/a207b3ce7dfeb3ed5aa5d2bc2f9c899926b61287/assets/images/chat-blue.png -------------------------------------------------------------------------------- /assets/images/chat-empty.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DWHengr/linyu_mobile/a207b3ce7dfeb3ed5aa5d2bc2f9c899926b61287/assets/images/chat-empty.png -------------------------------------------------------------------------------- /assets/images/chat-pink.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DWHengr/linyu_mobile/a207b3ce7dfeb3ed5aa5d2bc2f9c899926b61287/assets/images/chat-pink.png -------------------------------------------------------------------------------- /assets/images/default-portrait.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DWHengr/linyu_mobile/a207b3ce7dfeb3ed5aa5d2bc2f9c899926b61287/assets/images/default-portrait.jpeg -------------------------------------------------------------------------------- /assets/images/edit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DWHengr/linyu_mobile/a207b3ce7dfeb3ed5aa5d2bc2f9c899926b61287/assets/images/edit.png -------------------------------------------------------------------------------- /assets/images/empty-bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DWHengr/linyu_mobile/a207b3ce7dfeb3ed5aa5d2bc2f9c899926b61287/assets/images/empty-bg.png -------------------------------------------------------------------------------- /assets/images/file-blue.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DWHengr/linyu_mobile/a207b3ce7dfeb3ed5aa5d2bc2f9c899926b61287/assets/images/file-blue.png -------------------------------------------------------------------------------- /assets/images/file-pink.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DWHengr/linyu_mobile/a207b3ce7dfeb3ed5aa5d2bc2f9c899926b61287/assets/images/file-pink.png -------------------------------------------------------------------------------- /assets/images/file-white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DWHengr/linyu_mobile/a207b3ce7dfeb3ed5aa5d2bc2f9c899926b61287/assets/images/file-white.png -------------------------------------------------------------------------------- /assets/images/flutter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DWHengr/linyu_mobile/a207b3ce7dfeb3ed5aa5d2bc2f9c899926b61287/assets/images/flutter.png -------------------------------------------------------------------------------- /assets/images/group.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DWHengr/linyu_mobile/a207b3ce7dfeb3ed5aa5d2bc2f9c899926b61287/assets/images/group.png -------------------------------------------------------------------------------- /assets/images/icon-circle-of-friends.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DWHengr/linyu_mobile/a207b3ce7dfeb3ed5aa5d2bc2f9c899926b61287/assets/images/icon-circle-of-friends.png -------------------------------------------------------------------------------- /assets/images/linyu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DWHengr/linyu_mobile/a207b3ce7dfeb3ed5aa5d2bc2f9c899926b61287/assets/images/linyu.png -------------------------------------------------------------------------------- /assets/images/logo-about.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DWHengr/linyu_mobile/a207b3ce7dfeb3ed5aa5d2bc2f9c899926b61287/assets/images/logo-about.png -------------------------------------------------------------------------------- /assets/images/logo-login-blue.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DWHengr/linyu_mobile/a207b3ce7dfeb3ed5aa5d2bc2f9c899926b61287/assets/images/logo-login-blue.png -------------------------------------------------------------------------------- /assets/images/logo-login-pink.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DWHengr/linyu_mobile/a207b3ce7dfeb3ed5aa5d2bc2f9c899926b61287/assets/images/logo-login-pink.png -------------------------------------------------------------------------------- /assets/images/logo-qr-blue.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DWHengr/linyu_mobile/a207b3ce7dfeb3ed5aa5d2bc2f9c899926b61287/assets/images/logo-qr-blue.png -------------------------------------------------------------------------------- /assets/images/logo-qr-pink.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DWHengr/linyu_mobile/a207b3ce7dfeb3ed5aa5d2bc2f9c899926b61287/assets/images/logo-qr-pink.png -------------------------------------------------------------------------------- /assets/images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DWHengr/linyu_mobile/a207b3ce7dfeb3ed5aa5d2bc2f9c899926b61287/assets/images/logo.png -------------------------------------------------------------------------------- /assets/images/mine-about.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DWHengr/linyu_mobile/a207b3ce7dfeb3ed5aa5d2bc2f9c899926b61287/assets/images/mine-about.png -------------------------------------------------------------------------------- /assets/images/mine-blue.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DWHengr/linyu_mobile/a207b3ce7dfeb3ed5aa5d2bc2f9c899926b61287/assets/images/mine-blue.png -------------------------------------------------------------------------------- /assets/images/mine-empty.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DWHengr/linyu_mobile/a207b3ce7dfeb3ed5aa5d2bc2f9c899926b61287/assets/images/mine-empty.png -------------------------------------------------------------------------------- /assets/images/mine-notify-blue.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DWHengr/linyu_mobile/a207b3ce7dfeb3ed5aa5d2bc2f9c899926b61287/assets/images/mine-notify-blue.png -------------------------------------------------------------------------------- /assets/images/mine-notify-pink.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DWHengr/linyu_mobile/a207b3ce7dfeb3ed5aa5d2bc2f9c899926b61287/assets/images/mine-notify-pink.png -------------------------------------------------------------------------------- /assets/images/mine-password.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DWHengr/linyu_mobile/a207b3ce7dfeb3ed5aa5d2bc2f9c899926b61287/assets/images/mine-password.png -------------------------------------------------------------------------------- /assets/images/mine-pink.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DWHengr/linyu_mobile/a207b3ce7dfeb3ed5aa5d2bc2f9c899926b61287/assets/images/mine-pink.png -------------------------------------------------------------------------------- /assets/images/mine-set.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DWHengr/linyu_mobile/a207b3ce7dfeb3ed5aa5d2bc2f9c899926b61287/assets/images/mine-set.png -------------------------------------------------------------------------------- /assets/images/mine-talk-blue.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DWHengr/linyu_mobile/a207b3ce7dfeb3ed5aa5d2bc2f9c899926b61287/assets/images/mine-talk-blue.png -------------------------------------------------------------------------------- /assets/images/mine-talk-pink.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DWHengr/linyu_mobile/a207b3ce7dfeb3ed5aa5d2bc2f9c899926b61287/assets/images/mine-talk-pink.png -------------------------------------------------------------------------------- /assets/images/more.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DWHengr/linyu_mobile/a207b3ce7dfeb3ed5aa5d2bc2f9c899926b61287/assets/images/more.png -------------------------------------------------------------------------------- /assets/images/qr-affirm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DWHengr/linyu_mobile/a207b3ce7dfeb3ed5aa5d2bc2f9c899926b61287/assets/images/qr-affirm.png -------------------------------------------------------------------------------- /assets/images/signature.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DWHengr/linyu_mobile/a207b3ce7dfeb3ed5aa5d2bc2f9c899926b61287/assets/images/signature.png -------------------------------------------------------------------------------- /assets/images/talk-blue.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DWHengr/linyu_mobile/a207b3ce7dfeb3ed5aa5d2bc2f9c899926b61287/assets/images/talk-blue.png -------------------------------------------------------------------------------- /assets/images/talk-empty.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DWHengr/linyu_mobile/a207b3ce7dfeb3ed5aa5d2bc2f9c899926b61287/assets/images/talk-empty.png -------------------------------------------------------------------------------- /assets/images/talk-pink.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DWHengr/linyu_mobile/a207b3ce7dfeb3ed5aa5d2bc2f9c899926b61287/assets/images/talk-pink.png -------------------------------------------------------------------------------- /assets/images/user-blue.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DWHengr/linyu_mobile/a207b3ce7dfeb3ed5aa5d2bc2f9c899926b61287/assets/images/user-blue.png -------------------------------------------------------------------------------- /assets/images/user-empty.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DWHengr/linyu_mobile/a207b3ce7dfeb3ed5aa5d2bc2f9c899926b61287/assets/images/user-empty.png -------------------------------------------------------------------------------- /assets/images/user-pink.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DWHengr/linyu_mobile/a207b3ce7dfeb3ed5aa5d2bc2f9c899926b61287/assets/images/user-pink.png -------------------------------------------------------------------------------- /assets/sounds/success.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DWHengr/linyu_mobile/a207b3ce7dfeb3ed5aa5d2bc2f9c899926b61287/assets/sounds/success.mp3 -------------------------------------------------------------------------------- /ios/.gitignore: -------------------------------------------------------------------------------- 1 | **/dgph 2 | *.mode1v3 3 | *.mode2v3 4 | *.moved-aside 5 | *.pbxuser 6 | *.perspectivev3 7 | **/*sync/ 8 | .sconsign.dblite 9 | .tags* 10 | **/.vagrant/ 11 | **/DerivedData/ 12 | Icon? 13 | **/Pods/ 14 | **/.symlinks/ 15 | profile 16 | xcuserdata 17 | **/.generated/ 18 | Flutter/App.framework 19 | Flutter/Flutter.framework 20 | Flutter/Flutter.podspec 21 | Flutter/Generated.xcconfig 22 | Flutter/ephemeral/ 23 | Flutter/app.flx 24 | Flutter/app.zip 25 | Flutter/flutter_assets/ 26 | Flutter/flutter_export_environment.sh 27 | ServiceDefinitions.json 28 | Runner/GeneratedPluginRegistrant.* 29 | 30 | # Exceptions to above rules. 31 | !default.mode1v3 32 | !default.mode2v3 33 | !default.pbxuser 34 | !default.perspectivev3 35 | -------------------------------------------------------------------------------- /ios/Flutter/AppFrameworkInfo.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | App 9 | CFBundleIdentifier 10 | io.flutter.flutter.app 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | App 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1.0 23 | MinimumOSVersion 24 | 12.0 25 | 26 | 27 | -------------------------------------------------------------------------------- /ios/Flutter/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Generated.xcconfig" 2 | -------------------------------------------------------------------------------- /ios/Flutter/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Generated.xcconfig" 2 | -------------------------------------------------------------------------------- /ios/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.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Runner/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import Flutter 2 | import UIKit 3 | 4 | @main 5 | @objc class AppDelegate: FlutterAppDelegate { 6 | override func application( 7 | _ application: UIApplication, 8 | didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? 9 | ) -> Bool { 10 | GeneratedPluginRegistrant.register(with: self) 11 | return super.application(application, didFinishLaunchingWithOptions: launchOptions) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 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/DWHengr/linyu_mobile/a207b3ce7dfeb3ed5aa5d2bc2f9c899926b61287/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/DWHengr/linyu_mobile/a207b3ce7dfeb3ed5aa5d2bc2f9c899926b61287/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/DWHengr/linyu_mobile/a207b3ce7dfeb3ed5aa5d2bc2f9c899926b61287/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/DWHengr/linyu_mobile/a207b3ce7dfeb3ed5aa5d2bc2f9c899926b61287/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/DWHengr/linyu_mobile/a207b3ce7dfeb3ed5aa5d2bc2f9c899926b61287/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/DWHengr/linyu_mobile/a207b3ce7dfeb3ed5aa5d2bc2f9c899926b61287/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/DWHengr/linyu_mobile/a207b3ce7dfeb3ed5aa5d2bc2f9c899926b61287/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/DWHengr/linyu_mobile/a207b3ce7dfeb3ed5aa5d2bc2f9c899926b61287/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/DWHengr/linyu_mobile/a207b3ce7dfeb3ed5aa5d2bc2f9c899926b61287/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/DWHengr/linyu_mobile/a207b3ce7dfeb3ed5aa5d2bc2f9c899926b61287/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DWHengr/linyu_mobile/a207b3ce7dfeb3ed5aa5d2bc2f9c899926b61287/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/DWHengr/linyu_mobile/a207b3ce7dfeb3ed5aa5d2bc2f9c899926b61287/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DWHengr/linyu_mobile/a207b3ce7dfeb3ed5aa5d2bc2f9c899926b61287/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/DWHengr/linyu_mobile/a207b3ce7dfeb3ed5aa5d2bc2f9c899926b61287/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/DWHengr/linyu_mobile/a207b3ce7dfeb3ed5aa5d2bc2f9c899926b61287/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "LaunchImage.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "LaunchImage@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "LaunchImage@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DWHengr/linyu_mobile/a207b3ce7dfeb3ed5aa5d2bc2f9c899926b61287/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DWHengr/linyu_mobile/a207b3ce7dfeb3ed5aa5d2bc2f9c899926b61287/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DWHengr/linyu_mobile/a207b3ce7dfeb3ed5aa5d2bc2f9c899926b61287/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md: -------------------------------------------------------------------------------- 1 | # Launch Screen Assets 2 | 3 | You can customize the launch screen with your own desired assets by replacing the image files in this directory. 4 | 5 | You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. -------------------------------------------------------------------------------- /ios/Runner/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /ios/Runner/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /ios/Runner/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleDisplayName 8 | Linyu Mobile 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | linyu_mobile 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | $(FLUTTER_BUILD_NAME) 21 | CFBundleSignature 22 | ???? 23 | CFBundleVersion 24 | $(FLUTTER_BUILD_NUMBER) 25 | LSRequiresIPhoneOS 26 | 27 | UILaunchStoryboardName 28 | LaunchScreen 29 | UIMainStoryboardFile 30 | Main 31 | UISupportedInterfaceOrientations 32 | 33 | UIInterfaceOrientationPortrait 34 | UIInterfaceOrientationLandscapeLeft 35 | UIInterfaceOrientationLandscapeRight 36 | 37 | UISupportedInterfaceOrientations~ipad 38 | 39 | UIInterfaceOrientationPortrait 40 | UIInterfaceOrientationPortraitUpsideDown 41 | UIInterfaceOrientationLandscapeLeft 42 | UIInterfaceOrientationLandscapeRight 43 | 44 | CADisableMinimumFrameDurationOnPhone 45 | 46 | UIApplicationSupportsIndirectInputEvents 47 | 48 | LSApplicationQueriesSchemes 49 | 50 | sms 51 | tel 52 | http 53 | https 54 | 55 | NSPhotoLibraryAddUsageDescription 56 | 需要访问相册权限以保存图片 57 | 58 | 59 | -------------------------------------------------------------------------------- /ios/Runner/Runner-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | #import "GeneratedPluginRegistrant.h" 2 | -------------------------------------------------------------------------------- /ios/RunnerTests/RunnerTests.swift: -------------------------------------------------------------------------------- 1 | import Flutter 2 | import UIKit 3 | import XCTest 4 | 5 | class RunnerTests: XCTestCase { 6 | 7 | func testExample() { 8 | // If you add code to the Runner application, consider adding tests here. 9 | // See https://developer.apple.com/documentation/xctest for more information about using XCTest. 10 | } 11 | 12 | } 13 | -------------------------------------------------------------------------------- /lib/api/Http.dart: -------------------------------------------------------------------------------- 1 | import 'package:dio/dio.dart'; 2 | import 'package:flutter/foundation.dart' show kReleaseMode; 3 | import 'package:pretty_dio_logger/pretty_dio_logger.dart' show PrettyDioLogger; 4 | import 'package:shared_preferences/shared_preferences.dart'; 5 | 6 | class Http { 7 | static final Http _instance = Http._internal(); 8 | 9 | factory Http() => _instance; 10 | 11 | late Dio dio; 12 | 13 | Http._internal() { 14 | dio = Dio(BaseOptions( 15 | baseUrl: 'http://192.168.1.17:9200', 16 | connectTimeout: const Duration(seconds: 20), 17 | receiveTimeout: const Duration(seconds: 20), 18 | )); 19 | // 添加拦截器 20 | dio.interceptors 21 | .add(InterceptorsWrapper(onRequest: (options, handler) async { 22 | // 从本地存储获取token 23 | SharedPreferences prefs = await SharedPreferences.getInstance(); 24 | final token = prefs.getString('x-token'); 25 | if (token != null) { 26 | options.headers['x-token'] = token; 27 | } 28 | return handler.next(options); 29 | }, onError: (error, handler) { 30 | if (error.response?.statusCode == 401) { 31 | // 处理token过期 32 | } 33 | return handler.next(error); 34 | })); 35 | if (!kReleaseMode) 36 | dio.interceptors.add(PrettyDioLogger( 37 | // requestHeader: true, 38 | // requestBody: true, 39 | // responseHeader: true, 40 | responseBody: true, 41 | )); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /lib/api/chat_group_api.dart: -------------------------------------------------------------------------------- 1 | import 'package:dio/dio.dart'; 2 | import 'package:linyu_mobile/api/Http.dart'; 3 | 4 | class ChatGroupApi { 5 | final Dio _dio = Http().dio; 6 | 7 | static final ChatGroupApi _instance = ChatGroupApi._internal(); 8 | 9 | ChatGroupApi._internal(); 10 | 11 | factory ChatGroupApi() { 12 | return _instance; 13 | } 14 | 15 | Future> list() async { 16 | final response = await _dio.get('/v1/api/chat-group/list'); 17 | return response.data; 18 | } 19 | 20 | Future> details(String chatGroupId) async { 21 | final response = await _dio 22 | .post('/v1/api/chat-group/details', data: {'chatGroupId': chatGroupId}); 23 | return response.data; 24 | } 25 | 26 | Future> upload(FormData formData) async { 27 | final response = await _dio.post( 28 | '/v1/api/chat-group/upload/portrait/form', 29 | data: formData, 30 | ); 31 | return response.data; 32 | } 33 | 34 | Future> createWithPerson( 35 | String name, String? notice, List users) async { 36 | final response = await _dio.post('/v1/api/chat-group/create', data: { 37 | 'name': name, 38 | 'notice': notice, 39 | 'users': users, 40 | }); 41 | return response.data; 42 | } 43 | 44 | Future> update( 45 | String chatGroupId, String key, dynamic value) async { 46 | final response = await _dio.post('/v1/api/chat-group/update', 47 | data: {'groupId': chatGroupId, 'updateKey': key, 'updateValue': value}); 48 | return response.data; 49 | } 50 | 51 | Future> updateName( 52 | String chatGroupId, String name) async { 53 | final response = await _dio.post('/v1/api/chat-group/update/name', data: { 54 | 'groupId': chatGroupId, 55 | 'name': name, 56 | }); 57 | return response.data; 58 | } 59 | 60 | Future> kickChatGroup( 61 | String groupId, String userId) async { 62 | final response = await _dio.post('/v1/api/chat-group/kick', data: { 63 | 'groupId': groupId, 64 | 'userId': userId, 65 | }); 66 | return response.data; 67 | } 68 | 69 | Future> inviteMember( 70 | String groupId, List ids) async { 71 | final response = await _dio.post('/v1/api/chat-group/invite', data: { 72 | 'groupId': groupId, 73 | 'userIds': ids, 74 | }); 75 | return response.data; 76 | } 77 | 78 | Future> transferChatGroup( 79 | String groupId, String userId) async { 80 | final response = await _dio.post('/v1/api/chat-group/transfer', data: { 81 | 'groupId': groupId, 82 | 'userId': userId, 83 | }); 84 | return response.data; 85 | } 86 | 87 | Future> create(String name) async { 88 | final response = await _dio.post('/v1/api/chat-group/create', data: { 89 | 'name': name, 90 | }); 91 | return response.data; 92 | } 93 | 94 | Future> quitChatGroup(String groupId) async { 95 | final response = await _dio.post('/v1/api/chat-group/quit', data: { 96 | 'groupId': groupId, 97 | }); 98 | return response.data; 99 | } 100 | 101 | Future> dissolveChatGroup(String groupId) async { 102 | final response = await _dio.post('/v1/api/chat-group/dissolve', data: { 103 | 'groupId': groupId, 104 | }); 105 | return response.data; 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /lib/api/chat_group_member.dart: -------------------------------------------------------------------------------- 1 | import 'package:dio/dio.dart'; 2 | import 'package:linyu_mobile/api/Http.dart'; 3 | 4 | class ChatGroupMemberApi { 5 | final Dio _dio = Http().dio; 6 | 7 | static final ChatGroupMemberApi _instance = ChatGroupMemberApi._internal(); 8 | 9 | ChatGroupMemberApi._internal(); 10 | 11 | factory ChatGroupMemberApi() { 12 | return _instance; 13 | } 14 | 15 | Future> list(String chatGroupId) async { 16 | final response = await _dio.post('/v1/api/chat-group-member/list', 17 | data: {'chatGroupId': chatGroupId}); 18 | return response.data; 19 | } 20 | 21 | Future> listPage(String chatGroupId) async { 22 | final response = await _dio.post('/v1/api/chat-group-member/list/page', 23 | data: {'chatGroupId': chatGroupId}); 24 | return response.data; 25 | } 26 | 27 | Future> setChatBackground(FormData formData) async { 28 | final response = await _dio.post( 29 | '/v1/api/chat-group-member/set-chat-background', 30 | data: formData, 31 | ); 32 | return response.data; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /lib/api/chat_group_notice_api.dart: -------------------------------------------------------------------------------- 1 | import 'package:dio/dio.dart'; 2 | import 'package:linyu_mobile/api/Http.dart'; 3 | 4 | class ChatGroupNoticeApi { 5 | final Dio _dio = Http().dio; 6 | 7 | static final ChatGroupNoticeApi _instance = ChatGroupNoticeApi._internal(); 8 | 9 | ChatGroupNoticeApi._internal(); 10 | 11 | factory ChatGroupNoticeApi() { 12 | return _instance; 13 | } 14 | 15 | Future> list(String chatGroupId) async { 16 | final response = await _dio 17 | .post('/v1/api/chat-group-notice/list', data: {'groupId': chatGroupId}); 18 | return response.data; 19 | } 20 | 21 | Future> delete(String groupId, String noticeId) async { 22 | final response = await _dio.post('/v1/api/chat-group-notice/delete', 23 | data: {'groupId': groupId, 'noticeId': noticeId}); 24 | return response.data; 25 | } 26 | 27 | Future> create(String groupId, String content) async { 28 | final response = await _dio.post('/v1/api/chat-group-notice/create', 29 | data: {'groupId': groupId, 'content': content}); 30 | return response.data; 31 | } 32 | 33 | Future> update( 34 | String groupId, String groupNoticeId, String content) async { 35 | final response = await _dio.post('/v1/api/chat-group-notice/update', data: { 36 | 'groupId': groupId, 37 | 'noticeId': groupNoticeId, 38 | 'noticeContent': content 39 | }); 40 | return response.data; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /lib/api/chat_list_api.dart: -------------------------------------------------------------------------------- 1 | // lib/services/user_service.dart 2 | import 'package:dio/dio.dart'; 3 | import 'package:linyu_mobile/api/Http.dart'; 4 | 5 | class ChatListApi { 6 | final Dio _dio = Http().dio; 7 | 8 | static final ChatListApi _instance = ChatListApi._internal(); 9 | 10 | ChatListApi._internal(); 11 | 12 | Future> list() async { 13 | final response = await _dio.get('/v1/api/chat-list/list'); 14 | return response.data; 15 | } 16 | 17 | factory ChatListApi() { 18 | return _instance; 19 | } 20 | 21 | Future> top(String chatListId, bool isTop) async { 22 | final response = await _dio.post( 23 | '/v1/api/chat-list/top', 24 | data: {'chatListId': chatListId, 'isTop': isTop}, 25 | ); 26 | return response.data; 27 | } 28 | 29 | Future> create(String toId, 30 | {String? type = 'user'}) async { 31 | final response = await _dio.post( 32 | '/v1/api/chat-list/create', 33 | data: { 34 | 'toId': toId, 35 | 'type': type, 36 | }, 37 | ); 38 | return response.data; 39 | } 40 | 41 | Future> delete(String chatListId) async { 42 | final response = await _dio.post( 43 | '/v1/api/chat-list/delete', 44 | data: {'chatListId': chatListId}, 45 | ); 46 | return response.data; 47 | } 48 | 49 | Future> read(String targetId) async { 50 | final response = await _dio.get('/v1/api/chat-list/read/$targetId'); 51 | return response.data; 52 | } 53 | 54 | Future> detail(String targetId, String type) async { 55 | final response = await _dio.post('/v1/api/chat-list/detail', 56 | data: {'targetId': targetId, 'type': type}); 57 | return response.data; 58 | } 59 | 60 | Future> createChat(String toId, 61 | {String? type = 'user'}) async { 62 | final response = await _dio.post( 63 | '/v1/api/chat-list/create', 64 | data: { 65 | 'userId': toId, 66 | 'type': type, 67 | }, 68 | ); 69 | return response.data; 70 | } 71 | 72 | Future> search(String searchInfo) async { 73 | final response = await _dio.post( 74 | '/v1/api/chat-list/search', 75 | data: {'searchInfo': searchInfo}, 76 | ); 77 | return response.data; 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /lib/api/group_api.dart: -------------------------------------------------------------------------------- 1 | import 'package:dio/dio.dart'; 2 | import 'package:linyu_mobile/api/Http.dart'; 3 | 4 | class GroupApi { 5 | final Dio _dio = Http().dio; 6 | 7 | static final GroupApi _instance = GroupApi._internal(); 8 | 9 | GroupApi._internal(); 10 | 11 | factory GroupApi() { 12 | return _instance; 13 | } 14 | 15 | Future> list() async { 16 | final response = await _dio.get('/v1/api/group/list'); 17 | return response.data; 18 | } 19 | 20 | Future> create(String groupName) async { 21 | final response = 22 | await _dio.post('/v1/api/group/create', data: {'groupName': groupName}); 23 | return response.data; 24 | } 25 | 26 | Future> update(String groupId,String groupName) async { 27 | final response = await _dio.post('/v1/api/group/update', data: { 28 | 'groupId':groupId, 29 | 'groupName': groupName, 30 | }); 31 | return response.data; 32 | } 33 | 34 | Future> delete(String groupId) async { 35 | final response = await _dio.post('/v1/api/group/delete', data: { 36 | 'groupId': groupId, 37 | }); 38 | return response.data; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /lib/api/msg_api.dart: -------------------------------------------------------------------------------- 1 | import 'package:dio/dio.dart'; 2 | import 'package:flutter/foundation.dart'; 3 | import 'package:linyu_mobile/api/Http.dart'; 4 | 5 | class MsgApi { 6 | final Dio _dio = Http().dio; 7 | 8 | static final MsgApi _instance = MsgApi._internal(); 9 | 10 | MsgApi._internal(); 11 | 12 | factory MsgApi() { 13 | return _instance; 14 | } 15 | 16 | Future> record( 17 | String targetId, int index, int num) async { 18 | final response = await _dio.post('/v1/api/message/record', 19 | data: {'targetId': targetId, 'index': index, 'num': num}); 20 | return response.data; 21 | } 22 | 23 | Future> send(dynamic msg) async { 24 | final response = await _dio.post('/v1/api/message/send', data: msg); 25 | return response.data; 26 | } 27 | 28 | Future> getMedia(String msgId) async { 29 | final response = await _dio 30 | .get('/v1/api/message/get/media', queryParameters: {'msgId': msgId}); 31 | return response.data; 32 | } 33 | 34 | Future> sendMedia(FormData formData) async { 35 | final response = 36 | await _dio.post('/v1/api/message/send/file/form', data: formData); 37 | return response.data; 38 | } 39 | 40 | Future> retract(String msgId, String? targetId) async { 41 | final response = await _dio.post('/v1/api/message/retraction', 42 | data: {'msgId': msgId, 'targetId': targetId}); 43 | return response.data; 44 | } 45 | 46 | Future> reEdit(String msgId) async { 47 | final response = 48 | await _dio.post('/v1/api/message/reedit', data: {'msgId': msgId}); 49 | return response.data; 50 | } 51 | 52 | Future> voiceToText(String msgId, 53 | {bool? isChatGroupMessage}) async { 54 | if (msgId.isEmpty) return {'error': 'msgId 不能为空'}; 55 | try { 56 | final response = await _dio.get('/v1/api/message/voice/to/text/from', 57 | queryParameters: { 58 | 'msgId': msgId, 59 | 'isChatGroupMessage': isChatGroupMessage 60 | }); 61 | return response.data ?? {}; // 增加空值处理 62 | } on DioException catch (e) { 63 | if (kDebugMode) print('请求失败: ${e.message}'); 64 | return {'error': e.message}; // 返回错误信息 65 | } catch (e) { 66 | if (kDebugMode) print('发生未知错误: $e'); 67 | return {'error': '发生未知错误'}; 68 | } 69 | } 70 | 71 | Future> getLifeString() async { 72 | try { 73 | final response = await _dio.get('https://api.xygeng.cn/one'); 74 | return response.data ?? {}; // 增加空值处理 75 | } on DioException catch (e) { 76 | if (kDebugMode) print('请求失败: ${e.message}'); 77 | return {'error': e.message}; // 返回错误信息 78 | } catch (e) { 79 | if (kDebugMode) print('发生未知错误: $e'); 80 | return {'error': '发生未知错误'}; 81 | } 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /lib/api/notify_api.dart: -------------------------------------------------------------------------------- 1 | import 'package:dio/dio.dart'; 2 | import 'package:linyu_mobile/api/Http.dart'; 3 | 4 | class NotifyApi { 5 | final Dio _dio = Http().dio; 6 | 7 | static final NotifyApi _instance = NotifyApi._internal(); 8 | 9 | NotifyApi._internal(); 10 | 11 | factory NotifyApi() { 12 | return _instance; 13 | } 14 | 15 | Future> friendList() async { 16 | final response = await _dio.get('/v1/api/notify/friend/list'); 17 | return response.data; 18 | } 19 | 20 | Future> friendApply( 21 | String userId, String content) async { 22 | final response = await _dio.post( 23 | '/v1/api/notify/friend/apply', 24 | data: {'userId': userId, 'content': content}, 25 | ); 26 | return response.data; 27 | } 28 | 29 | Future> read(type) async { 30 | final response = 31 | await _dio.post('/v1/api/notify/read', data: {'notifyType': type}); 32 | return response.data; 33 | } 34 | 35 | Future> systemListNotify() async { 36 | final response = await _dio.get('/v1/api/notify/system/list'); 37 | return response.data; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /lib/api/qr_api.dart: -------------------------------------------------------------------------------- 1 | import 'package:dio/dio.dart'; 2 | import 'package:linyu_mobile/api/Http.dart'; 3 | 4 | class QrApi { 5 | final Dio _dio = Http().dio; 6 | 7 | static final QrApi _instance = QrApi._internal(); 8 | 9 | QrApi._internal(); 10 | 11 | factory QrApi() { 12 | return _instance; 13 | } 14 | 15 | Future> code() async { 16 | final response = 17 | await _dio.get('/qr/code', queryParameters: {'action': 'mine'}); 18 | return response.data; 19 | } 20 | 21 | Future> status(String? key) async { 22 | final response = await _dio.get('/qr/code/status', data: {'key': key}); 23 | return response.data; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /lib/api/talk_api.dart: -------------------------------------------------------------------------------- 1 | // lib/services/user_service.dart 2 | import 'package:dio/dio.dart'; 3 | import 'package:linyu_mobile/api/Http.dart'; 4 | 5 | class TalkApi { 6 | final Dio _dio = Http().dio; 7 | 8 | static final TalkApi _instance = TalkApi._internal(); 9 | 10 | TalkApi._internal(); 11 | 12 | factory TalkApi() { 13 | return _instance; 14 | } 15 | 16 | Future> list(int index, int num, String targetId) async { 17 | final response = await _dio.post('/v1/api/talk/list', 18 | data: {'index': index, 'num': num, 'targetId': targetId}); 19 | return response.data; 20 | } 21 | 22 | Future> details(String talkId) async { 23 | final response = 24 | await _dio.post('/v1/api/talk/details', data: {'talkId': talkId}); 25 | return response.data; 26 | } 27 | 28 | Future> uploadImg(FormData formData) async { 29 | final response = 30 | await _dio.post('/v1/api/talk/upload/img/form', data: formData); 31 | return response.data; 32 | } 33 | 34 | Future> create(String text, List permission) async { 35 | final response = await _dio.post('/v1/api/talk/create', 36 | data: {'text': text, 'permission': permission}); 37 | return response.data; 38 | } 39 | 40 | Future> delete(String talkId) async { 41 | final response = 42 | await _dio.post('/v1/api/talk/delete', data: {'talkId': talkId}); 43 | return response.data; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /lib/api/talk_comment_api.dart: -------------------------------------------------------------------------------- 1 | import 'package:dio/dio.dart'; 2 | import 'package:linyu_mobile/api/Http.dart'; 3 | 4 | class TalkCommentApi { 5 | final Dio _dio = Http().dio; 6 | 7 | static final TalkCommentApi _instance = TalkCommentApi._internal(); 8 | 9 | TalkCommentApi._internal(); 10 | 11 | factory TalkCommentApi() { 12 | return _instance; 13 | } 14 | 15 | Future> list(String talkId) async { 16 | final response = 17 | await _dio.post('/v1/api/talk-comment/list', data: {'talkId': talkId}); 18 | return response.data; 19 | } 20 | 21 | Future> create(String talkId, String comment) async { 22 | final response = await _dio.post('/v1/api/talk-comment/create', 23 | data: {'talkId': talkId, 'comment': comment}); 24 | return response.data; 25 | } 26 | 27 | Future> delete( 28 | String talkId, String talkCommentId) async { 29 | final response = await _dio.post('/v1/api/talk-comment/delete', 30 | data: {'talkId': talkId, 'talkCommentId': talkCommentId}); 31 | return response.data; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /lib/api/talk_like_api.dart: -------------------------------------------------------------------------------- 1 | import 'package:dio/dio.dart'; 2 | import 'package:linyu_mobile/api/Http.dart'; 3 | 4 | class TalkLikeApi { 5 | final Dio _dio = Http().dio; 6 | 7 | static final TalkLikeApi _instance = TalkLikeApi._internal(); 8 | 9 | TalkLikeApi._internal(); 10 | 11 | factory TalkLikeApi() { 12 | return _instance; 13 | } 14 | 15 | Future> list(String talkId) async { 16 | final response = 17 | await _dio.post('/v1/api/talk-like/list', data: {'talkId': talkId}); 18 | return response.data; 19 | } 20 | 21 | Future> create(String talkId) async { 22 | final response = 23 | await _dio.post('/v1/api/talk-like/create', data: {'talkId': talkId}); 24 | return response.data; 25 | } 26 | 27 | Future> delete(String talkId) async { 28 | final response = 29 | await _dio.post('/v1/api/talk-like/delete', data: {'talkId': talkId}); 30 | return response.data; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /lib/api/video_api.dart: -------------------------------------------------------------------------------- 1 | import 'package:dio/dio.dart'; 2 | import 'package:linyu_mobile/api/Http.dart'; 3 | 4 | class VideoApi { 5 | final Dio _dio = Http().dio; 6 | 7 | static final VideoApi _instance = VideoApi._internal(); 8 | 9 | VideoApi._internal(); 10 | 11 | factory VideoApi() { 12 | return _instance; 13 | } 14 | 15 | Future> offer(String userId, dynamic desc) async { 16 | final response = await _dio.post('/v1/api/video/offer', data: { 17 | 'userId': userId, 18 | 'desc': desc, 19 | }); 20 | return response.data; 21 | } 22 | 23 | Future> answer(String userId, dynamic desc) async { 24 | final response = await _dio.post( 25 | '/v1/api/video/answer', 26 | data: {'userId': userId, 'desc': desc}, 27 | ); 28 | return response.data; 29 | } 30 | 31 | Future> candidate( 32 | String userId, dynamic candidate) async { 33 | final response = await _dio.post( 34 | '/v1/api/video/candidate', 35 | data: {'userId': userId, 'candidate': candidate}, 36 | ); 37 | return response.data; 38 | } 39 | 40 | Future> hangup(String userId) async { 41 | final response = await _dio.post( 42 | '/v1/api/video/hangup', 43 | data: {'userId': userId}, 44 | ); 45 | return response.data; 46 | } 47 | 48 | Future> invite(String userId, bool isOnlyAudio) async { 49 | final response = await _dio.post( 50 | '/v1/api/video/invite', 51 | data: {'userId': userId, 'isOnlyAudio': isOnlyAudio}, 52 | ); 53 | return response.data; 54 | } 55 | 56 | Future> accept(String userId) async { 57 | final response = await _dio.post( 58 | '/v1/api/video/accept', 59 | data: {'userId': userId}, 60 | ); 61 | return response.data; 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /lib/components/CustomDialog/index.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:get/get.dart'; 3 | import 'package:linyu_mobile/components/custom_button/index.dart'; 4 | import 'package:linyu_mobile/utils/getx_config/GlobalThemeConfig.dart'; 5 | 6 | class CustomDialog { 7 | static void showTipDialog(BuildContext context, 8 | {required String text, 9 | required VoidCallback onOk, 10 | VoidCallback? onCancel}) { 11 | GlobalThemeConfig theme = Get.find(); 12 | showDialog( 13 | context: context, 14 | barrierDismissible: false, 15 | builder: (BuildContext context) { 16 | return Dialog( 17 | shape: RoundedRectangleBorder( 18 | borderRadius: BorderRadius.circular(10.0), 19 | ), 20 | child: Container( 21 | padding: const EdgeInsets.all(20), 22 | child: Column( 23 | mainAxisSize: MainAxisSize.min, 24 | children: [ 25 | Text( 26 | '提示', 27 | style: TextStyle( 28 | fontSize: 18, 29 | fontWeight: FontWeight.bold, 30 | color: theme.primaryColor, 31 | ), 32 | ), 33 | const SizedBox(height: 20), 34 | Text(text, textAlign: TextAlign.center), 35 | const SizedBox(height: 20), 36 | Row( 37 | mainAxisAlignment: MainAxisAlignment.spaceEvenly, 38 | children: [ 39 | Expanded( 40 | child: CustomButton( 41 | text: '确定', 42 | onTap: () { 43 | onOk(); 44 | Navigator.of(context).pop(); 45 | }, 46 | width: 120, 47 | height: 34, 48 | ), 49 | ), 50 | const SizedBox(width: 10), 51 | Expanded( 52 | child: CustomButton( 53 | text: '取消', 54 | onTap: () { 55 | onCancel?.call(); 56 | Navigator.of(context).pop(); 57 | }, 58 | type: 'minor', 59 | height: 34, 60 | width: 120, 61 | ), 62 | ), 63 | ], 64 | ), 65 | ], 66 | ), 67 | ), 68 | ); 69 | }, 70 | ); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /lib/components/app_bar_title/index.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | 3 | class AppBarTitle extends StatelessWidget { 4 | final String title; 5 | 6 | const AppBarTitle(this.title, {super.key}); 7 | 8 | @override 9 | Widget build(BuildContext context) { 10 | return Text(title, style: const TextStyle(fontSize: 20)); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /lib/components/custom_animated_dots_text/index.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class CustomAnimatedDotsText extends StatefulWidget { 4 | final String text; 5 | final Duration duration; 6 | final TextStyle? textStyle; 7 | final BoxDecoration? decoration; 8 | final EdgeInsetsGeometry? padding; 9 | 10 | const CustomAnimatedDotsText({ 11 | super.key, 12 | required this.text, 13 | this.duration = const Duration(milliseconds: 1200), 14 | this.textStyle, 15 | this.decoration, 16 | this.padding, 17 | }); 18 | 19 | @override 20 | State createState() => _AnimatedDotsTextState(); 21 | } 22 | 23 | class _AnimatedDotsTextState extends State 24 | with SingleTickerProviderStateMixin { 25 | late AnimationController _controller; 26 | 27 | @override 28 | void initState() { 29 | super.initState(); 30 | _controller = AnimationController( 31 | vsync: this, 32 | duration: widget.duration, 33 | )..repeat(); 34 | } 35 | 36 | @override 37 | void dispose() { 38 | _controller.dispose(); 39 | super.dispose(); 40 | } 41 | 42 | @override 43 | Widget build(BuildContext context) { 44 | return Container( 45 | padding: widget.padding ?? const EdgeInsets.all(4), 46 | decoration: widget.decoration ?? 47 | BoxDecoration( 48 | color: Colors.black.withOpacity(0.2), 49 | borderRadius: BorderRadius.circular(5), 50 | ), 51 | child: AnimatedBuilder( 52 | animation: _controller, 53 | builder: (context, child) { 54 | int dots = (_controller.value * 3).floor() + 1; 55 | return Text( 56 | '${widget.text}${'.' * (dots % 4)}', 57 | style: widget.textStyle ?? 58 | const TextStyle(fontSize: 14, color: Colors.white), 59 | ); 60 | }, 61 | ), 62 | ); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /lib/components/custom_badge/index.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:linyu_mobile/utils/getx_config/config.dart'; 3 | 4 | class CustomBadge extends StatelessThemeWidget { 5 | final String text; 6 | final String type; 7 | 8 | const CustomBadge({ 9 | super.key, 10 | required this.text, 11 | this.type = 'primary', 12 | }); 13 | 14 | Color _getColor(String type) { 15 | switch (type) { 16 | case 'primary': 17 | return theme.primaryColor; 18 | case 'gold': 19 | return const Color(0xFFF3B659); 20 | default: 21 | return theme.primaryColor; 22 | } 23 | } 24 | 25 | @override 26 | Widget build(BuildContext context) { 27 | return Container( 28 | padding: const EdgeInsets.all(2), 29 | decoration: BoxDecoration( 30 | color: _getColor(type).withOpacity(0.1), 31 | borderRadius: const BorderRadius.all(Radius.circular(4)), 32 | border: Border.all(color: _getColor(type), width: 1), 33 | ), 34 | child: Center( 35 | child: Text( 36 | text, 37 | style: TextStyle(fontSize: 10, color: _getColor(type)), 38 | ), 39 | ), 40 | ); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /lib/components/custom_button/index.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:linyu_mobile/components/custom_material_button/index.dart'; 3 | import 'package:linyu_mobile/utils/getx_config/config.dart'; 4 | 5 | class CustomButton extends StatelessThemeWidget { 6 | final String text; 7 | final VoidCallback onTap; 8 | final String type; 9 | final double? width; 10 | final double? height; 11 | final double? textSize; 12 | final EdgeInsetsGeometry? padding; 13 | final double? borderRadius; 14 | 15 | const CustomButton( 16 | {super.key, 17 | required this.text, 18 | this.width = 200, 19 | this.height = 40, 20 | this.textSize = 16, 21 | this.type = 'primary', 22 | this.borderRadius = 10, 23 | this.padding = const EdgeInsets.all(10), 24 | required this.onTap}); 25 | 26 | Color _getColor(String type) { 27 | switch (type) { 28 | case 'primary': 29 | return theme.primaryColor; 30 | case 'minor': 31 | return const Color(0xFFEDF2F9); 32 | default: 33 | return theme.primaryColor; 34 | } 35 | } 36 | 37 | TextStyle _getTextStyle(String type) { 38 | switch (type) { 39 | case 'primary': 40 | return TextStyle(color: Colors.white, fontSize: textSize); 41 | case 'minor': 42 | return TextStyle(color: const Color(0xFF1F1F1F), fontSize: textSize); 43 | default: 44 | return TextStyle(color: Colors.white, fontSize: textSize); 45 | } 46 | } 47 | 48 | BoxDecoration _getBoxDecoration(String type) { 49 | switch (type) { 50 | case 'gradient': 51 | return BoxDecoration( 52 | gradient: LinearGradient( 53 | colors: [theme.primaryColor, theme.boldColor], 54 | begin: Alignment.topLeft, 55 | end: Alignment.bottomRight, 56 | ), 57 | borderRadius: BorderRadius.circular(borderRadius!), 58 | ); 59 | default: 60 | return BoxDecoration( 61 | color: _getColor(type), 62 | borderRadius: BorderRadius.circular(borderRadius!), 63 | ); 64 | } 65 | } 66 | 67 | @override 68 | Widget build(BuildContext context) { 69 | return CustomMaterialButton( 70 | onTap: onTap, 71 | color: type == 'gradient' ? null : _getColor(type), 72 | child: Container( 73 | width: width, 74 | height: height, 75 | decoration: _getBoxDecoration(type), 76 | child: Center( 77 | child: Text( 78 | text, 79 | style: _getTextStyle(type), 80 | ), 81 | ), 82 | ), 83 | ); 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /lib/components/custom_flutter_toast/index.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:fluttertoast/fluttertoast.dart'; 3 | import 'package:get/get_instance/src/get_instance.dart'; 4 | import 'package:linyu_mobile/utils/getx_config/GlobalThemeConfig.dart'; 5 | 6 | class CustomFlutterToast { 7 | static void showSuccessToast(String msg) { 8 | GlobalThemeConfig theme = GetInstance().find(); 9 | Fluttertoast.showToast( 10 | msg: msg, 11 | toastLength: Toast.LENGTH_SHORT, 12 | gravity: ToastGravity.TOP, 13 | timeInSecForIosWeb: 1, 14 | backgroundColor: theme.primaryColor, 15 | textColor: Colors.white, 16 | fontSize: 16.0); 17 | } 18 | 19 | static void showErrorToast(String msg) { 20 | Fluttertoast.showToast( 21 | msg: msg, 22 | toastLength: Toast.LENGTH_SHORT, 23 | gravity: ToastGravity.TOP, 24 | timeInSecForIosWeb: 1, 25 | backgroundColor: Colors.red, 26 | textColor: Colors.white, 27 | fontSize: 16.0); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /lib/components/custom_gradient_line/index.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class CustomGradientLine extends StatelessWidget { 4 | final double width; 5 | final double height; 6 | final Gradient gradient; 7 | 8 | const CustomGradientLine({ 9 | super.key, 10 | required this.width, 11 | required this.height, 12 | required this.gradient, 13 | }); 14 | 15 | @override 16 | Widget build(BuildContext context) { 17 | return CustomPaint( 18 | size: Size(width, height), 19 | painter: _GradientLinePainter(gradient: gradient), 20 | ); 21 | } 22 | } 23 | 24 | class _GradientLinePainter extends CustomPainter { 25 | final Gradient gradient; 26 | 27 | _GradientLinePainter({required this.gradient}); 28 | 29 | @override 30 | void paint(Canvas canvas, Size size) { 31 | final paint = Paint() 32 | ..shader = 33 | gradient.createShader(Rect.fromLTWH(0, 0, size.width, size.height)) 34 | ..style = PaintingStyle.stroke 35 | ..strokeWidth = size.height; 36 | canvas.drawLine( 37 | Offset(0, size.height / 2), 38 | Offset(size.width, size.height / 2), 39 | paint, 40 | ); 41 | } 42 | 43 | @override 44 | bool shouldRepaint(covariant CustomPainter oldDelegate) => false; 45 | } 46 | -------------------------------------------------------------------------------- /lib/components/custom_icon_button/index.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:linyu_mobile/components/custom_material_button/index.dart'; 3 | 4 | class CustomIconButton extends StatelessWidget { 5 | final double width; 6 | final double height; 7 | final Color? color; 8 | final IconData icon; 9 | final double iconSize; 10 | final Color? iconColor; 11 | final double? radius; 12 | final Function() onTap; 13 | final String? text; 14 | 15 | const CustomIconButton({ 16 | super.key, 17 | this.width = 40, 18 | this.height = 40, 19 | this.iconSize = 36, 20 | this.color, 21 | required this.onTap, 22 | this.iconColor, 23 | required this.icon, 24 | this.text, 25 | this.radius, 26 | }); 27 | 28 | @override 29 | Widget build(BuildContext context) { 30 | return Column( 31 | crossAxisAlignment: CrossAxisAlignment.center, 32 | mainAxisAlignment: MainAxisAlignment.center, 33 | children: [ 34 | CustomMaterialButton( 35 | color: color ?? const Color(0xFFE3ECFF), 36 | borderRadius: radius ?? width / 2, 37 | onTap: onTap, 38 | child: Container( 39 | width: width, 40 | height: height, 41 | decoration: BoxDecoration( 42 | borderRadius: BorderRadius.circular(15), 43 | ), 44 | child: Icon( 45 | icon, 46 | size: iconSize, 47 | color: iconColor ?? Colors.black38, 48 | ), 49 | ), 50 | ), 51 | if (text != null) ...[ 52 | const SizedBox(height: 4), 53 | SizedBox( 54 | width: width + 10, 55 | child: Center( 56 | child: Text( 57 | text!, 58 | style: const TextStyle( 59 | fontSize: 12, 60 | overflow: TextOverflow.ellipsis, 61 | ), 62 | ), 63 | ), 64 | ), 65 | ], 66 | ], 67 | ); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /lib/components/custom_image/index.dart: -------------------------------------------------------------------------------- 1 | import 'package:cached_network_image/cached_network_image.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:get/get.dart'; 4 | 5 | class CustomImage extends StatelessWidget { 6 | final String url; 7 | 8 | const CustomImage({super.key, required this.url}); 9 | 10 | void onOpenImage() { 11 | Get.toNamed('/image_viewer_update', arguments: { 12 | 'imageUrl': url, 13 | 'isUpdate': false, 14 | }); 15 | } 16 | 17 | @override 18 | Widget build(BuildContext context) { 19 | return GestureDetector( 20 | onTap: onOpenImage, 21 | child: ClipRRect( 22 | borderRadius: const BorderRadius.all(Radius.circular(5)), 23 | child: CachedNetworkImage( 24 | width: double.infinity, 25 | imageUrl: url ?? '', 26 | placeholder: (context, url) => Container( 27 | color: Colors.grey[300], 28 | child: const Center( 29 | child: CircularProgressIndicator( 30 | color: Color(0xffffffff), 31 | strokeWidth: 2, 32 | ), 33 | ), 34 | ), 35 | errorWidget: (context, url, error) => 36 | Image.asset('assets/images/empty-bg.png'), 37 | ), 38 | ), 39 | ); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /lib/components/custom_image_group/index.dart: -------------------------------------------------------------------------------- 1 | import 'package:cached_network_image/cached_network_image.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:get/get.dart'; 4 | import 'package:linyu_mobile/api/user_api.dart'; 5 | import 'package:linyu_mobile/components/custom_material_button/index.dart'; 6 | 7 | final _userApi = UserApi(); 8 | 9 | class CustomImageGroup extends StatelessWidget { 10 | final List imagesList; 11 | final String userId; 12 | List imageUrls = []; 13 | 14 | CustomImageGroup({ 15 | super.key, 16 | required this.imagesList, 17 | required this.userId, 18 | }) : imageUrls = List.filled(imagesList.length, ''); 19 | 20 | void _handlerOpenImageViewer(index) { 21 | Get.toNamed('/image_viewer', 22 | arguments: {'imageUrls': imageUrls, 'currentIndex': index}); 23 | } 24 | 25 | Future onGetImg(String fileName, String userId, int index) async { 26 | dynamic res = await _userApi.getImg(fileName, userId); 27 | if (res['code'] == 0) { 28 | imageUrls[index] = res['data']; 29 | return res['data']; 30 | } 31 | return ''; 32 | } 33 | 34 | Widget _buildTalkImage(String imageStr, String userId, int index) { 35 | return CustomMaterialButton( 36 | onTap: () => _handlerOpenImageViewer(index), 37 | child: Container( 38 | padding: const EdgeInsets.all(2.0), 39 | child: FutureBuilder( 40 | future: onGetImg(imageStr, userId, index), 41 | builder: (context, snapshot) { 42 | if (snapshot.hasData) { 43 | return CachedNetworkImage( 44 | imageUrl: snapshot.data ?? '', 45 | fit: BoxFit.cover, 46 | placeholder: (context, url) => Container( 47 | color: Colors.grey[300], 48 | child: const Center( 49 | child: CircularProgressIndicator( 50 | color: Color(0xffffffff), 51 | strokeWidth: 2, 52 | ), 53 | ), 54 | ), 55 | errorWidget: (context, url, error) => 56 | Image.asset('assets/images/empty-bg.png'), 57 | ); 58 | } else { 59 | return Container( 60 | color: Colors.grey[300], 61 | child: const Center( 62 | child: CircularProgressIndicator( 63 | color: Color(0xffffffff), 64 | strokeWidth: 2, 65 | ), 66 | ), 67 | ); 68 | } 69 | }, 70 | ), 71 | ), 72 | ); 73 | } 74 | 75 | Widget _buildImageGrid(List imageUrls, String userId) { 76 | return GridView.builder( 77 | shrinkWrap: true, 78 | physics: const NeverScrollableScrollPhysics(), 79 | gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( 80 | crossAxisCount: 3, 81 | crossAxisSpacing: 0, 82 | mainAxisSpacing: 0, 83 | childAspectRatio: 1.0, 84 | ), 85 | itemCount: imageUrls.length, 86 | itemBuilder: (context, index) { 87 | return _buildTalkImage(imageUrls[index], userId, index); 88 | }, 89 | ); 90 | } 91 | 92 | @override 93 | Widget build(BuildContext context) { 94 | return _buildImageGrid(imagesList, userId); 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /lib/components/custom_label_value/index.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class CustomLabelValue extends StatelessWidget { 4 | final String label; 5 | final String value; 6 | final double? width; 7 | 8 | const CustomLabelValue({ 9 | super.key, 10 | required this.label, 11 | required this.value, 12 | this.width = 60, 13 | }); 14 | 15 | @override 16 | Widget build(BuildContext context) { 17 | return Container( 18 | padding: const EdgeInsets.all(10), 19 | decoration: BoxDecoration( 20 | color: Colors.white, 21 | borderRadius: BorderRadius.circular(8), 22 | border: Border( 23 | bottom: BorderSide( 24 | color: Colors.grey[200]!, 25 | width: 0.5, 26 | ), 27 | ), // One pixel width] 28 | ), 29 | child: Row( 30 | crossAxisAlignment: CrossAxisAlignment.start, 31 | mainAxisSize: MainAxisSize.min, 32 | mainAxisAlignment: MainAxisAlignment.center, 33 | children: [ 34 | SizedBox( 35 | width: width, 36 | child: Text( 37 | label, 38 | style: const TextStyle( 39 | fontSize: 14, 40 | color: Colors.black54, 41 | ), 42 | ), 43 | ), 44 | Expanded( 45 | child: Text( 46 | value, 47 | style: const TextStyle( 48 | fontSize: 14, 49 | color: Colors.black87, 50 | ), 51 | maxLines: 5, 52 | overflow: TextOverflow.ellipsis, 53 | ), 54 | ), 55 | ], 56 | ), 57 | ); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /lib/components/custom_label_value_button/index.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:linyu_mobile/components/custom_material_button/index.dart'; 3 | 4 | class CustomLabelValueButton extends StatelessWidget { 5 | final String? label; 6 | final String? value; 7 | final double? width; 8 | final VoidCallback onTap; 9 | final int? maxLines; 10 | final Widget? child; 11 | final String hint; 12 | final Color? color; 13 | final bool compact; 14 | final bool? iconShowCenter; 15 | final GestureLongPressCallback? onLongPress; 16 | final GestureTapCallback? onDoubleTap; 17 | 18 | const CustomLabelValueButton({ 19 | super.key, 20 | @Deprecated('Use child instead') this.label, 21 | required this.onTap, 22 | this.value, 23 | this.child, 24 | this.maxLines = 5, 25 | this.hint = '', 26 | this.width = 60, 27 | this.color, 28 | this.compact = true, 29 | this.iconShowCenter = false, 30 | this.onLongPress, 31 | this.onDoubleTap, 32 | }); 33 | 34 | Widget _getContent() { 35 | if (null == child && (value == null || value!.trim().isEmpty)) { 36 | return Text( 37 | hint, 38 | style: const TextStyle( 39 | fontSize: 14, 40 | color: Colors.black38, 41 | ), 42 | maxLines: maxLines, 43 | overflow: TextOverflow.ellipsis, 44 | ); 45 | } 46 | return child ?? 47 | Text( 48 | value!, 49 | style: const TextStyle( 50 | fontSize: 14, 51 | color: Colors.black87, 52 | ), 53 | maxLines: maxLines, 54 | overflow: TextOverflow.ellipsis, 55 | ); 56 | } 57 | 58 | @override 59 | Widget build(BuildContext context) { 60 | return CustomMaterialButton( 61 | color: color, 62 | onTap: onTap, 63 | onLongPress: onLongPress, 64 | onDoubleTap: onDoubleTap, 65 | child: Container( 66 | padding: const EdgeInsets.all(10), 67 | decoration: BoxDecoration( 68 | borderRadius: BorderRadius.circular(8), 69 | ), 70 | child: Column( 71 | mainAxisAlignment: MainAxisAlignment.start, 72 | crossAxisAlignment: CrossAxisAlignment.start, 73 | children: [ 74 | Row( 75 | crossAxisAlignment: iconShowCenter! 76 | ? CrossAxisAlignment.center 77 | : CrossAxisAlignment.start, 78 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 79 | children: [ 80 | if (label != null) 81 | SizedBox( 82 | width: width, 83 | child: Text( 84 | label!, 85 | style: const TextStyle( 86 | fontSize: 14, 87 | color: Colors.black54, 88 | ), 89 | ), 90 | ), 91 | if (compact) 92 | Expanded( 93 | child: _getContent(), 94 | ), 95 | Icon( 96 | const IconData(0xe61f, fontFamily: 'IconFont'), 97 | size: 16, 98 | color: Colors.grey[700], 99 | ), 100 | ], 101 | ), 102 | if (!compact) _getContent(), 103 | ], 104 | )), 105 | ); 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /lib/components/custom_least_button/index.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:linyu_mobile/components/custom_material_button/index.dart'; 3 | 4 | class CustomLeastButton extends StatelessWidget { 5 | final String text; 6 | final VoidCallback onTap; 7 | final Color? textColor; 8 | 9 | const CustomLeastButton({ 10 | super.key, 11 | required this.text, 12 | required this.onTap, 13 | this.textColor, 14 | }); 15 | 16 | @override 17 | Widget build(BuildContext context) { 18 | return CustomMaterialButton( 19 | onTap: onTap, 20 | child: Container( 21 | height: 50, 22 | padding: const EdgeInsets.symmetric(horizontal: 20), 23 | decoration: const BoxDecoration( 24 | color: Colors.transparent, 25 | ), 26 | child: Row( 27 | mainAxisAlignment: MainAxisAlignment.center, 28 | children: [ 29 | Text( 30 | text, 31 | style: TextStyle( 32 | fontSize: 14, 33 | color: textColor ?? Colors.black, 34 | ), 35 | ), 36 | ], 37 | ), 38 | ), 39 | ); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /lib/components/custom_material_button/index.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class CustomMaterialButton extends StatelessWidget { 4 | final Widget child; 5 | final VoidCallback onTap; 6 | final double borderRadius; 7 | final Color? color; 8 | final GestureLongPressCallback? onLongPress; 9 | final GestureTapCallback? onDoubleTap; 10 | 11 | const CustomMaterialButton( 12 | {required this.child, 13 | required this.onTap, 14 | this.color, 15 | this.onLongPress, 16 | this.borderRadius = 10, 17 | this.onDoubleTap, 18 | super.key}); 19 | 20 | @override 21 | Widget build(BuildContext context) { 22 | return Material( 23 | borderRadius: BorderRadius.circular(borderRadius), 24 | color: color ?? Colors.white, 25 | child: InkWell( 26 | borderRadius: BorderRadius.circular(borderRadius), 27 | onTap: onTap, 28 | onLongPress: onLongPress, 29 | onDoubleTap: onDoubleTap, 30 | child: child, 31 | ), 32 | ); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /lib/components/custom_portrait/index.dart: -------------------------------------------------------------------------------- 1 | import 'package:cached_network_image/cached_network_image.dart'; 2 | import 'package:flutter/material.dart'; 3 | 4 | class CustomPortrait extends StatelessWidget { 5 | final double size; 6 | final String url; 7 | final double radius; 8 | final VoidCallback? onTap, onDoubleTap, onLongPress; 9 | final bool? isGreyColor; 10 | 11 | const CustomPortrait({ 12 | super.key, 13 | this.size = 50, 14 | required this.url, 15 | this.radius = 25, 16 | this.onTap, 17 | this.isGreyColor = false, 18 | this.onDoubleTap, 19 | this.onLongPress, 20 | }); 21 | 22 | @override 23 | Widget build(BuildContext context) { 24 | return GestureDetector( 25 | onTap: onTap, 26 | onDoubleTap: onDoubleTap, 27 | onLongPress: onLongPress, 28 | child: Stack( 29 | children: [ 30 | ClipRRect( 31 | borderRadius: BorderRadius.circular(radius), 32 | child: CachedNetworkImage( 33 | imageUrl: url, 34 | width: size, 35 | height: size, 36 | fit: BoxFit.cover, 37 | placeholder: (context, url) => Container( 38 | width: size, 39 | height: size, 40 | color: Colors.grey[300], 41 | child: const Center( 42 | child: CircularProgressIndicator( 43 | color: Color(0xffffffff), 44 | strokeWidth: 2, 45 | ), 46 | ), 47 | ), 48 | errorWidget: (context, url, error) => Container( 49 | width: size, 50 | height: size, 51 | color: Colors.grey[300], 52 | child: Image.asset('assets/images/default-portrait.jpeg'), 53 | ), 54 | ), 55 | ), 56 | Opacity( 57 | opacity: !isGreyColor! ? 0 : 0.5, 58 | child: ClipRRect( 59 | borderRadius: BorderRadius.circular(radius), 60 | child: Container( 61 | width: size, 62 | height: size, 63 | color: Colors.grey, 64 | ), 65 | ), 66 | ), 67 | ], 68 | ), 69 | ); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /lib/components/custom_search_box/index.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:linyu_mobile/utils/getx_config/config.dart'; 3 | 4 | class CustomSearchBox extends StatelessThemeWidget { 5 | final bool isCentered; 6 | final Color backgroundColor; 7 | final double borderRadius; 8 | final ValueChanged onChanged; 9 | final double? height; 10 | final String? hintText; 11 | final Widget? prefix; 12 | final TextEditingController? textEditingController; 13 | final FocusNode? focusNode; 14 | 15 | const CustomSearchBox({ 16 | super.key, 17 | this.isCentered = false, 18 | this.backgroundColor = const Color(0xFFE3ECFF), 19 | this.borderRadius = 10.0, 20 | required this.onChanged, 21 | this.height = 40, 22 | this.hintText = "搜索", 23 | this.prefix, 24 | this.textEditingController, 25 | this.focusNode, 26 | }); 27 | 28 | @override 29 | Widget build(BuildContext context) { 30 | Color iconColor = theme.primaryColor; 31 | return Container( 32 | alignment: isCentered ? Alignment.center : Alignment.centerLeft, 33 | height: height, 34 | padding: const EdgeInsets.symmetric(horizontal: 10.0), 35 | decoration: BoxDecoration( 36 | color: theme.searchBarColor, 37 | borderRadius: BorderRadius.circular(borderRadius), 38 | ), 39 | child: Row( 40 | mainAxisAlignment: 41 | isCentered ? MainAxisAlignment.center : MainAxisAlignment.start, 42 | crossAxisAlignment: CrossAxisAlignment.center, 43 | children: [ 44 | prefix ?? 45 | Icon(const IconData(0xe669, fontFamily: 'IconFont'), 46 | size: 20, color: iconColor), 47 | const SizedBox(width: 8), 48 | Expanded( 49 | child: TextField( 50 | focusNode: focusNode, 51 | controller: textEditingController, 52 | onChanged: onChanged, 53 | textAlign: isCentered ? TextAlign.center : TextAlign.start, 54 | style: TextStyle(color: iconColor, fontSize: 16), 55 | decoration: InputDecoration( 56 | contentPadding: const EdgeInsets.all(0), 57 | hintText: hintText, 58 | hintStyle: TextStyle( 59 | color: iconColor, 60 | fontSize: 16, 61 | fontWeight: FontWeight.w700), 62 | border: const OutlineInputBorder( 63 | borderSide: BorderSide(color: Colors.transparent, width: 1), 64 | ), 65 | enabledBorder: const OutlineInputBorder( 66 | borderSide: BorderSide(color: Colors.transparent, width: 1), 67 | ), 68 | focusedBorder: const OutlineInputBorder( 69 | borderSide: BorderSide(color: Colors.transparent, width: 1), 70 | ), 71 | isDense: true, 72 | ), 73 | ), 74 | ), 75 | ], 76 | ), 77 | ); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /lib/components/custom_shadow_text/index.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | import 'package:linyu_mobile/utils/getx_config/config.dart'; 3 | 4 | class CustomShadowText extends StatelessThemeWidget { 5 | final String text; 6 | final double fontSize; 7 | final FontWeight fontWeight; 8 | final double shadowTop; 9 | 10 | const CustomShadowText( 11 | {super.key, 12 | required this.text, 13 | this.fontSize = 16, 14 | this.shadowTop = 13, 15 | this.fontWeight = FontWeight.bold}); 16 | 17 | @override 18 | Widget build(BuildContext context) { 19 | return Stack( 20 | alignment: Alignment.center, 21 | clipBehavior: Clip.none, 22 | children: [ 23 | Positioned( 24 | top: shadowTop, 25 | child: Padding( 26 | padding: const EdgeInsets.symmetric(horizontal: 0, vertical: 0), 27 | child: Container( 28 | padding: const EdgeInsets.symmetric(horizontal: 5), 29 | height: 15, 30 | decoration: BoxDecoration( 31 | gradient: LinearGradient( 32 | colors: [ 33 | theme.primaryColor.withOpacity(0.1), 34 | theme.primaryColor, 35 | ], 36 | begin: Alignment.centerLeft, 37 | end: Alignment.centerRight, 38 | ), 39 | borderRadius: BorderRadius.circular(10), // 圆角 40 | ), 41 | child: Opacity( 42 | opacity: 0, 43 | child: Text( 44 | text, 45 | style: TextStyle(fontSize: fontSize), 46 | ), 47 | ), 48 | ), 49 | ), 50 | ), 51 | Text( 52 | text, 53 | style: TextStyle(fontSize: fontSize, fontWeight: fontWeight), 54 | ), 55 | ], 56 | ); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /lib/components/custom_sound_icon/index.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class CustomSoundIcon extends StatefulWidget { 4 | final bool isStart; 5 | final Color barColor; 6 | final double width; 7 | final double height; 8 | 9 | const CustomSoundIcon({ 10 | super.key, 11 | this.isStart = false, 12 | this.barColor = Colors.black, 13 | this.width = 26, 14 | this.height = 16, 15 | }); 16 | 17 | @override 18 | State createState() => _SoundIconState(); 19 | } 20 | 21 | class _SoundIconState extends State 22 | with TickerProviderStateMixin { 23 | late List _controllers; 24 | late List> _animations; 25 | 26 | @override 27 | void initState() { 28 | super.initState(); 29 | _controllers = List.generate(5, (index) { 30 | return AnimationController( 31 | duration: const Duration(milliseconds: 600), 32 | vsync: this, 33 | ); 34 | }); 35 | 36 | _animations = _controllers.map((controller) { 37 | return Tween(begin: 1.0, end: 1.5).animate( 38 | CurvedAnimation( 39 | parent: controller, 40 | curve: Curves.easeInOut, 41 | ), 42 | ); 43 | }).toList(); 44 | 45 | if (widget.isStart) { 46 | _startAnimation(); 47 | } 48 | } 49 | 50 | @override 51 | void didUpdateWidget(CustomSoundIcon oldWidget) { 52 | super.didUpdateWidget(oldWidget); 53 | if (widget.isStart != oldWidget.isStart) { 54 | if (widget.isStart) { 55 | _startAnimation(); 56 | } else { 57 | _stopAnimation(); 58 | } 59 | } 60 | } 61 | 62 | void _startAnimation() { 63 | for (var i = 0; i < _controllers.length; i++) { 64 | Future.delayed( 65 | Duration( 66 | milliseconds: i == 0 || i == 4 67 | ? 0 68 | : i == 1 || i == 3 69 | ? 250 70 | : 500), () { 71 | _controllers[i].repeat(reverse: true); 72 | }); 73 | } 74 | } 75 | 76 | void _stopAnimation() { 77 | for (var controller in _controllers) { 78 | controller.stop(); 79 | controller.reset(); 80 | } 81 | } 82 | 83 | @override 84 | void dispose() { 85 | for (var controller in _controllers) { 86 | controller.dispose(); 87 | } 88 | super.dispose(); 89 | } 90 | 91 | @override 92 | Widget build(BuildContext context) { 93 | return SizedBox( 94 | width: widget.width, 95 | height: widget.height, 96 | child: Row( 97 | mainAxisAlignment: MainAxisAlignment.spaceAround, 98 | children: List.generate(5, (index) { 99 | return AnimatedBuilder( 100 | animation: _animations[index], 101 | builder: (context, child) { 102 | return Container( 103 | width: 2, 104 | height: _getBarHeight(index) * _animations[index].value, 105 | color: widget.barColor, 106 | ); 107 | }, 108 | ); 109 | }), 110 | ), 111 | ); 112 | } 113 | 114 | double _getBarHeight(int index) { 115 | switch (index) { 116 | case 0: 117 | case 4: 118 | return 4.0; 119 | case 1: 120 | case 3: 121 | return 10.0; 122 | case 2: 123 | return 16.0; 124 | default: 125 | return 4.0; 126 | } 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /lib/components/custom_text_button/index.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:linyu_mobile/utils/getx_config/config.dart'; 3 | 4 | class CustomTextButton extends StatelessThemeWidget { 5 | final String value; 6 | final GestureTapCallback onTap; 7 | final Color? textColor; 8 | final double fontSize; 9 | final EdgeInsetsGeometry? padding; 10 | 11 | const CustomTextButton( 12 | this.value, { 13 | super.key, 14 | required this.onTap, 15 | this.fontSize = 12, 16 | this.padding, 17 | this.textColor, 18 | }); 19 | 20 | @override 21 | Widget build(BuildContext context) { 22 | return InkWell( 23 | borderRadius: const BorderRadius.all(Radius.circular(15)), 24 | onTap: onTap, 25 | child: Container( 26 | padding: padding, 27 | child: Row( 28 | mainAxisSize: MainAxisSize.max, 29 | crossAxisAlignment: CrossAxisAlignment.center, 30 | mainAxisAlignment: MainAxisAlignment.center, 31 | children: [ 32 | Text( 33 | value, 34 | style: TextStyle( 35 | fontSize: fontSize, 36 | color: textColor ?? theme.primaryColor, 37 | ), 38 | ), 39 | ], 40 | ), 41 | ), 42 | ); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /lib/components/custom_tip/index.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class CustomTip extends StatelessWidget { 4 | final int count; 5 | final double right; 6 | final double top; 7 | 8 | const CustomTip(this.count, {super.key, this.right = -10, this.top = -5}); 9 | 10 | @override 11 | Widget build(BuildContext context) { 12 | return Positioned( 13 | right: right, 14 | top: top, 15 | child: Container( 16 | height: 16, 17 | padding: const EdgeInsets.symmetric(horizontal: 4), 18 | decoration: BoxDecoration( 19 | color: Colors.red, 20 | borderRadius: BorderRadius.circular(10), 21 | ), 22 | constraints: const BoxConstraints( 23 | minWidth: 16, 24 | ), 25 | child: Text( 26 | count > 99 ? '99+' : count.toString(), 27 | style: const TextStyle( 28 | color: Colors.white, 29 | fontSize: 10, 30 | ), 31 | textAlign: TextAlign.center, 32 | ), 33 | ), 34 | ); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /lib/components/custom_update_portrait/index.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:linyu_mobile/components/custom_portrait/index.dart'; 3 | 4 | class CustomUpdatePortrait extends StatelessWidget { 5 | final String url; 6 | final double size; 7 | final double radius; 8 | final GestureTapCallback onTap; 9 | final bool isEdit; 10 | 11 | const CustomUpdatePortrait( 12 | {super.key, 13 | required this.url, 14 | this.size = 70, 15 | this.radius = 35, 16 | required this.onTap, 17 | this.isEdit = true}); 18 | 19 | @override 20 | Widget build(BuildContext context) { 21 | return Stack( 22 | alignment: Alignment.center, 23 | children: [ 24 | CustomPortrait( 25 | onTap: onTap, 26 | url: url, 27 | size: size, 28 | radius: radius, 29 | ), 30 | if (isEdit) 31 | GestureDetector( 32 | onTap: onTap, 33 | child: Container( 34 | width: size, 35 | height: size, 36 | decoration: BoxDecoration( 37 | color: Colors.black.withOpacity(0.3), 38 | borderRadius: BorderRadius.circular(radius), 39 | ), 40 | alignment: Alignment.center, 41 | child: const Icon( 42 | Icons.camera_alt, 43 | color: Colors.white, 44 | size: 20, 45 | ), 46 | ), 47 | ), 48 | ], 49 | ); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_localizations/flutter_localizations.dart'; 3 | import 'package:linyu_mobile/utils/getx_config/ControllerBinding.dart'; 4 | import 'package:linyu_mobile/utils/getx_config/config.dart'; 5 | import 'package:shared_preferences/shared_preferences.dart'; 6 | import 'package:get/get.dart' 7 | show Get, GetMaterialApp, Inst, SmartManagement, Transition; 8 | 9 | void main() async { 10 | WidgetsFlutterBinding.ensureInitialized(); 11 | // SharedPreferences prefs = await SharedPreferences.getInstance(); 12 | SharedPreferences prefs = await Get.putAsync( 13 | () async => await SharedPreferences.getInstance(), 14 | permanent: true); 15 | String? token = prefs.getString('x-token'); 16 | String? sex = prefs.getString('sex'); 17 | runApp(MyApp(initialRoute: token != null ? '/?sex=$sex' : '/login')); 18 | } 19 | 20 | class MyApp extends StatelessWidget { 21 | final String? initialRoute; 22 | final Widget? initialPage; 23 | 24 | const MyApp({super.key, this.initialPage, this.initialRoute}); 25 | 26 | @override 27 | Widget build(BuildContext context) { 28 | return GetMaterialApp( 29 | smartManagement: SmartManagement.keepFactory, 30 | title: '林语', 31 | //国际化 32 | localizationsDelegates: const [ 33 | GlobalMaterialLocalizations.delegate, 34 | GlobalCupertinoLocalizations.delegate, 35 | GlobalWidgetsLocalizations.delegate, 36 | ], 37 | supportedLocales: const [ 38 | Locale('zh', 'CH'), 39 | Locale('en', 'US'), 40 | ], 41 | locale: const Locale('zh'), 42 | //全局绑定Controller 43 | initialBinding: ControllerBinding(), 44 | enableLog: true, 45 | //路由配置 46 | getPages: pageRoute, 47 | //路由从右侧向左滑入(对GetX有效) 48 | defaultTransition: Transition.rightToLeft, 49 | initialRoute: initialRoute, 50 | //路由监听 51 | routingCallback: routingCallback, 52 | theme: ThemeData( 53 | colorScheme: ColorScheme.fromSeed( 54 | seedColor: const Color(0xFF4C9BFF), 55 | surface: const Color(0xFFFFFFFF), 56 | onSurface: const Color(0xFF1F1F1F), 57 | primary: const Color(0xFF4C9BFF), 58 | onPrimary: Colors.white, 59 | ), 60 | splashColor: const Color(0x80EAEAEA), 61 | highlightColor: const Color(0x80EAEAEA), 62 | useMaterial3: true, 63 | ), 64 | // home: initialPage, 65 | ); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /lib/pages/add_friend/friend_info/logic.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:get/get.dart'; 3 | import 'package:linyu_mobile/utils/getx_config/config.dart'; 4 | 5 | class SearchInfoLogic extends Logic { 6 | //搜索结果 7 | dynamic get _friendInfo => arguments['friend']; 8 | 9 | //好友头像 10 | String _friendPortrait = ''; 11 | 12 | String get friendPortrait => _friendPortrait; 13 | 14 | set friendPortrait(String value) { 15 | _friendPortrait = value; 16 | update([const Key('search_info')]); 17 | } 18 | 19 | //好友昵称 20 | String _friendName = ''; 21 | 22 | String get friendName => _friendName; 23 | 24 | set friendName(String value) { 25 | _friendName = value; 26 | update([const Key('search_info')]); 27 | } 28 | 29 | //好友账号 30 | String _friendAccount = ''; 31 | 32 | String get friendAccount => _friendAccount; 33 | 34 | set friendAccount(String value) { 35 | _friendAccount = value; 36 | update([const Key('friend_info')]); 37 | } 38 | 39 | //好友签名 40 | String _friendSignature = ''; 41 | 42 | String get friendSignature => _friendSignature; 43 | 44 | set friendSignature(String value) { 45 | _friendSignature = value; 46 | update([const Key('search_info')]); 47 | } 48 | 49 | //好友性别 50 | String _friendSex = ''; 51 | 52 | String get friendSex => _friendSex; 53 | 54 | set friendSex(String value) { 55 | _friendSex = value; 56 | update([const Key('friend_info')]); 57 | } 58 | 59 | //好友生日 60 | String _friendBirthday = ''; 61 | 62 | String get friendBirthday => _friendBirthday; 63 | 64 | set friendBirthday(String value) { 65 | _friendBirthday = value; 66 | update([const Key('search_info')]); 67 | } 68 | 69 | //邮箱 70 | String _friendEmail = ''; 71 | 72 | String get friendEmail => _friendEmail; 73 | 74 | set friendEmail(String value) { 75 | _friendEmail = value; 76 | update([const Key('search_info')]); 77 | } 78 | 79 | void initData() { 80 | friendPortrait = _friendInfo['portrait']; 81 | friendName = _friendInfo['name']; 82 | friendAccount = _friendInfo['account']; 83 | friendSignature = _friendInfo['signature'] ?? ''; 84 | friendSex = _friendInfo['sex']; 85 | friendBirthday = _friendInfo['birthday']; 86 | } 87 | 88 | //进入好友申请页面 89 | void goApplyFriend() => 90 | Get.toNamed('/friend_request', arguments: {'friendInfo': _friendInfo}); 91 | 92 | @override 93 | void onInit() { 94 | super.onInit(); 95 | initData(); 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /lib/pages/add_friend/friend_request/logic.dart: -------------------------------------------------------------------------------- 1 | // ignore_for_file: curly_braces_in_flow_control_structures 2 | 3 | import 'package:flutter/material.dart'; 4 | import 'package:get/get.dart'; 5 | import 'package:linyu_mobile/api/notify_api.dart'; 6 | import 'package:linyu_mobile/components/custom_flutter_toast/index.dart'; 7 | import 'package:linyu_mobile/utils/getx_config/config.dart'; 8 | 9 | class FriendRequestLogic extends Logic { 10 | final _notifyApi = new NotifyApi(); 11 | 12 | final TextEditingController applyFriendController = 13 | new TextEditingController(); 14 | 15 | dynamic get _friendInfo => arguments['friendInfo']; 16 | 17 | //好友头像 18 | String _friendPortrait = ''; 19 | 20 | String get friendPortrait => _friendPortrait; 21 | 22 | set friendPortrait(String value) { 23 | _friendPortrait = value; 24 | update([const Key('friend_request')]); 25 | } 26 | 27 | //好友昵称 28 | String _friendName = ''; 29 | 30 | String get friendName => _friendName; 31 | 32 | set friendName(String value) { 33 | _friendName = value; 34 | update([const Key('friend_request')]); 35 | } 36 | 37 | //申请信息文本长度 38 | int _applyFriendLength = 0; 39 | 40 | int get applyFriendLength => _applyFriendLength; 41 | 42 | set applyFriendLength(int value) { 43 | _applyFriendLength = value; 44 | update([const Key('friend_request')]); 45 | } 46 | 47 | void initData() { 48 | friendPortrait = _friendInfo['portrait']; 49 | friendName = _friendInfo['name']; 50 | } 51 | 52 | //个性签名输入长度 53 | void applyFriendTextChanged(String value) { 54 | applyFriendLength = value.length; 55 | if (applyFriendLength >= 100) applyFriendLength = 100; 56 | } 57 | 58 | //好友请求 59 | void applyFriend() async { 60 | final result = await _notifyApi.friendApply( 61 | _friendInfo['id'], 62 | applyFriendController.text, 63 | ); 64 | if (result['code'] == 0) { 65 | CustomFlutterToast.showSuccessToast("申请成功,等待对方验证~"); 66 | Future.delayed(const Duration(milliseconds: 2300), () => Get.back()); 67 | } else 68 | CustomFlutterToast.showErrorToast(result['msg']); 69 | } 70 | 71 | @override 72 | void onInit() { 73 | super.onInit(); 74 | initData(); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /lib/pages/add_friend/logic.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:get/get.dart'; 3 | import 'package:linyu_mobile/api/user_api.dart'; 4 | import 'package:linyu_mobile/utils/getx_config/config.dart'; 5 | 6 | class AddFriendLogic extends Logic { 7 | final _userApi = new UserApi(); 8 | 9 | late List searchList = []; 10 | 11 | void onSearchFriend(String friendInfo) { 12 | if (friendInfo.trim() == '') { 13 | searchList = []; 14 | searchList = []; 15 | update([const Key("add_friend")]); 16 | return; 17 | } 18 | _userApi.search(friendInfo).then((res) { 19 | if (res['code'] == 0) { 20 | searchList = res['data']; 21 | update([const Key("add_friend")]); 22 | } 23 | }); 24 | } 25 | 26 | // 进入好友详情页 27 | void toFriendDetail(dynamic friend) => 28 | Get.toNamed("/search_info", arguments: {"friend": friend}); 29 | 30 | // 申请加好友 31 | void goApplyFriend(dynamic friend) => 32 | Get.toNamed('/friend_request', arguments: {'friendInfo': friend}); 33 | 34 | @override 35 | void onClose() { 36 | super.onClose(); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /lib/pages/chat_frame/chat_content/call.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | 3 | import 'package:flutter/material.dart'; 4 | import 'package:linyu_mobile/utils/date.dart'; 5 | import 'package:linyu_mobile/utils/getx_config/config.dart'; 6 | 7 | class CallMessage extends StatelessThemeWidget { 8 | final dynamic value; 9 | final bool isRight; 10 | 11 | const CallMessage({ 12 | super.key, 13 | required this.value, 14 | required this.isRight, 15 | }); 16 | 17 | @override 18 | Widget build(BuildContext context) { 19 | final content = jsonDecode(value['msgContent']['content']); 20 | final type = content?['type']; 21 | final time = content?['time'] ?? 0; 22 | 23 | return Container( 24 | width: 150, 25 | height: 40, 26 | decoration: BoxDecoration( 27 | color: isRight ? theme.primaryColor : Colors.white, 28 | // borderRadius: BorderRadius.circular(5), 29 | borderRadius: isRight 30 | ? const BorderRadius.only( 31 | topLeft: Radius.circular(10), 32 | bottomLeft: Radius.circular(10), 33 | bottomRight: Radius.circular(10), 34 | ) 35 | : const BorderRadius.only( 36 | bottomLeft: Radius.circular(10), 37 | bottomRight: Radius.circular(10), 38 | topRight: Radius.circular(10), 39 | ), 40 | ), 41 | child: Row( 42 | crossAxisAlignment: CrossAxisAlignment.center, 43 | children: [ 44 | Padding( 45 | padding: const EdgeInsets.symmetric(horizontal: 4), 46 | child: Icon( 47 | type == 'audio' ? Icons.phone : Icons.videocam, 48 | size: 20, 49 | color: isRight ? Colors.white : Colors.black, 50 | ), 51 | ), 52 | Text( 53 | time > 0 ? "通话时长 ${DateUtil.formatTimingTime(time)}" : "通话未接通", 54 | style: TextStyle( 55 | color: isRight ? Colors.white : null, 56 | fontSize: 14, 57 | ), 58 | ), 59 | ], 60 | ), 61 | ); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /lib/pages/chat_frame/chat_content/image.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/foundation.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:get/get.dart'; 4 | import 'package:linyu_mobile/api/msg_api.dart'; 5 | import 'package:linyu_mobile/components/custom_image/index.dart'; 6 | import 'package:linyu_mobile/utils/getx_config/config.dart'; 7 | 8 | class ImageMessage extends StatelessThemeWidget { 9 | final _msgApi = MsgApi(); 10 | final dynamic value; 11 | final bool isRight; 12 | 13 | ImageMessage({ 14 | super.key, 15 | required this.value, 16 | required this.isRight, 17 | }); 18 | 19 | Future _onGetImg() async { 20 | try { 21 | final fromForwardMsgId = value['fromForwardMsgId']; 22 | final res = await _msgApi.getMedia(fromForwardMsgId ?? value['id']); 23 | if (res['code'] == 0) return res['data']; 24 | } catch (e) { 25 | // 处理错误,您可以记录日志或执行其他操作 26 | if (kDebugMode) print('获取图片时出错: $e'); 27 | } 28 | return ''; 29 | } 30 | 31 | @override 32 | Widget build(BuildContext context) => Container( 33 | height: MediaQuery.of(Get.context!).size.width * 0.4, 34 | width: MediaQuery.of(Get.context!).size.width * 0.4, 35 | decoration: BoxDecoration( 36 | color: Colors.black, 37 | borderRadius: isRight 38 | ? const BorderRadius.only( 39 | topLeft: Radius.circular(10), 40 | bottomLeft: Radius.circular(10), 41 | bottomRight: Radius.circular(10), 42 | ) 43 | : const BorderRadius.only( 44 | bottomLeft: Radius.circular(10), 45 | bottomRight: Radius.circular(10), 46 | topRight: Radius.circular(10), 47 | ), 48 | ), 49 | child: FutureBuilder( 50 | future: _onGetImg(), 51 | builder: (context, snapshot) => snapshot.hasData 52 | ? CustomImage(url: snapshot.data ?? '') 53 | : Container( 54 | width: MediaQuery.of(Get.context!).size.width * 0.4, 55 | color: isRight ? theme.primaryColor : Colors.white, 56 | height: MediaQuery.of(Get.context!).size.width * 0.4, 57 | alignment: Alignment.center, 58 | child: const SizedBox( 59 | width: 40, 60 | height: 40, 61 | child: CircularProgressIndicator( 62 | color: Color(0xffffffff), 63 | strokeWidth: 2, 64 | ), 65 | ), 66 | ), 67 | ), 68 | ); 69 | } 70 | -------------------------------------------------------------------------------- /lib/pages/chat_frame/chat_content/retraction.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class RetractionMessage extends StatelessWidget { 4 | final bool isRight; 5 | final String? userName; 6 | 7 | const RetractionMessage({ 8 | super.key, 9 | this.isRight = false, 10 | this.userName, 11 | }); 12 | 13 | @override 14 | Widget build(BuildContext context) => Container( 15 | margin: const EdgeInsets.only(bottom: 10), 16 | alignment: Alignment.center, 17 | height: 20, 18 | constraints: const BoxConstraints(minHeight: 20), 19 | child: Text( 20 | isRight 21 | ? "你撤回了一条消息" 22 | : userName != null 23 | ? "$userName 撤回了一条消息" 24 | : " 对方撤回了一条消息", 25 | style: const TextStyle( 26 | fontSize: 12, 27 | color: Colors.black54, 28 | ), 29 | ), 30 | ); 31 | } 32 | -------------------------------------------------------------------------------- /lib/pages/chat_frame/chat_content/system.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | 3 | import 'package:flutter/material.dart'; 4 | 5 | import 'package:linyu_mobile/utils/getx_config/config.dart'; 6 | 7 | class SystemMessage extends StatelessThemeWidget { 8 | final dynamic value; 9 | 10 | const SystemMessage({super.key, this.value}); 11 | 12 | @override 13 | Widget build(BuildContext context) { 14 | List systemMsgList = jsonDecode(value['content'] ?? '[]'); 15 | return Container( 16 | padding: const EdgeInsets.symmetric(vertical: 2, horizontal: 5), 17 | decoration: BoxDecoration( 18 | color: Colors.white, 19 | borderRadius: BorderRadius.circular(3.0), 20 | ), 21 | child: Row( 22 | crossAxisAlignment: CrossAxisAlignment.center, 23 | mainAxisSize: MainAxisSize.min, 24 | children: systemMsgList.map((msg) { 25 | final isEmphasize = msg['isEmphasize'] as bool? ?? false; 26 | final content = msg['content'] as String? ?? ''; 27 | return Padding( 28 | padding: const EdgeInsets.symmetric(vertical: 4.0), 29 | child: Text( 30 | content, 31 | style: TextStyle( 32 | fontSize: 12, 33 | color: isEmphasize ? theme.primaryColor : Colors.black, 34 | fontWeight: isEmphasize ? FontWeight.w600 : FontWeight.normal, 35 | ), 36 | ), 37 | ); 38 | }).toList(), 39 | ), 40 | ); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /lib/pages/chat_frame/chat_content/text.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:get/get.dart'; 3 | import 'package:linyu_mobile/utils/getx_config/config.dart'; 4 | 5 | class TextMessage extends StatelessThemeWidget { 6 | final dynamic value; 7 | final bool isRight; 8 | 9 | const TextMessage({ 10 | super.key, 11 | required this.value, 12 | required this.isRight, 13 | }); 14 | 15 | @override 16 | Widget build(BuildContext context) => Container( 17 | padding: const EdgeInsets.all(10), 18 | decoration: BoxDecoration( 19 | color: isRight ? theme.primaryColor : Colors.white, 20 | borderRadius: isRight 21 | ? const BorderRadius.only( 22 | topLeft: Radius.circular(10), 23 | bottomLeft: Radius.circular(10), 24 | bottomRight: Radius.circular(10), 25 | ) 26 | : const BorderRadius.only( 27 | bottomLeft: Radius.circular(10), 28 | bottomRight: Radius.circular(10), 29 | topRight: Radius.circular(10), 30 | ), 31 | ), 32 | constraints: BoxConstraints( 33 | maxWidth: MediaQuery.of(Get.context!).size.width * 0.7, 34 | ), 35 | child: Text( 36 | value['msgContent']['content'], 37 | style: TextStyle(color: isRight ? Colors.white : null, fontSize: 14), 38 | ), 39 | ); 40 | } 41 | -------------------------------------------------------------------------------- /lib/pages/chat_frame/chat_content/time.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class TimeContent extends StatelessWidget { 4 | late dynamic value; 5 | 6 | TimeContent({super.key, required this.value}); 7 | 8 | @override 9 | Widget build(BuildContext context) => Column( 10 | children: [ 11 | Container( 12 | padding: const EdgeInsets.symmetric(vertical: 2, horizontal: 5), 13 | decoration: BoxDecoration( 14 | color: Colors.white, borderRadius: BorderRadius.circular(3)), 15 | child: Text( 16 | value, 17 | style: const TextStyle(fontSize: 12, color: Colors.black54), 18 | ), 19 | ), 20 | const SizedBox(height: 10), 21 | ], 22 | ); 23 | } 24 | -------------------------------------------------------------------------------- /lib/pages/contacts/chat_group_information/chat_group_notice/add_chat_group_notice/index.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter/services.dart'; 3 | import 'package:get/get.dart'; 4 | import 'package:linyu_mobile/components/app_bar_title/index.dart'; 5 | import 'package:linyu_mobile/components/custom_text_button/index.dart'; 6 | import 'package:linyu_mobile/components/custom_text_field/index.dart'; 7 | import 'package:linyu_mobile/pages/contacts/chat_group_information/chat_group_notice/add_chat_group_notice/logic.dart'; 8 | import 'package:linyu_mobile/utils/getx_config/config.dart'; 9 | 10 | class AddChatGroupNoticePage extends CustomWidget { 11 | AddChatGroupNoticePage({super.key}); 12 | 13 | @override 14 | Widget buildWidget(BuildContext context) { 15 | return Scaffold( 16 | backgroundColor: const Color(0xFFF9FBFF), 17 | appBar: AppBar( 18 | backgroundColor: Colors.transparent, 19 | title: const AppBarTitle('编辑群公告'), 20 | centerTitle: true, 21 | elevation: 0, 22 | systemOverlayStyle: SystemUiOverlayStyle.dark, 23 | actions: [ 24 | CustomTextButton('完成', 25 | onTap: controller.onAddNotice, 26 | padding: 27 | const EdgeInsets.symmetric(horizontal: 20.0, vertical: 5.0), 28 | fontSize: 14), 29 | ]), 30 | body: Container( 31 | padding: const EdgeInsets.symmetric(horizontal: 16.0), 32 | child: Column( 33 | children: [ 34 | const SizedBox(height: 10), 35 | Obx( 36 | () => CustomTextField( 37 | // labelText: "群公告", 38 | controller: controller.noticeController, 39 | onChanged: (value) { 40 | controller.noticeLength.value = value.length; 41 | }, 42 | inputLimit: 100, 43 | hintText: "请输入群公告~", 44 | minLines: 8, 45 | maxLines: 8, 46 | hintTextColor: theme.primaryColor, 47 | suffix: Text('${controller.noticeLength.value}/100'), 48 | ), 49 | ), 50 | ], 51 | ), 52 | ), 53 | ); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /lib/pages/contacts/chat_group_information/chat_group_notice/add_chat_group_notice/logic.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:get/get.dart'; 3 | import 'package:linyu_mobile/api/chat_group_notice_api.dart'; 4 | import 'package:linyu_mobile/components/custom_flutter_toast/index.dart'; 5 | 6 | class AddChatGroupNoticeLogic extends GetxController { 7 | final _chatGroupNoticeApi = ChatGroupNoticeApi(); 8 | final TextEditingController noticeController = TextEditingController(); 9 | late String? chatGroupNoticeId; 10 | late String chatGroupId; 11 | late RxInt noticeLength = 0.obs; 12 | 13 | @override 14 | void onInit() { 15 | noticeController.text = Get.arguments['content'] ?? ''; 16 | chatGroupNoticeId = Get.arguments['chatGroupNoticeId']; 17 | chatGroupId = Get.arguments['chatGroupId']; 18 | noticeLength.value = noticeController.text.length; 19 | } 20 | 21 | void onAddNotice() async { 22 | if (noticeController.text == null || noticeController.text.trim().isEmpty) { 23 | CustomFlutterToast.showErrorToast('公告不能为空~'); 24 | return; 25 | } 26 | Map response = {'code': 1}; 27 | if (chatGroupNoticeId == null) { 28 | response = 29 | await _chatGroupNoticeApi.create(chatGroupId, noticeController.text); 30 | } else { 31 | response = await _chatGroupNoticeApi.update( 32 | chatGroupId, chatGroupNoticeId!, noticeController.text); 33 | } 34 | if (response['code'] == 0) { 35 | CustomFlutterToast.showSuccessToast('群公告设置成功~'); 36 | Get.back(result: true); 37 | } else { 38 | CustomFlutterToast.showErrorToast('群公告设置失败~'); 39 | } 40 | } 41 | 42 | @override 43 | void onClose() { 44 | noticeController.dispose(); 45 | super.onClose(); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /lib/pages/contacts/chat_group_information/chat_group_notice/logic.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | import 'package:get/get.dart'; 3 | import 'package:linyu_mobile/api/chat_group_notice_api.dart'; 4 | import 'package:linyu_mobile/components/CustomDialog/index.dart'; 5 | import 'package:linyu_mobile/components/custom_flutter_toast/index.dart'; 6 | 7 | class ChatGroupNoticeLogic extends GetxController { 8 | final _chatGroupNoticeApi = ChatGroupNoticeApi(); 9 | late String chatGroupId; 10 | late List chatGroupNoticeList = []; 11 | late bool isOwner = false; 12 | 13 | @override 14 | void onInit() { 15 | chatGroupId = Get.arguments['chatGroupId']; 16 | isOwner = Get.arguments['isOwner'] ?? false; 17 | onGetChatGroupNoticeList(); 18 | } 19 | 20 | void onGetChatGroupNoticeList() { 21 | _chatGroupNoticeApi.list(chatGroupId).then((res) { 22 | if (res['code'] == 0) { 23 | chatGroupNoticeList = res['data']; 24 | update([const Key('chat_group_notice')]); 25 | } 26 | }); 27 | } 28 | 29 | void onDelChatGroupNotice(context, String id) { 30 | CustomDialog.showTipDialog( 31 | context, 32 | text: '确定删除该条公告?', 33 | onOk: () { 34 | _chatGroupNoticeApi.delete(chatGroupId, id).then((res) { 35 | if (res['code'] == 0) { 36 | CustomFlutterToast.showSuccessToast('删除成功~'); 37 | onGetChatGroupNoticeList(); 38 | } 39 | }); 40 | }, 41 | onCancel: () {}, 42 | ); 43 | } 44 | 45 | void handlerEditNotice(String id, String content) async { 46 | var result = await Get.toNamed('/add_chat_group_notice', arguments: { 47 | 'chatGroupId': chatGroupId, 48 | 'chatGroupNoticeId': id, 49 | 'content': content, 50 | }); 51 | if (result != null && result) { 52 | onGetChatGroupNoticeList(); 53 | } 54 | } 55 | 56 | void handlerAddNotice() async { 57 | var result = await Get.toNamed('/add_chat_group_notice', arguments: { 58 | 'chatGroupId': chatGroupId, 59 | }); 60 | if (result != null && result) { 61 | onGetChatGroupNoticeList(); 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /lib/pages/contacts/chat_group_information/set_group_name/index.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter/services.dart'; 3 | import 'package:get/get.dart'; 4 | import 'package:linyu_mobile/components/app_bar_title/index.dart'; 5 | import 'package:linyu_mobile/components/custom_text_button/index.dart'; 6 | import 'package:linyu_mobile/components/custom_text_field/index.dart'; 7 | import 'package:linyu_mobile/pages/contacts/chat_group_information/set_group_name/logic.dart'; 8 | import 'package:linyu_mobile/utils/getx_config/config.dart'; 9 | 10 | class SetGroupNamePage extends CustomWidget { 11 | SetGroupNamePage({super.key}); 12 | 13 | @override 14 | Widget buildWidget(BuildContext context) { 15 | return Scaffold( 16 | backgroundColor: const Color(0xFFF9FBFF), 17 | appBar: AppBar( 18 | backgroundColor: Colors.transparent, 19 | title: const AppBarTitle('群名称'), 20 | centerTitle: true, 21 | elevation: 0, 22 | systemOverlayStyle: SystemUiOverlayStyle.dark, 23 | actions: [ 24 | CustomTextButton('完成', 25 | onTap: controller.onSetName, 26 | padding: 27 | const EdgeInsets.symmetric(horizontal: 20.0, vertical: 5.0), 28 | fontSize: 14), 29 | ]), 30 | body: Container( 31 | padding: const EdgeInsets.symmetric(horizontal: 16.0), 32 | child: Column( 33 | children: [ 34 | const SizedBox(height: 10), 35 | Obx( 36 | () => CustomTextField( 37 | labelText: "群名称", 38 | controller: controller.nameController, 39 | onChanged: (value) { 40 | controller.nameLength.value = value.length; 41 | }, 42 | inputLimit: 10, 43 | hintText: "请输入群名称~", 44 | suffix: Text('${controller.nameLength.value}/10'), 45 | ), 46 | ), 47 | ], 48 | ), 49 | ), 50 | ); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /lib/pages/contacts/chat_group_information/set_group_name/logic.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:get/get.dart'; 3 | import 'package:linyu_mobile/api/chat_group_api.dart'; 4 | import 'package:linyu_mobile/components/custom_flutter_toast/index.dart'; 5 | 6 | class SetGroupNameLogic extends GetxController { 7 | final _chatGroupApi = ChatGroupApi(); 8 | final TextEditingController nameController = TextEditingController(); 9 | late String chatGroupId; 10 | late RxInt nameLength = 0.obs; 11 | 12 | @override 13 | void onInit() { 14 | nameController.text = Get.arguments['name']; 15 | chatGroupId = Get.arguments['chatGroupId']; 16 | nameLength.value = nameController.text.length; 17 | } 18 | 19 | void onSetName() async { 20 | if (nameController.text == null || nameController.text.trim().isEmpty) { 21 | CustomFlutterToast.showErrorToast('名称不能为空~'); 22 | return; 23 | } 24 | final response = 25 | await _chatGroupApi.updateName(chatGroupId, nameController.text); 26 | if (response['code'] == 0) { 27 | CustomFlutterToast.showSuccessToast('更新群名称成功~'); 28 | Get.back(result: nameController.text); 29 | } else { 30 | CustomFlutterToast.showErrorToast(response['msg']); 31 | } 32 | } 33 | 34 | @override 35 | void onClose() { 36 | nameController.dispose(); 37 | super.onClose(); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /lib/pages/contacts/chat_group_information/set_group_nickname/index.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter/services.dart'; 3 | import 'package:get/get.dart'; 4 | import 'package:linyu_mobile/components/app_bar_title/index.dart'; 5 | import 'package:linyu_mobile/components/custom_text_button/index.dart'; 6 | import 'package:linyu_mobile/components/custom_text_field/index.dart'; 7 | import 'package:linyu_mobile/pages/contacts/chat_group_information/set_group_nickname/logic.dart'; 8 | import 'package:linyu_mobile/utils/getx_config/config.dart'; 9 | 10 | class SetGroupNickNamePage extends CustomWidget { 11 | SetGroupNickNamePage({super.key}); 12 | 13 | @override 14 | Widget buildWidget(BuildContext context) { 15 | return Scaffold( 16 | backgroundColor: const Color(0xFFF9FBFF), 17 | appBar: AppBar( 18 | backgroundColor: Colors.transparent, 19 | title: const AppBarTitle('群昵称'), 20 | centerTitle: true, 21 | elevation: 0, 22 | systemOverlayStyle: SystemUiOverlayStyle.dark, 23 | actions: [ 24 | CustomTextButton('完成', 25 | onTap: controller.onSetName, 26 | padding: 27 | const EdgeInsets.symmetric(horizontal: 20.0, vertical: 5.0), 28 | fontSize: 14), 29 | ]), 30 | body: Container( 31 | padding: const EdgeInsets.symmetric(horizontal: 16.0), 32 | child: Column( 33 | children: [ 34 | const SizedBox(height: 10), 35 | Obx( 36 | () => CustomTextField( 37 | labelText: "群昵称", 38 | controller: controller.nameController, 39 | onChanged: (value) { 40 | controller.nameLength.value = value.length; 41 | }, 42 | inputLimit: 10, 43 | hintText: "请输入群昵称~", 44 | suffix: Text('${controller.nameLength.value}/10'), 45 | ), 46 | ) 47 | ], 48 | ), 49 | ), 50 | ); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /lib/pages/contacts/chat_group_information/set_group_nickname/logic.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:get/get.dart'; 3 | import 'package:linyu_mobile/api/chat_group_api.dart'; 4 | import 'package:linyu_mobile/components/custom_flutter_toast/index.dart'; 5 | 6 | class SetGroupNameNickLogic extends GetxController { 7 | final _chatGroupApi = ChatGroupApi(); 8 | final TextEditingController nameController = TextEditingController(); 9 | late String chatGroupId; 10 | late RxInt nameLength = 0.obs; 11 | 12 | @override 13 | void onInit() { 14 | nameController.text = Get.arguments['name']; 15 | chatGroupId = Get.arguments['chatGroupId']; 16 | nameLength.value = nameController.text.length; 17 | } 18 | 19 | void onSetName() async { 20 | if (nameController.text == null) { 21 | return; 22 | } 23 | final response = await _chatGroupApi.update( 24 | chatGroupId, 'group_name', nameController.text); 25 | if (response['code'] == 0) { 26 | CustomFlutterToast.showSuccessToast('群昵称设置成功~'); 27 | Get.back(result: nameController.text); 28 | } else { 29 | CustomFlutterToast.showErrorToast(response['msg']); 30 | } 31 | } 32 | 33 | @override 34 | void onClose() { 35 | nameController.dispose(); 36 | super.onClose(); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /lib/pages/contacts/chat_group_information/set_group_remark/index.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter/services.dart'; 3 | import 'package:get/get_state_manager/src/rx_flutter/rx_obx_widget.dart'; 4 | import 'package:linyu_mobile/components/app_bar_title/index.dart'; 5 | import 'package:linyu_mobile/components/custom_text_button/index.dart'; 6 | import 'package:linyu_mobile/components/custom_text_field/index.dart'; 7 | import 'package:linyu_mobile/pages/contacts/chat_group_information/set_group_remark/logic.dart'; 8 | import 'package:linyu_mobile/utils/getx_config/config.dart'; 9 | 10 | class SetGroupRemarkPage extends CustomWidget { 11 | SetGroupRemarkPage({super.key}); 12 | 13 | @override 14 | Widget buildWidget(BuildContext context) { 15 | return Scaffold( 16 | backgroundColor: const Color(0xFFF9FBFF), 17 | appBar: AppBar( 18 | backgroundColor: Colors.transparent, 19 | title: const AppBarTitle('群备注'), 20 | centerTitle: true, 21 | elevation: 0, 22 | systemOverlayStyle: SystemUiOverlayStyle.dark, 23 | actions: [ 24 | CustomTextButton('完成', 25 | onTap: controller.onSetName, 26 | padding: 27 | const EdgeInsets.symmetric(horizontal: 20.0, vertical: 5.0), 28 | fontSize: 14), 29 | ]), 30 | body: Container( 31 | padding: const EdgeInsets.symmetric(horizontal: 16.0), 32 | child: Column( 33 | children: [ 34 | const SizedBox(height: 10), 35 | Obx( 36 | () => CustomTextField( 37 | labelText: "群备注", 38 | controller: controller.remarkController, 39 | onChanged: (value) { 40 | controller.remarkLength.value = value.length; 41 | }, 42 | inputLimit: 10, 43 | hintText: "请输入群备注~", 44 | suffix: Text('${controller.remarkLength.value}/10'), 45 | ), 46 | ), 47 | ], 48 | ), 49 | ), 50 | ); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /lib/pages/contacts/chat_group_information/set_group_remark/logic.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:get/get.dart'; 3 | import 'package:linyu_mobile/api/chat_group_api.dart'; 4 | import 'package:linyu_mobile/components/custom_flutter_toast/index.dart'; 5 | 6 | class SetGroupRemarkLogic extends GetxController { 7 | final _chatGroupApi = ChatGroupApi(); 8 | final TextEditingController remarkController = TextEditingController(); 9 | late String chatGroupId; 10 | late RxInt remarkLength = 0.obs; 11 | 12 | @override 13 | void onInit() { 14 | remarkController.text = Get.arguments['remark']; 15 | chatGroupId = Get.arguments['chatGroupId']; 16 | remarkLength.value = remarkController.text.length; 17 | } 18 | 19 | void onSetName() async { 20 | if (remarkController.text == null) { 21 | return; 22 | } 23 | final response = await _chatGroupApi.update( 24 | chatGroupId, 'group_remark', remarkController.text); 25 | if (response['code'] == 0) { 26 | CustomFlutterToast.showSuccessToast('备注修改成功~'); 27 | Get.back(result: remarkController.text); 28 | } else { 29 | CustomFlutterToast.showErrorToast(response['msg']); 30 | } 31 | } 32 | 33 | @override 34 | void onClose() { 35 | remarkController.dispose(); 36 | super.onClose(); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /lib/pages/contacts/friend_information/set_remark/index.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter/services.dart'; 3 | import 'package:linyu_mobile/components/app_bar_title/index.dart'; 4 | import 'package:linyu_mobile/components/custom_text_button/index.dart'; 5 | import 'package:linyu_mobile/components/custom_text_field/index.dart'; 6 | import 'package:linyu_mobile/pages/contacts/friend_information/set_remark/logic.dart'; 7 | import 'package:linyu_mobile/utils/getx_config/config.dart'; 8 | 9 | class SetRemarkPage extends CustomWidget { 10 | SetRemarkPage({super.key}); 11 | 12 | @override 13 | Widget buildWidget(BuildContext context) { 14 | return Scaffold( 15 | backgroundColor: const Color(0xFFF9FBFF), 16 | appBar: AppBar( 17 | backgroundColor: Colors.transparent, 18 | title: const AppBarTitle('好友备注'), 19 | centerTitle: true, 20 | elevation: 0, 21 | systemOverlayStyle: SystemUiOverlayStyle.dark, 22 | actions: [ 23 | CustomTextButton('完成', 24 | onTap: controller.onSetRemark, 25 | padding: 26 | const EdgeInsets.symmetric(horizontal: 20.0, vertical: 5.0), 27 | fontSize: 14), 28 | ]), 29 | body: Container( 30 | padding: const EdgeInsets.symmetric(horizontal: 16.0), 31 | child: Column( 32 | children: [ 33 | const SizedBox(height: 10), 34 | CustomTextField( 35 | labelText: "备注", 36 | controller: controller.remarkController, 37 | inputLimit: 10, 38 | hintText: "请输入用户备注~", 39 | onChanged: controller.onRemarkChanged, 40 | suffix: Text('${controller.remarkLength}/10'), 41 | ), 42 | ], 43 | ), 44 | ), 45 | ); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /lib/pages/contacts/friend_information/set_remark/logic.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:get/get.dart'; 3 | import 'package:linyu_mobile/api/friend_api.dart'; 4 | import 'package:linyu_mobile/components/custom_flutter_toast/index.dart'; 5 | import 'package:linyu_mobile/pages/contacts/friend_information/logic.dart'; 6 | import 'package:linyu_mobile/utils/getx_config/GlobalThemeConfig.dart'; 7 | 8 | class SetRemarkLogic extends GetxController { 9 | final _friendApi = FriendApi(); 10 | final TextEditingController remarkController = TextEditingController(); 11 | late int remarkLength = 0; 12 | late String friendId; 13 | final FriendInformationLogic _friendInformationLogic = 14 | GetInstance().find(); 15 | GlobalThemeConfig theme = GetInstance().find(); 16 | 17 | @override 18 | void onInit() { 19 | remarkController.text = Get.arguments['remark']; 20 | friendId = Get.arguments['friendId']; 21 | remarkLength = remarkController.text.length; 22 | } 23 | 24 | void onSetRemark() async { 25 | if (remarkController.text == null) { 26 | return; 27 | } 28 | final response = 29 | await _friendApi.setRemark(friendId, remarkController.text); 30 | if (response['code'] == 0) { 31 | CustomFlutterToast.showSuccessToast('备注设置成功~'); 32 | _friendInformationLogic.getFriendInfo(); 33 | Get.back(); 34 | } else { 35 | CustomFlutterToast.showErrorToast(response['msg']); 36 | } 37 | } 38 | 39 | void onRemarkChanged(String value) { 40 | remarkLength = value.length; 41 | update([const Key('set_remark')]); 42 | } 43 | 44 | @override 45 | void onClose() { 46 | remarkController.dispose(); 47 | super.onClose(); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /lib/pages/contacts/friend_information/signature_info/index.dart: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DWHengr/linyu_mobile/a207b3ce7dfeb3ed5aa5d2bc2f9c899926b61287/lib/pages/contacts/friend_information/signature_info/index.dart -------------------------------------------------------------------------------- /lib/pages/contacts/friend_information/signature_info/logic.dart: -------------------------------------------------------------------------------- 1 | import 'package:get/get.dart'; 2 | 3 | class SignatureInfoLogic extends GetxController { 4 | final signature = ''.obs; 5 | 6 | @override 7 | void onInit() { 8 | super.onInit(); 9 | signature.value = Get.arguments['signature']; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /lib/pages/contacts/user_select/logic.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | import 'package:get/get.dart'; 3 | import 'package:linyu_mobile/api/friend_api.dart'; 4 | 5 | class UserSelectLogic extends GetxController { 6 | final _friendApi = FriendApi(); 7 | late List userList = []; 8 | late List allUserList = []; 9 | late List selectedUsers = []; 10 | late List onlyUsers = []; 11 | 12 | @override 13 | void onInit() { 14 | super.onInit(); 15 | selectedUsers = Get.arguments['selectedUsers'] ?? []; 16 | onlyUsers = Get.arguments['onlyUsers'] ?? []; 17 | update([const Key('user_select')]); 18 | loadUsers(); 19 | } 20 | 21 | void loadUsers() async { 22 | _friendApi.listFlat().then((res) { 23 | if (res['code'] == 0) { 24 | userList = res['data']; 25 | allUserList = res['data']; 26 | update([const Key('user_select')]); 27 | } 28 | }); 29 | } 30 | 31 | void handlerSelectUser(user) { 32 | if (selectedUsers 33 | .any((selected) => selected['friendId'] == user['friendId'])) { 34 | selectedUsers 35 | .removeWhere((selected) => selected['friendId'] == user['friendId']); 36 | } else { 37 | selectedUsers.add(user); 38 | } 39 | update([const Key('user_select')]); 40 | } 41 | 42 | void handlerSearchUser(String keyword) { 43 | if (keyword.isEmpty || keyword == '') { 44 | userList = allUserList; 45 | } else { 46 | userList = allUserList 47 | .where((user) => 48 | (user['name']?.contains(keyword) ?? false) || 49 | (user['remark']?.contains(keyword) ?? false)) 50 | .toList(); 51 | } 52 | update([const Key('user_select')]); 53 | } 54 | 55 | void handlerConfirmSelection() { 56 | Get.back(result: selectedUsers); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /lib/pages/file_details/logic.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | import 'package:dio/dio.dart'; 4 | import 'package:flutter/cupertino.dart'; 5 | import 'package:get/get.dart'; 6 | import 'package:linyu_mobile/components/custom_flutter_toast/index.dart'; 7 | import 'package:linyu_mobile/utils/getx_config/GlobalData.dart'; 8 | import 'package:open_filex/open_filex.dart'; 9 | import 'package:path_provider/path_provider.dart'; 10 | 11 | class FileDetailsLogic extends GetxController { 12 | late String fileName; 13 | late String fileUrl; 14 | late String fileType; 15 | late int fileSize; 16 | RxDouble downloadProgress = 0.0.obs; 17 | RxBool isDownloading = false.obs; 18 | bool isDownloaded = false; 19 | String? localFilePath; 20 | CancelToken? cancelToken; 21 | 22 | GlobalData get globalData => GetInstance().find(); 23 | 24 | @override 25 | void onInit() { 26 | super.onInit(); 27 | fileName = Get.arguments['fileName']; 28 | fileUrl = Get.arguments['fileUrl']; 29 | fileType = Get.arguments['fileType']; 30 | fileSize = Get.arguments['fileSize']; 31 | checkFileExists(); 32 | } 33 | 34 | Future checkFileExists() async { 35 | final localPath = await getLocalFilePath(); 36 | var file = File(localPath); 37 | if (file.existsSync() && file.lengthSync() == fileSize) { 38 | isDownloaded = true; 39 | localFilePath = localPath; 40 | update([const Key('file_details')]); 41 | } 42 | } 43 | 44 | Future getLocalFilePath() async { 45 | final directory = await getTemporaryDirectory(); 46 | return '${directory.path}/${globalData.currentUserAccount}/$fileName'; 47 | } 48 | 49 | Future startDownload() async { 50 | if (isDownloading.value) { 51 | return; 52 | } 53 | isDownloading.value = true; 54 | cancelToken = CancelToken(); 55 | try { 56 | final dio = Dio(); 57 | final localPath = await getLocalFilePath(); 58 | await dio.download( 59 | fileUrl, 60 | localPath, 61 | cancelToken: cancelToken, 62 | onReceiveProgress: (received, total) { 63 | if (total != -1) { 64 | downloadProgress.value = received / total; 65 | } 66 | }, 67 | ); 68 | isDownloaded = true; 69 | localFilePath = localPath; 70 | } on DioException catch (e) { 71 | if (CancelToken.isCancel(e)) { 72 | CustomFlutterToast.showSuccessToast('下载已取消~'); 73 | } else { 74 | CustomFlutterToast.showErrorToast('下载失败,请稍后再试~'); 75 | } 76 | } finally { 77 | isDownloading.value = false; 78 | update([const Key('file_details')]); 79 | } 80 | } 81 | 82 | void cancelDownload() { 83 | if (!isDownloading.value) return; 84 | cancelToken?.cancel('下载取消'); 85 | downloadProgress.value = 0.0; 86 | isDownloading.value = false; 87 | update([const Key('file_details')]); 88 | } 89 | 90 | Future openFile() async { 91 | if (localFilePath != null) { 92 | OpenResult result = await OpenFilex.open(localFilePath!); 93 | if (result.type == ResultType.noAppToOpen) { 94 | CustomFlutterToast.showErrorToast('没有找到对应的打开方式~'); 95 | } 96 | if (result.type == ResultType.permissionDenied) { 97 | CustomFlutterToast.showErrorToast('权限不足,无法打开文件~'); 98 | } 99 | } 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /lib/pages/image_viewer/image_viewer_update/index.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:get/get_state_manager/src/rx_flutter/rx_obx_widget.dart'; 3 | import 'package:image_picker/image_picker.dart'; 4 | import 'package:linyu_mobile/components/custom_button/index.dart'; 5 | import 'package:linyu_mobile/pages/image_viewer/image_viewer_update/logic.dart'; 6 | import 'package:linyu_mobile/utils/getx_config/config.dart'; 7 | import 'package:photo_view/photo_view.dart'; 8 | 9 | class ImageViewerUpdatePage extends CustomWidget { 10 | ImageViewerUpdatePage({super.key}); 11 | 12 | @override 13 | Widget buildWidget(BuildContext context) { 14 | return Scaffold( 15 | appBar: AppBar( 16 | centerTitle: true, 17 | iconTheme: const IconThemeData(color: Colors.white), 18 | backgroundColor: Colors.black, 19 | ), 20 | body: Container( 21 | padding: const EdgeInsets.all(20), 22 | color: Colors.black, 23 | child: Column( 24 | crossAxisAlignment: CrossAxisAlignment.center, 25 | mainAxisAlignment: MainAxisAlignment.center, 26 | children: [ 27 | Expanded( 28 | child: Container( 29 | decoration: const BoxDecoration( 30 | borderRadius: BorderRadius.all(Radius.circular(20)), 31 | ), 32 | child: Obx( 33 | () => PhotoView( 34 | minScale: PhotoViewComputedScale.contained * 0.5, 35 | maxScale: PhotoViewComputedScale.covered * 2, 36 | imageProvider: NetworkImage(controller.imageUrl.value), 37 | ), 38 | ), 39 | ), 40 | ), 41 | const SizedBox(height: 30), 42 | Obx( 43 | () { 44 | if (controller.isUpdate.value) { 45 | return Column( 46 | children: [ 47 | CustomButton( 48 | text: controller.text.value, 49 | onTap: () => _showDialog(context), 50 | width: MediaQuery.of(context).size.width / 2, 51 | ), 52 | const SizedBox(height: 15), 53 | ], 54 | ); 55 | } 56 | return const SizedBox.shrink(); 57 | }, 58 | ), 59 | CustomButton( 60 | text: '保存图片', 61 | onTap: () => controller.saveImage(), 62 | type: 'minor', 63 | width: MediaQuery.of(context).size.width / 2, 64 | ), 65 | const SizedBox(height: 20), 66 | ], 67 | ), 68 | ), 69 | ); 70 | } 71 | 72 | void _showDialog(BuildContext context) { 73 | showModalBottomSheet( 74 | context: context, 75 | backgroundColor: Colors.white, 76 | shape: const RoundedRectangleBorder( 77 | borderRadius: BorderRadius.vertical(top: Radius.circular(10.0)), 78 | ), 79 | builder: (BuildContext context) { 80 | return Column( 81 | mainAxisSize: MainAxisSize.min, 82 | children: [ 83 | ListTile( 84 | leading: const Icon(Icons.photo), 85 | title: const Text('图库'), 86 | onTap: () => controller.cropChatBackgroundPicture(null), 87 | ), 88 | ListTile( 89 | leading: const Icon(Icons.camera_alt), 90 | title: const Text('拍照'), 91 | onTap: () => 92 | controller.cropChatBackgroundPicture(ImageSource.camera), 93 | ), 94 | ], 95 | ); 96 | }, 97 | ); 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /lib/pages/image_viewer/image_viewer_update/logic.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | import 'package:get/get.dart'; 4 | import 'package:image_gallery_saver/image_gallery_saver.dart'; 5 | import 'package:image_picker/image_picker.dart'; 6 | import 'package:linyu_mobile/components/custom_flutter_toast/index.dart'; 7 | import 'package:linyu_mobile/utils/cropPicture.dart'; 8 | import 'package:permission_handler/permission_handler.dart'; 9 | import 'package:http/http.dart' as http; 10 | 11 | class ImageViewerUpdateLogic extends GetxController { 12 | late RxString imageUrl = ''.obs; 13 | late RxString text = ''.obs; 14 | late UploadPictureCallback onConfirm; 15 | late RxBool isUpdate = true.obs; 16 | 17 | @override 18 | void onInit() { 19 | imageUrl.value = Get.arguments['imageUrl'] ?? ''; 20 | text.value = Get.arguments['text'] ?? '更改头像'; 21 | onConfirm = Get.arguments['onConfirm'] ?? (File file) async {}; 22 | isUpdate.value = Get.arguments['isUpdate'] ?? true; 23 | super.onInit(); 24 | } 25 | 26 | Future cropChatBackgroundPicture(ImageSource? type) async { 27 | Get.back(); 28 | cropPicture(type, onUpdate); 29 | } 30 | 31 | Future saveImage() async { 32 | try { 33 | // 检查权限 34 | var status = await Permission.storage.status; 35 | if (!status.isGranted) { 36 | await Permission.storage.request(); 37 | } 38 | // 下载图片 39 | final response = await http.get(Uri.parse(imageUrl.value)); 40 | final bytes = response.bodyBytes; 41 | // 保存到相册 42 | final result = await ImageGallerySaver.saveImage(bytes, 43 | quality: 100, name: "image_${DateTime.now().millisecondsSinceEpoch}"); 44 | CustomFlutterToast.showSuccessToast("保存成功${result['filePath']}"); 45 | } catch (e) { 46 | CustomFlutterToast.showSuccessToast("保存失败~"); 47 | } 48 | } 49 | 50 | Future onUpdate(File file) async { 51 | onConfirm(file); 52 | Get.back(); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /lib/pages/image_viewer/logic.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | import 'package:get/get.dart'; 3 | import 'package:image_gallery_saver/image_gallery_saver.dart'; 4 | import 'package:linyu_mobile/components/custom_flutter_toast/index.dart'; 5 | import 'package:permission_handler/permission_handler.dart'; 6 | import 'package:http/http.dart' as http; 7 | 8 | class ImageViewerLogic extends GetxController { 9 | late final PageController pageController; 10 | late final List imageUrls; 11 | final RxInt currentIndex = 0.obs; 12 | 13 | @override 14 | void onInit() { 15 | imageUrls = Get.arguments['imageUrls']; 16 | currentIndex.value = Get.arguments['currentIndex']; 17 | pageController = PageController(initialPage: currentIndex.value); 18 | super.onInit(); 19 | } 20 | 21 | void onPageChanged(int index) { 22 | currentIndex.value = index; 23 | update(); 24 | } 25 | 26 | Future saveImage() async { 27 | try { 28 | // 检查权限 29 | var status = await Permission.storage.status; 30 | if (!status.isGranted) { 31 | await Permission.storage.request(); 32 | } 33 | // 下载图片 34 | final response = await http.get(Uri.parse(imageUrls[currentIndex.value])); 35 | final bytes = response.bodyBytes; 36 | // 保存到相册 37 | final result = await ImageGallerySaver.saveImage(bytes, 38 | quality: 100, name: "image_${DateTime.now().millisecondsSinceEpoch}"); 39 | CustomFlutterToast.showSuccessToast("保存成功${result['filePath']}"); 40 | } catch (e) { 41 | CustomFlutterToast.showSuccessToast("保存失败~"); 42 | } 43 | } 44 | 45 | @override 46 | void onClose() { 47 | pageController.dispose(); 48 | super.onClose(); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /lib/pages/login/logic.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:get/get.dart'; 3 | import 'package:linyu_mobile/utils/getx_config/GlobalThemeConfig.dart'; 4 | import 'package:shared_preferences/shared_preferences.dart'; 5 | import 'package:linyu_mobile/api/user_api.dart'; 6 | import 'package:linyu_mobile/utils/encrypt.dart'; 7 | import 'package:url_launcher/url_launcher.dart'; 8 | 9 | class LoginPageLogic extends GetxController { 10 | final _useApi = UserApi(); 11 | 12 | RxInt accountTextLength = 0.obs; 13 | 14 | RxInt passwordTextLength = 0.obs; 15 | 16 | //用户账号输入长度 17 | void onAccountTextChanged(String value) { 18 | accountTextLength.value = value.length; 19 | if (accountTextLength.value >= 30) accountTextLength.value = 30; 20 | } 21 | 22 | //用户密码输入长度 23 | void onPasswordTextChanged(String value) { 24 | passwordTextLength.value = value.length; 25 | if (passwordTextLength.value >= 16) passwordTextLength.value = 16; 26 | } 27 | 28 | void _dialog( 29 | String content, 30 | BuildContext context, [ 31 | String title = '登录失败', 32 | ]) { 33 | showDialog( 34 | context: context, 35 | builder: (context) => AlertDialog( 36 | title: Text(title), 37 | content: Text(content), 38 | actions: [ 39 | TextButton( 40 | onPressed: () => Navigator.pop(context), 41 | child: const Text("确定"), 42 | ), 43 | ], 44 | ), 45 | ); 46 | } 47 | 48 | void login(context, username, password) async { 49 | if (username.isEmpty || password.isEmpty) { 50 | _dialog("用户名或密码不能为空~", context = context); 51 | return; 52 | } 53 | final encryptedPassword = await passwordEncrypt(password); 54 | final loginResult = await _useApi.login(username, encryptedPassword); 55 | if (loginResult['code'] == 0) { 56 | SharedPreferences prefs = await SharedPreferences.getInstance(); 57 | await prefs.setString('x-token', loginResult['data']['token']); 58 | await prefs.setString('username', loginResult['data']['username']); 59 | await prefs.setString('userId', loginResult['data']['userId']); 60 | await prefs.setString('account', loginResult['data']['account']); 61 | await prefs.setString('portrait', loginResult['data']['portrait']); 62 | await prefs.setString('sex', loginResult['data']['sex'] ?? '男'); 63 | Get.offAllNamed('/?sex=${loginResult['data']['sex']}'); 64 | } else { 65 | _dialog("用户名或密码错误,请重试尝试~", context = context); 66 | } 67 | } 68 | 69 | void toRegister() => Get.toNamed('/register'); 70 | 71 | void toRetrievePassword() => Get.toNamed('/retrieve_password'); 72 | 73 | Future launchURL(String url) async { 74 | final uri = Uri.parse(url); 75 | if (await canLaunchUrl(uri)) { 76 | await launchUrl( 77 | uri, 78 | mode: LaunchMode.externalApplication, 79 | ); 80 | } else { 81 | throw 'Could not launch $url'; 82 | } 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /lib/pages/mine/about/index.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter/services.dart'; 3 | import 'package:linyu_mobile/components/app_bar_title/index.dart'; 4 | import 'package:linyu_mobile/components/custom_label_value/index.dart'; 5 | import 'package:linyu_mobile/pages/mine/about/logic.dart'; 6 | import 'package:linyu_mobile/utils/getx_config/config.dart'; 7 | 8 | class AboutPage extends CustomWidget { 9 | AboutPage({super.key}); 10 | 11 | @override 12 | Widget buildWidget(BuildContext context) { 13 | return Container( 14 | decoration: BoxDecoration( 15 | gradient: LinearGradient( 16 | colors: [ 17 | theme.minorColor, 18 | const Color(0xFFFFFFFF), 19 | const Color(0xFFFFFFFF), 20 | theme.minorColor, 21 | ], 22 | // 渐变颜色 23 | begin: Alignment.topLeft, 24 | end: Alignment.bottomRight, 25 | ), 26 | ), 27 | child: Scaffold( 28 | backgroundColor: Colors.transparent, 29 | resizeToAvoidBottomInset: false, 30 | appBar: AppBar( 31 | backgroundColor: Colors.transparent, 32 | title: const AppBarTitle('关于我们'), 33 | centerTitle: true, 34 | elevation: 0, 35 | systemOverlayStyle: SystemUiOverlayStyle.dark, 36 | ), 37 | body: Container( 38 | padding: const EdgeInsets.only( 39 | left: 16.0, right: 16.0, top: 0, bottom: 40), 40 | child: Column( 41 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 42 | children: [ 43 | Column( 44 | children: [ 45 | const SizedBox(height: 30), 46 | Image.asset( 47 | "assets/images/logo-about.png", 48 | width: 80, 49 | ), 50 | const SizedBox(height: 10), 51 | Image.asset( 52 | "assets/images/linyu.png", 53 | height: 30, 54 | ), 55 | const SizedBox(height: 30), 56 | const CustomLabelValue( 57 | label: '作者', value: "Heath", width: 80), 58 | const SizedBox(height: 1), 59 | const CustomLabelValue( 60 | label: 'QQ群', value: "729158695", width: 80), 61 | const SizedBox(height: 1), 62 | const CustomLabelValue( 63 | label: '开源地址', 64 | value: "https://github.com/DWHengr/linyu_mobile", 65 | width: 80), 66 | const SizedBox(height: 30), 67 | ], 68 | ), 69 | Row( 70 | mainAxisAlignment: MainAxisAlignment.center, 71 | children: [ 72 | const Text( 73 | "基于", 74 | style: TextStyle(color: Colors.black54), 75 | ), 76 | const SizedBox(width: 10), 77 | Image.asset( 78 | "assets/images/flutter.png", 79 | height: 20, 80 | ), 81 | const SizedBox(width: 10), 82 | const Text( 83 | "开发", 84 | style: TextStyle(color: Colors.black54), 85 | ), 86 | ], 87 | ) 88 | ], 89 | ), 90 | ), 91 | ), 92 | ); 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /lib/pages/mine/about/logic.dart: -------------------------------------------------------------------------------- 1 | import 'package:get/get_state_manager/src/simple/get_controllers.dart'; 2 | 3 | class AboutLogic extends GetxController { 4 | 5 | } -------------------------------------------------------------------------------- /lib/pages/mine/logic.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/foundation.dart'; 2 | import 'package:get/get.dart'; 3 | import 'package:linyu_mobile/utils/getx_config/GlobalData.dart'; 4 | import 'package:linyu_mobile/utils/web_socket.dart'; 5 | import 'package:shared_preferences/shared_preferences.dart'; 6 | 7 | class MineLogic extends GetxController { 8 | late dynamic currentUserInfo = {}; 9 | final _wsManager = Get.find(); 10 | final _globalData = Get.find(); 11 | final SharedPreferences _prefs = Get.find(); 12 | 13 | void init() async { 14 | currentUserInfo['name'] = _prefs.getString('username'); 15 | currentUserInfo['portrait'] = _prefs.getString('portrait'); 16 | currentUserInfo['account'] = _prefs.getString('account'); 17 | currentUserInfo['sex'] = _prefs.getString('sex'); 18 | update([const Key("mine")]); 19 | } 20 | 21 | void handlerLogout() async { 22 | final bool isLogout = await _prefs.clear(); 23 | _globalData.currentToken = null; 24 | _wsManager.disconnect(); 25 | if (kDebugMode) print('logout success: $isLogout'); 26 | if (isLogout) Get.offAllNamed('/login'); 27 | } 28 | 29 | @override 30 | void onInit() { 31 | init(); 32 | if (kDebugMode) print('currentToken: ${_globalData.currentToken}'); 33 | super.onInit(); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /lib/pages/mine/mine_qr_code/logic.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | import 'package:get/get_state_manager/src/simple/get_controllers.dart'; 3 | import 'package:linyu_mobile/api/qr_api.dart'; 4 | import 'package:shared_preferences/shared_preferences.dart'; 5 | 6 | class MineQRCodeLogic extends GetxController { 7 | final _qrApi = QrApi(); 8 | late dynamic currentUserInfo = {}; 9 | late String qrCode = ''; 10 | 11 | @override 12 | void onInit() async { 13 | super.onInit(); 14 | SharedPreferences prefs = await SharedPreferences.getInstance(); 15 | currentUserInfo['name'] = prefs.getString('username'); 16 | currentUserInfo['portrait'] = prefs.getString('portrait'); 17 | currentUserInfo['account'] = prefs.getString('account'); 18 | currentUserInfo['sex'] = prefs.getString('sex'); 19 | update([const Key("mine_qr_code")]); 20 | onQrCode(); 21 | } 22 | 23 | void onQrCode() { 24 | _qrApi.code().then((res) { 25 | if (res['code'] == 0) { 26 | qrCode = res['data']; 27 | update([const Key("mine_qr_code")]); 28 | } 29 | }); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /lib/pages/mine/system_notify/index.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:linyu_mobile/components/app_bar_title/index.dart'; 3 | import 'package:linyu_mobile/components/custom_image/index.dart'; 4 | import 'package:linyu_mobile/pages/mine/system_notify/logic.dart'; 5 | import 'package:linyu_mobile/utils/date.dart'; 6 | import 'package:linyu_mobile/utils/getx_config/config.dart'; 7 | 8 | class SystemNotifyPage extends CustomWidget { 9 | SystemNotifyPage({super.key}); 10 | 11 | @override 12 | Widget buildWidget(BuildContext context) { 13 | return Scaffold( 14 | backgroundColor: const Color(0xFFF9FBFF), 15 | appBar: AppBar( 16 | title: const AppBarTitle('系统通知'), 17 | centerTitle: true, 18 | backgroundColor: const Color(0xFFF9FBFF), 19 | ), 20 | body: Padding( 21 | padding: const EdgeInsets.all(16), 22 | child: ListView( 23 | children: [ 24 | ...controller.systemNotifyList 25 | .map((notify) => _buildNotifyItem(notify)), 26 | ], 27 | ), 28 | ), 29 | ); 30 | } 31 | 32 | Widget _buildNotifyItem(notify) { 33 | return Column( 34 | mainAxisSize: MainAxisSize.max, 35 | children: [ 36 | Container( 37 | padding: const EdgeInsets.all(5), 38 | decoration: const BoxDecoration( 39 | borderRadius: BorderRadius.all(Radius.circular(5)), 40 | color: Colors.white, 41 | ), 42 | child: Text( 43 | DateUtil.formatTime(notify['createTime']), 44 | style: const TextStyle(fontSize: 12), 45 | ), 46 | ), 47 | const SizedBox(height: 5), 48 | Container( 49 | width: double.infinity, 50 | padding: const EdgeInsets.all(10), 51 | decoration: const BoxDecoration( 52 | borderRadius: BorderRadius.all(Radius.circular(10)), 53 | color: Colors.white, 54 | ), 55 | child: Column( 56 | crossAxisAlignment: CrossAxisAlignment.start, 57 | children: [ 58 | CustomImage( 59 | url: notify['content']['img'] ?? 60 | 'http://192.168.101.4:9000/linyu/default-portrait.jpg', 61 | ), 62 | const SizedBox(height: 5), 63 | Text( 64 | notify['content']['title'] ?? '', 65 | style: 66 | const TextStyle(fontSize: 16, fontWeight: FontWeight.w500), 67 | ), 68 | Text( 69 | notify['content']['text'] ?? '', 70 | style: const TextStyle(fontSize: 14, color: Colors.black54), 71 | ), 72 | ], 73 | ), 74 | ), 75 | const SizedBox(height: 15), 76 | ], 77 | ); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /lib/pages/mine/system_notify/logic.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | import 'package:get/get_state_manager/src/simple/get_controllers.dart'; 3 | import 'package:linyu_mobile/api/notify_api.dart'; 4 | 5 | class SystemNotifyLogic extends GetxController { 6 | final _notifyApi = NotifyApi(); 7 | late List systemNotifyList = []; 8 | 9 | @override 10 | void onInit() { 11 | super.onInit(); 12 | onGetSystemNotify(); 13 | } 14 | 15 | void onGetSystemNotify() { 16 | _notifyApi.systemListNotify().then((res) { 17 | if (res['code'] == 0) { 18 | systemNotifyList = res['data']; 19 | update([const Key('system_notify')]); 20 | } 21 | }); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /lib/pages/navigation/index.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:get/get_state_manager/src/rx_flutter/rx_obx_widget.dart'; 3 | import 'package:linyu_mobile/components/custom_tip/index.dart'; 4 | import 'package:linyu_mobile/pages/chat_list/index.dart'; 5 | import 'package:linyu_mobile/pages/contacts/index.dart'; 6 | import 'package:linyu_mobile/pages/mine/index.dart'; 7 | import 'package:linyu_mobile/pages/navigation/logic.dart'; 8 | import 'package:linyu_mobile/pages/talk/index.dart'; 9 | import 'package:linyu_mobile/utils/getx_config/config.dart'; 10 | 11 | class NavigationPage extends CustomWidget { 12 | NavigationPage({required super.key}); 13 | 14 | Widget _buildPage(int index) { 15 | switch (index) { 16 | case 0: 17 | return ChatListPage(key: const Key('chat_list')); 18 | case 1: 19 | return ContactsPage(key: const Key('contacts')); 20 | case 2: 21 | return TalkPage(key: const Key('talk')); 22 | case 3: 23 | return MinePage(key: const Key('mine')); 24 | default: 25 | return ChatListPage(key: const Key('chat_list')); 26 | } 27 | } 28 | 29 | @override 30 | Widget buildWidget(BuildContext context) { 31 | return Obx( 32 | () => Scaffold( 33 | body: _buildPage((controller.currentIndex.value)), 34 | bottomNavigationBar: BottomNavigationBar( 35 | currentIndex: controller.currentIndex.value, 36 | onTap: (index) => controller.currentIndex.value = index, 37 | selectedItemColor: theme.primaryColor, 38 | showUnselectedLabels: true, 39 | backgroundColor: const Color(0xFFEDF2F9), 40 | unselectedItemColor: Colors.grey, 41 | type: BottomNavigationBarType.fixed, 42 | items: List.generate(controller.unselectedIcons.length, (index) { 43 | return BottomNavigationBarItem( 44 | icon: Stack( 45 | clipBehavior: Clip.none, 46 | children: [ 47 | Image.asset( 48 | controller.currentIndex.value == index 49 | ? 'assets/images/${controller.selectedIcons[index]}-${theme.themeMode.value}.png' 50 | : controller.unselectedIcons[index], 51 | width: 26, 52 | height: 26, 53 | ), 54 | if (controller.selectedIcons[index] == 'chat' && 55 | globalData.getUnreadCount('chat') > 0) 56 | CustomTip(globalData.getUnreadCount('chat')), 57 | if (controller.selectedIcons[index] == 'user' && 58 | globalData.getUnreadCount('friendNotify') > 0) 59 | CustomTip(globalData.getUnreadCount('friendNotify')), 60 | ], 61 | ), 62 | label: controller.name[index], 63 | ); 64 | }), 65 | ), 66 | ), 67 | ); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /lib/pages/navigation/logic.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:flutter/cupertino.dart'; 4 | import 'package:get/get.dart'; 5 | import 'package:linyu_mobile/utils/getx_config/GlobalData.dart'; 6 | import 'package:linyu_mobile/utils/getx_config/GlobalThemeConfig.dart'; 7 | import 'package:linyu_mobile/utils/notification.dart'; 8 | import 'package:linyu_mobile/utils/permission_handler.dart'; 9 | import 'package:linyu_mobile/utils/web_socket.dart'; 10 | 11 | class NavigationLogic extends GetxController { 12 | late RxInt currentIndex = 0.obs; 13 | final _wsManager = WebSocketUtil(); 14 | StreamSubscription? _subscription; 15 | 16 | GlobalData get globalData => GetInstance().find(); 17 | 18 | Future initThemeData() async { 19 | late String sex = Get.parameters['sex'] ?? "男"; 20 | GlobalThemeConfig theme = GetInstance().find(); 21 | theme.changeThemeMode(sex == "女" ? 'pink' : 'blue'); 22 | } 23 | 24 | @override 25 | void onInit() { 26 | super.onInit(); 27 | (() async { 28 | await globalData.init(); 29 | await NotificationUtil.initialize(); 30 | await NotificationUtil.createNotificationChannel(); 31 | await PermissionHandler.permissionRequest(); 32 | await connectWebSocket(); 33 | eventListen(); 34 | })(); 35 | WidgetsBinding.instance.addPostFrameCallback((_) async { 36 | await initThemeData(); 37 | }); 38 | } 39 | 40 | void eventListen() { 41 | // 监听消息 42 | _subscription = _wsManager.eventStream.listen((event) { 43 | globalData.onGetUserUnreadInfo(); 44 | if (event['type'] == 'on-receive-video') { 45 | var data = event['content']; 46 | if (data['type'] == "invite") { 47 | Get.toNamed('/video_chat', arguments: { 48 | 'userId': data['fromId'], 49 | 'isSender': false, 50 | 'isOnlyAudio': data['isOnlyAudio'], 51 | }); 52 | } 53 | } 54 | }); 55 | } 56 | 57 | Future connectWebSocket() async { 58 | _wsManager.connect(); 59 | } 60 | 61 | final List selectedIcons = [ 62 | 'chat', 63 | 'user', 64 | 'talk', 65 | 'mine', 66 | ]; 67 | 68 | final List unselectedIcons = [ 69 | 'assets/images/chat-empty.png', 70 | 'assets/images/user-empty.png', 71 | 'assets/images/talk-empty.png', 72 | 'assets/images/mine-empty.png', 73 | ]; 74 | 75 | final List name = [ 76 | '消息', 77 | '通讯', 78 | '说说', 79 | '我的', 80 | ]; 81 | 82 | @override 83 | void onClose() { 84 | super.onClose(); 85 | _wsManager.dispose(); 86 | _subscription?.cancel(); 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /lib/pages/password/retrieve/logic.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:flutter/material.dart'; 4 | import 'package:get/get.dart'; 5 | 6 | import 'package:linyu_mobile/api/user_api.dart'; 7 | import 'package:linyu_mobile/components/custom_flutter_toast/index.dart'; 8 | import 'package:linyu_mobile/utils/encrypt.dart'; 9 | 10 | class RetrievePasswordLogic extends GetxController { 11 | final _useApi = UserApi(); 12 | 13 | //账号 14 | final accountController = TextEditingController(); 15 | 16 | //密码 17 | final passwordController = TextEditingController(); 18 | 19 | //验证码 20 | final codeController = TextEditingController(); 21 | 22 | int _countdownTime = 0; 23 | 24 | //计时器 25 | late Timer _timer; 26 | 27 | int get countdownTime => _countdownTime; 28 | 29 | set countdownTime(int value) { 30 | _countdownTime = value; 31 | update([ 32 | const Key("retrieve_password"), 33 | ]); 34 | } 35 | 36 | int _accountTextLength = 0; 37 | 38 | int get accountTextLength => _accountTextLength; 39 | 40 | set accountTextLength(int value) { 41 | _accountTextLength = value; 42 | update([const Key("retrieve_password")]); 43 | } 44 | 45 | int _passwordTextLength = 0; 46 | 47 | int get passwordTextLength => _passwordTextLength; 48 | 49 | set passwordTextLength(int value) { 50 | _passwordTextLength = value; 51 | update([const Key("retrieve_password")]); 52 | } 53 | 54 | //发送验证码 55 | void onTapSendMail() async { 56 | if (countdownTime == 0) { 57 | final String account = accountController.text; 58 | final emailVerificationResult = 59 | await _useApi.emailVerificationByAccount(account); 60 | if (emailVerificationResult['code'] == 0) { 61 | CustomFlutterToast.showSuccessToast('发送成功~'); 62 | countdownTime = 30; 63 | _startCountdownTimer(); 64 | } else { 65 | CustomFlutterToast.showErrorToast(emailVerificationResult['msg']); 66 | } 67 | } 68 | } 69 | 70 | //用户账号输入长度 71 | void onAccountTextChanged(String value) { 72 | accountTextLength = value.length; 73 | if (accountTextLength >= 30) accountTextLength = 30; 74 | } 75 | 76 | //用户密码输入长度 77 | void onPasswordTextChanged(String value) { 78 | passwordTextLength = value.length; 79 | if (passwordTextLength >= 16) passwordTextLength = 16; 80 | } 81 | 82 | void onSubmit() async { 83 | String account = accountController.text; 84 | String password = passwordController.text; 85 | String code = codeController.text; 86 | if (account.isEmpty || password.isEmpty || code.isEmpty) { 87 | CustomFlutterToast.showSuccessToast('不能为空,请填写完整!'); 88 | } else { 89 | final encryptedPassword = await passwordEncrypt(password); 90 | assert(encryptedPassword != "-1"); 91 | final passwordForgetResult = 92 | await _useApi.forget(account, encryptedPassword, code); 93 | if (passwordForgetResult['code'] == 0) { 94 | CustomFlutterToast.showSuccessToast(passwordForgetResult['msg']); 95 | Get.back(); 96 | } else { 97 | CustomFlutterToast.showErrorToast(passwordForgetResult['msg']); 98 | } 99 | } 100 | } 101 | 102 | //开始倒计时 103 | void _startCountdownTimer() { 104 | const oneSec = Duration(seconds: 1); 105 | callback(timer) => { 106 | if (countdownTime < 1) 107 | {_timer.cancel()} 108 | else 109 | {countdownTime = countdownTime - 1} 110 | }; 111 | _timer = Timer.periodic(oneSec, callback); 112 | } 113 | 114 | @override 115 | void onClose() { 116 | accountController.dispose(); 117 | passwordController.dispose(); 118 | codeController.dispose(); 119 | super.onClose(); 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /lib/pages/qr_code_scan/logic.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | import 'package:get/get.dart'; 3 | import 'package:just_audio/just_audio.dart'; 4 | import 'package:linyu_mobile/api/friend_api.dart'; 5 | import 'package:linyu_mobile/api/qr_api.dart'; 6 | import 'package:mobile_scanner/mobile_scanner.dart'; 7 | 8 | class QRCodeScanLogic extends GetxController { 9 | final _qrApi = QrApi(); 10 | final _friendApi = FriendApi(); 11 | final player = AudioPlayer(); 12 | String? qrText; 13 | bool isScanning = true; 14 | 15 | final mobileScannerController = MobileScannerController( 16 | detectionSpeed: DetectionSpeed.noDuplicates, 17 | returnImage: false, 18 | ); 19 | 20 | void onDetect(BarcodeCapture capture) async { 21 | if (isScanning && capture.barcodes.isNotEmpty) { 22 | final barcode = capture.barcodes.first; 23 | qrText = barcode.rawValue; 24 | isScanning = false; 25 | mobileScannerController.stop(); 26 | update([const Key("qr_code_scan")]); 27 | await player.setAsset('assets/sounds/success.mp3'); 28 | await player.play(); 29 | final result = await _qrApi.status(qrText!); 30 | if (result['code'] == 0) { 31 | switch (result['data']['action']) { 32 | case 'login': 33 | Get.toNamed('/qr_login_affirm', arguments: {'qrCode': qrText}); 34 | break; 35 | case 'mine': 36 | var friendInfo = result['data']['extend']; 37 | final isFriend = await _friendApi.isFriend(friendInfo['id']); 38 | if (isFriend['code'] == 0 && isFriend['data']) { 39 | Get.toNamed('/friend_info', 40 | arguments: {'friendId': friendInfo['id']}); 41 | } else { 42 | Get.toNamed('/qr_friend_affirm', 43 | arguments: {'result': friendInfo}); 44 | } 45 | break; 46 | default: 47 | Get.toNamed('/qr_other_result', 48 | arguments: {'text': "二维码内容无法识别或已失效"}); 49 | break; 50 | } 51 | } else { 52 | Get.toNamed('/qr_other_result', arguments: {'text': "二维码内容无法识别或已失效"}); 53 | } 54 | } 55 | } 56 | 57 | void restartScanning() { 58 | qrText = null; 59 | isScanning = true; 60 | mobileScannerController.start(); 61 | update([const Key("qr_code_scan")]); 62 | } 63 | 64 | @override 65 | void onClose() { 66 | mobileScannerController.dispose(); 67 | player.dispose(); 68 | super.onClose(); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /lib/pages/qr_code_scan/qr_friend_affirm/logic.dart: -------------------------------------------------------------------------------- 1 | import 'package:get/get.dart'; 2 | import 'package:linyu_mobile/api/notify_api.dart'; 3 | import 'package:linyu_mobile/components/custom_flutter_toast/index.dart'; 4 | 5 | class QRFriendAffirmLogic extends GetxController { 6 | final _notifyApi = NotifyApi(); 7 | late final dynamic result; 8 | 9 | @override 10 | void onInit() { 11 | super.onInit(); 12 | result = Get.arguments['result']; 13 | } 14 | 15 | void onAddFriend() { 16 | _notifyApi.friendApply(result['id'], "通过二维码添加好友").then((res) { 17 | if (res['code'] == 0) { 18 | CustomFlutterToast.showSuccessToast("请求成功"); 19 | } 20 | // Get.until((route) => Get.currentRoute == "/"); 21 | }); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /lib/pages/qr_code_scan/qr_login_affirm/index.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:linyu_mobile/components/app_bar_title/index.dart'; 3 | import 'package:linyu_mobile/components/custom_button/index.dart'; 4 | import 'package:linyu_mobile/utils/getx_config/config.dart'; 5 | 6 | import 'logic.dart'; 7 | 8 | class QrLoginAffirmPage extends CustomWidget { 9 | QrLoginAffirmPage({super.key}); 10 | 11 | @override 12 | Widget buildWidget(BuildContext context) { 13 | return Scaffold( 14 | backgroundColor: const Color(0xFFF9FBFF), 15 | appBar: AppBar( 16 | centerTitle: true, 17 | title: const AppBarTitle('登录确认'), 18 | backgroundColor: const Color(0xFFF9FBFF), 19 | ), 20 | body: Padding( 21 | padding: const EdgeInsets.symmetric(vertical: 100.0, horizontal: 20.0), 22 | child: Center( 23 | child: Column( 24 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 25 | crossAxisAlignment: CrossAxisAlignment.center, 26 | children: [ 27 | Column( 28 | children: [ 29 | Image.asset('assets/images/qr-affirm.png', width: 180), 30 | const SizedBox(height: 10), 31 | const Text( 32 | 'Linyu电脑版本请求登录', 33 | style: TextStyle(fontSize: 18), 34 | ), 35 | ], 36 | ), 37 | Column( 38 | children: [ 39 | CustomButton( 40 | text: '确认登录', onTap: controller.onQrLogin, width: 220), 41 | const SizedBox(height: 20), 42 | ], 43 | ) 44 | ], 45 | ), 46 | ), 47 | ), 48 | ); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /lib/pages/qr_code_scan/qr_login_affirm/logic.dart: -------------------------------------------------------------------------------- 1 | import 'package:get/get.dart'; 2 | import 'package:linyu_mobile/api/user_api.dart'; 3 | import 'package:linyu_mobile/components/custom_flutter_toast/index.dart'; 4 | 5 | class QRLoginAffirmLogic extends GetxController { 6 | final _userAPi = UserApi(); 7 | late final String qrCode; 8 | 9 | @override 10 | void onInit() { 11 | qrCode = Get.arguments['qrCode']; 12 | } 13 | 14 | void onQrLogin() { 15 | _userAPi.qrLogin(qrCode).then((res) { 16 | if (res['code'] == 0) { 17 | CustomFlutterToast.showSuccessToast("登录成功~"); 18 | Get.offAllNamed('/'); 19 | } 20 | }); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /lib/pages/qr_code_scan/qr_other_result/index.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:linyu_mobile/components/app_bar_title/index.dart'; 3 | import 'package:linyu_mobile/pages/qr_code_scan/qr_other_result/logic.dart'; 4 | import 'package:linyu_mobile/utils/getx_config/config.dart'; 5 | 6 | class QrOtherResultPage extends CustomWidget { 7 | QrOtherResultPage({super.key}); 8 | 9 | @override 10 | Widget buildWidget(BuildContext context) { 11 | return Scaffold( 12 | backgroundColor: const Color(0xFFF9FBFF), 13 | appBar: AppBar( 14 | centerTitle: true, 15 | title: const AppBarTitle('扫描结果'), 16 | backgroundColor: const Color(0xFFF9FBFF), 17 | ), 18 | body: Center( 19 | child: Container( 20 | padding: const EdgeInsets.only(top: 50), 21 | child: Column( 22 | children: [ 23 | const Icon( 24 | IconData(0xe66a, fontFamily: 'IconFont'), 25 | size: 100, 26 | color: Color(0xCCFF4C4C), 27 | ), 28 | const SizedBox(height: 20), 29 | Text(controller.text, style: const TextStyle(fontSize: 18)) 30 | ], 31 | ), 32 | ), 33 | ), 34 | ); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /lib/pages/qr_code_scan/qr_other_result/logic.dart: -------------------------------------------------------------------------------- 1 | import 'package:get/get.dart'; 2 | 3 | class QrOtherResultLogic extends GetxController { 4 | late String text; 5 | 6 | @override 7 | void onInit() { 8 | text = Get.arguments['text']; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /lib/pages/talk/logic.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | import 'package:get/get.dart'; 3 | import 'package:linyu_mobile/api/talk_api.dart'; 4 | import 'package:linyu_mobile/api/user_api.dart'; 5 | import 'package:linyu_mobile/components/CustomDialog/index.dart'; 6 | import 'package:shared_preferences/shared_preferences.dart'; 7 | 8 | class TalkLogic extends GetxController { 9 | final _talkApi = TalkApi(); 10 | final _userApi = UserApi(); 11 | String currentUserId = ''; 12 | String targetUserId = ''; 13 | String title = '说说'; 14 | 15 | List talkList = []; 16 | int index = 0; 17 | bool hasMore = true; 18 | bool isLoading = false; 19 | final ScrollController scrollController = ScrollController(); 20 | 21 | @override 22 | void onInit() { 23 | super.onInit(); 24 | init(); 25 | } 26 | 27 | void init() async { 28 | if (Get.arguments != null) { 29 | targetUserId = Get.arguments['userId'] ?? ''; 30 | title = Get.arguments['title'] ?? '说说'; 31 | } 32 | refreshData(); 33 | scrollController.addListener(scrollListener); 34 | SharedPreferences.getInstance().then((prefs) { 35 | currentUserId = prefs.getString('userId') ?? ''; 36 | }); 37 | } 38 | 39 | @override 40 | void onClose() { 41 | scrollController.dispose(); 42 | super.onClose(); 43 | } 44 | 45 | void scrollListener() { 46 | if (scrollController.position.pixels == 47 | scrollController.position.maxScrollExtent) { 48 | onTalkList(); 49 | } 50 | } 51 | 52 | void onTalkList() { 53 | if (!hasMore || isLoading) return; 54 | isLoading = true; 55 | update([const Key("talk")]); 56 | _talkApi.list(index, 10, targetUserId).then((res) { 57 | if (res['code'] == 0) { 58 | final List newTalks = res['data']; 59 | if (newTalks.isEmpty) { 60 | hasMore = false; 61 | } else { 62 | talkList.addAll(newTalks); 63 | index += newTalks.length; 64 | } 65 | isLoading = false; 66 | } else { 67 | isLoading = false; 68 | } 69 | }).catchError(() { 70 | isLoading = false; 71 | }).whenComplete(() { 72 | update([const Key("talk")]); 73 | }); 74 | } 75 | 76 | Future refreshData() async { 77 | talkList.clear(); 78 | index = 0; 79 | hasMore = true; 80 | update([const Key("talk")]); 81 | onTalkList(); 82 | } 83 | 84 | void updateTalkLikeOrCommentCount(String key, int num, String talkId) { 85 | for (var talk in talkList) { 86 | if (talk['talkId'] == talkId) { 87 | talk[key] = num; 88 | update([const Key("talk")]); 89 | return; 90 | } 91 | } 92 | } 93 | 94 | void onDeleteTalk(talkId) { 95 | _talkApi.delete(talkId).then((res) { 96 | if (res['code'] == 0) { 97 | for (var talk in talkList) { 98 | if (talk['talkId'] == talkId) { 99 | talkList.remove(talk); 100 | update([const Key("talk")]); 101 | return; 102 | } 103 | } 104 | } 105 | }); 106 | } 107 | 108 | void handlerDeleteTalkTip(BuildContext context, String talkId) { 109 | CustomDialog.showTipDialog( 110 | context, 111 | text: '确认删除该条说说?', 112 | onOk: () => onDeleteTalk(talkId), 113 | onCancel: () {}, 114 | ); 115 | } 116 | 117 | Future onGetImg(String fileName, String userId) async { 118 | dynamic res = await _userApi.getImg(fileName, userId); 119 | if (res['code'] == 0) { 120 | return res['data']; 121 | } 122 | return ''; 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /lib/pages/talk/talk_create/logic.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | import 'package:dio/dio.dart'; 4 | import 'package:flutter/cupertino.dart'; 5 | import 'package:get/get.dart' 6 | show Get, GetInstance, GetNavigation, GetxController, TextEditingController; 7 | import 'package:get/get_rx/get_rx.dart'; 8 | import 'package:image_picker/image_picker.dart'; 9 | import 'package:linyu_mobile/api/talk_api.dart'; 10 | import 'package:dio/dio.dart' show MultipartFile, FormData; 11 | import 'package:linyu_mobile/components/CustomDialog/index.dart'; 12 | import 'package:linyu_mobile/components/custom_flutter_toast/index.dart'; 13 | import 'package:linyu_mobile/pages/talk/logic.dart'; 14 | 15 | class TalkCreateLogic extends GetxController { 16 | final _talkApi = TalkApi(); 17 | final contentController = TextEditingController(); 18 | final selectedImages = [].obs; 19 | late List selectedUsers = []; 20 | 21 | TalkLogic get talkLogic => GetInstance().find(); 22 | 23 | Future pickImages() async { 24 | final ImagePicker picker = ImagePicker(); 25 | final List? images = await picker.pickMultiImage(); 26 | 27 | if (images != null) { 28 | for (var image in images) { 29 | selectedImages.add(File(image.path)); 30 | } 31 | } 32 | } 33 | 34 | void removeImage(int index) { 35 | selectedImages.removeAt(index); 36 | } 37 | 38 | Future onUploadImg(String talkId, File img) async { 39 | Map map = {}; 40 | final file = await MultipartFile.fromFile(img.path, 41 | filename: img.path.split('/').last); 42 | map['talkId'] = talkId; 43 | map['name'] = img.path.split('/').last; 44 | map['size'] = img.lengthSync(); 45 | map["file"] = file; 46 | FormData formData = FormData.fromMap(map); 47 | await _talkApi.uploadImg(formData); 48 | } 49 | 50 | void onCreateTalk() { 51 | if (contentController.text.isEmpty) { 52 | CustomFlutterToast.showSuccessToast('内容不能为空~'); 53 | return; 54 | } 55 | List permission = selectedUsers.map((user) => user['friendId']).toList(); 56 | _talkApi.create(contentController.text, permission).then((res) async { 57 | if (res['code'] == 0) { 58 | CustomFlutterToast.showSuccessToast('发表成功~'); 59 | Get.back(); 60 | for (var img in selectedImages) { 61 | await onUploadImg(res['data']['id'], img); 62 | } 63 | talkLogic.refreshData(); 64 | } 65 | }); 66 | } 67 | 68 | Future handlerToUserSelect() async { 69 | var result = await Get.toNamed('/user_select', 70 | arguments: {'selectedUsers': selectedUsers}); 71 | if (result != null) { 72 | selectedUsers = result; 73 | } 74 | update([const Key('talk_create')]); 75 | } 76 | 77 | @override 78 | void onClose() { 79 | contentController.dispose(); 80 | super.onClose(); 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /lib/utils/String.dart: -------------------------------------------------------------------------------- 1 | class StringUtil { 2 | static bool isNullOrEmpty(String? str) { 3 | return str == null || str.trim().isEmpty; 4 | } 5 | 6 | static bool isNotNullOrEmpty(String? str) { 7 | return !isNullOrEmpty(str); 8 | } 9 | 10 | static String formatSize(int size) { 11 | if (size < 1024) { 12 | return '$size B'; 13 | } 14 | const units = ['KB', 'MB', 'GB', 'TB']; 15 | int i = -1; 16 | double newSize = size.toDouble(); 17 | while (newSize >= 1024 && i < units.length - 1) { 18 | newSize /= 1024; 19 | i++; 20 | } 21 | return '${newSize.toStringAsFixed(1)} ${units[i]}'; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /lib/utils/app_badger.dart: -------------------------------------------------------------------------------- 1 | import 'package:app_badge_plus/app_badge_plus.dart'; 2 | 3 | class AppBadger { 4 | static int _chatCount = 0; 5 | static int _notifyCount = 0; 6 | 7 | static void setCount(int chatCount, int notifyCount) { 8 | _chatCount = chatCount; 9 | _notifyCount = notifyCount; 10 | _updateBadgeCount(); 11 | } 12 | 13 | static void setChatCount(int count) { 14 | _chatCount = count; 15 | _updateBadgeCount(); 16 | } 17 | 18 | static void setNotifyCount(int count) { 19 | _notifyCount = count; 20 | _updateBadgeCount(); 21 | } 22 | 23 | static void _updateBadgeCount() async { 24 | if (await AppBadgePlus.isSupported()) { 25 | if (_chatCount + _notifyCount > 0) { 26 | await AppBadgePlus.updateBadge(_chatCount + _notifyCount); 27 | } else { 28 | _notifyCount = 0; 29 | _chatCount = 0; 30 | await AppBadgePlus.updateBadge(0); 31 | } 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /lib/utils/cropPicture.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:get/get.dart'; 4 | import 'package:image_cropper/image_cropper.dart'; 5 | import 'package:image_picker/image_picker.dart'; 6 | import 'getx_config/GlobalThemeConfig.dart'; 7 | 8 | typedef UploadPictureCallback = Future Function(File picture); 9 | 10 | //图片剪切 11 | Future cropPicture(ImageSource? type, UploadPictureCallback uploadPicture, 12 | {isVariable = false}) async { 13 | final GlobalThemeConfig theme = GetInstance().find(); 14 | final ImagePicker picker = ImagePicker(); 15 | final XFile? pickedFile = 16 | await picker.pickImage(source: type ?? ImageSource.gallery); 17 | 18 | File? croppedFile = await ImageCropper().cropImage( 19 | sourcePath: pickedFile!.path, 20 | aspectRatioPresets: Platform.isAndroid 21 | ? [ 22 | isVariable 23 | ? CropAspectRatioPreset.original 24 | : CropAspectRatioPreset.square, 25 | // CropAspectRatioPreset.ratio3x2, 26 | // CropAspectRatioPreset.ratio4x3, 27 | // CropAspectRatioPreset.ratio16x9 28 | ] 29 | : [ 30 | isVariable 31 | ? CropAspectRatioPreset.original 32 | : CropAspectRatioPreset.square, 33 | // CropAspectRatioPreset.ratio3x2, 34 | // CropAspectRatioPreset.ratio4x3, 35 | // CropAspectRatioPreset.ratio5x3, 36 | // CropAspectRatioPreset.ratio5x4, 37 | // CropAspectRatioPreset.ratio7x5, 38 | // CropAspectRatioPreset.ratio16x9 39 | ], 40 | androidUiSettings: AndroidUiSettings( 41 | toolbarTitle: '剪切', 42 | // toolbarColor: Colors.deepOrange, 43 | toolbarWidgetColor: theme.primaryColor, 44 | dimmedLayerColor: Colors.black54, 45 | cropFrameColor: theme.primaryColor, 46 | activeControlsWidgetColor: theme.primaryColor, 47 | initAspectRatio: CropAspectRatioPreset.original, 48 | lockAspectRatio: !isVariable, 49 | ), 50 | iosUiSettings: const IOSUiSettings( 51 | title: '剪切', 52 | ), 53 | ); 54 | if (croppedFile != null) { 55 | pickedFile.path.split("/"); 56 | uploadPicture(croppedFile); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /lib/utils/emoji.dart: -------------------------------------------------------------------------------- 1 | class Emoji { 2 | static List emojis = [ 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 | '😮', 46 | '💨', 47 | '😤', 48 | '😠', 49 | // '😡', 50 | // '🤬', 51 | // '🤯', 52 | '😳', 53 | '😱', 54 | '😨', 55 | '😰', 56 | '😥', 57 | '😓', 58 | '🤗', 59 | '🤔', 60 | // '🤭', 61 | // '🤫', 62 | // '🤥', 63 | '😶', 64 | '🌫️', 65 | '😐', 66 | '😑', 67 | '😬', 68 | '🙄', 69 | '😯', 70 | '😦', 71 | '😧', 72 | '😮', 73 | '😲', 74 | '😴', 75 | // '🤤', 76 | '😪', 77 | '😵', 78 | '💫', 79 | '🤐', 80 | // '🤢', 81 | // '🤮', 82 | // '🤧', 83 | '😷', 84 | '🤒', 85 | '🤕', 86 | '🤑', 87 | // '🤠', 88 | '😈', 89 | '👿', 90 | '👹', 91 | '👺', 92 | // '🤡', 93 | '💩', 94 | '👻', 95 | '💀', 96 | '👽', 97 | '👾', 98 | '🤖', 99 | '🎃', 100 | '😺', 101 | '😸', 102 | '😹', 103 | '😻', 104 | '😼', 105 | '😽', 106 | '🙀', 107 | '😿', 108 | '😾', 109 | '🐶', 110 | ]; 111 | } 112 | -------------------------------------------------------------------------------- /lib/utils/encrypt.dart: -------------------------------------------------------------------------------- 1 | import 'package:pointycastle/asymmetric/api.dart'; 2 | import 'package:linyu_mobile/api/user_api.dart'; 3 | import 'package:encrypt/encrypt.dart' as encrypt; 4 | 5 | final _useApi = UserApi(); 6 | 7 | Future passwordEncrypt(String password) async { 8 | final publicKeyResult = await _useApi.publicKey(); 9 | if (publicKeyResult['code'] == 0) { 10 | String key = publicKeyResult['data']; 11 | final parsedKey = encrypt.RSAKeyParser().parse(key) as RSAPublicKey; 12 | final encrypter = encrypt.Encrypter(encrypt.RSA(publicKey: parsedKey)); 13 | final encryptedPassword = encrypter.encrypt(password).base64; 14 | return encryptedPassword; 15 | } 16 | return "-1"; 17 | } 18 | -------------------------------------------------------------------------------- /lib/utils/extension.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/foundation.dart'; 2 | 3 | extension UsersListExtension> on List { 4 | bool include(Map value) { 5 | for (E e in this) { 6 | if (e['friendId'] == value['friendId']) return true; 7 | } 8 | return false; 9 | } 10 | 11 | bool delete(Map value) { 12 | for (E e in this) { 13 | if (e['friendId'] == value['friendId']) { 14 | return remove(e); 15 | } 16 | } 17 | return false; 18 | } 19 | 20 | List copy({List? list}) { 21 | List sourceList = list ?? this; // 若 list 为空,将其设为当前对象 22 | if (sourceList.isEmpty) return []; // 若源列表为空,返回空列表 23 | List copyList = []; // 创建一个新的列表用于存放复制的元素 24 | try { 25 | for (var item in sourceList) { 26 | if (item is Map) { 27 | Map mapItem = Map.from(item); 28 | copyList.add(mapItem); 29 | } else if (item is List) { 30 | copyList.add(item.copy()); 31 | } else { 32 | copyList.add(item); 33 | } 34 | } 35 | } catch (e) { 36 | if (kDebugMode) { 37 | print('复制列表时发生错误: $e'); 38 | } // 错误处理,输出错误信息 39 | } 40 | return copyList; // 返回复制后的列表 41 | } 42 | 43 | List replace({dynamic oldValue, dynamic newValue, List? list}) { 44 | List sourceList = list ?? this.copy(); 45 | oldValue ??= newValue; 46 | // 如果源列表为空,直接返回空列表 47 | if (sourceList.isEmpty) return []; 48 | try { 49 | // 遍历列表并进行替换 50 | for (var i = 0; i < sourceList.length; i++) { 51 | if (sourceList[i]['id'] == oldValue['id']) { 52 | sourceList[i] = newValue; 53 | break; // 找到并替换后退出循环 54 | } 55 | } 56 | } catch (e) { 57 | if (kDebugMode) { 58 | print('替换值时发生错误: $e'); // 输出错误信息 59 | } 60 | } 61 | 62 | return sourceList; // 返回处理后的列表 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /lib/utils/getx_config/GlobalData.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/foundation.dart'; 2 | import 'package:get/get.dart'; 3 | import 'package:linyu_mobile/api/user_api.dart'; 4 | import 'package:linyu_mobile/utils/app_badger.dart'; 5 | import 'package:shared_preferences/shared_preferences.dart'; 6 | 7 | class GlobalData extends GetxController { 8 | final _userApi = UserApi(); 9 | var unread = {}.obs; 10 | var currentUserId = ''; 11 | var currentUserAccount = ''; 12 | late String? currentUserName; 13 | // late String? currentAvatarUrl = 14 | // 'http://192.168.101.4:9000/linyu/default-portrait.jpg'; 15 | late String? currentAvatarUrl = 16 | 'http://114.96.70.115:19000/linyu/default-portrait.jpg'; 17 | late String? currentToken; 18 | 19 | Future init() async { 20 | try { 21 | SharedPreferences prefs = await SharedPreferences.getInstance(); 22 | String? token = prefs.getString('x-token'); 23 | if (token == null) return; 24 | currentToken = token; 25 | currentUserId = prefs.getString('userId') ?? ''; 26 | currentUserAccount = prefs.getString('account') ?? ''; 27 | currentUserName = prefs.getString('username'); 28 | currentAvatarUrl = prefs.getString('portrait') ?? 29 | 'http://114.96.70.115:19000/linyu/default-portrait.jpg'; 30 | // 仅当用户 ID 不为空时才获取未读信息 31 | if (currentUserId.isNotEmpty) await onGetUserUnreadInfo(); 32 | } catch (e) { 33 | // 增加错误处理 34 | if (kDebugMode) print('初始化失败: $e'); 35 | // 根据需求可以添加其他处理逻辑,比如记录日志等 36 | } 37 | } 38 | 39 | Future onGetUserUnreadInfo() async { 40 | try { 41 | final result = await _userApi.unread(); 42 | if (result['code'] == 0) { 43 | unread.assignAll(Map.from(result['data'])); 44 | // 优化:仅在有未读消息时更新角标 45 | int chatCount = getUnreadCount('chat'); 46 | int notifyCount = getUnreadCount('notify'); 47 | if (chatCount > 0 || notifyCount > 0) { 48 | AppBadger.setCount(chatCount, notifyCount); 49 | } 50 | } 51 | } catch (e) { 52 | // 增加错误处理 53 | if (kDebugMode) print('获取未读信息失败: $e'); 54 | // 这里可以根据需求添加其他处理逻辑,比如记录日志等 55 | } 56 | } 57 | 58 | int getUnreadCount(String type) { 59 | if (unread.value.containsKey(type)) return unread.value[type]!; 60 | return 0; 61 | } 62 | 63 | @override 64 | void onInit() { 65 | super.onInit(); 66 | init(); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /lib/utils/getx_config/GlobalThemeConfig.dart: -------------------------------------------------------------------------------- 1 | import 'dart:ui'; 2 | 3 | import 'package:flutter/cupertino.dart'; 4 | import 'package:get/get.dart'; 5 | 6 | class GlobalThemeConfig extends GetxController { 7 | late RxString themeMode = 'blue'.obs; 8 | 9 | void changeThemeMode(String mode) { 10 | themeMode.value = mode; 11 | update(); 12 | } 13 | 14 | //主题色 15 | Color get primaryColor { 16 | switch (themeMode.value) { 17 | case 'blue': 18 | return const Color(0xFF4C9BFF); 19 | case 'pink': 20 | return const Color(0xFFFFA0CF); 21 | default: 22 | return const Color(0xFF4C9BFF); 23 | } 24 | } 25 | 26 | Color get boldColor { 27 | switch (themeMode.value) { 28 | case 'blue': 29 | return const Color(0xFF0060D9); 30 | case 'pink': 31 | return const Color(0xFFFF53A8); 32 | default: 33 | return const Color(0xFF0060D9); 34 | } 35 | } 36 | 37 | Color get minorColor { 38 | switch (themeMode.value) { 39 | case 'blue': 40 | return const Color(0xFFDFF4FF); 41 | case 'pink': 42 | return const Color(0xFFFBEBFF); 43 | default: 44 | return const Color(0xFFDFF4FF); 45 | } 46 | } 47 | 48 | Color get qrColor { 49 | switch (themeMode.value) { 50 | case 'blue': 51 | return const Color(0xFFA0D9F6); 52 | case 'pink': 53 | return const Color(0xFFF5CFFF); 54 | default: 55 | return const Color(0xFFA0D9F6); 56 | } 57 | } 58 | 59 | //搜索框背景色 60 | Color get searchBarColor { 61 | switch (themeMode.value) { 62 | case 'blue': 63 | return const Color(0xFFE3ECFF); 64 | case 'pink': 65 | return const Color(0xFFFBEDFF); 66 | default: 67 | return const Color(0xFFE3ECFF); 68 | } 69 | } 70 | 71 | Color get errorColor { 72 | return const Color(0xFFFF4C4C); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /lib/utils/linyu_msg.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | 3 | import 'package:linyu_mobile/utils/date.dart'; 4 | 5 | class LinyuMsgUtil { 6 | static String getMsgContent(dynamic msgContent) { 7 | String contentStr = ''; 8 | try { 9 | switch (msgContent['type']) { 10 | case "text": 11 | contentStr = msgContent['content']; 12 | break; 13 | case "file": 14 | var content = jsonDecode(msgContent['content']); 15 | contentStr = '[文件] ${content['name']}'; 16 | break; 17 | case "img": 18 | contentStr = '[图片]'; 19 | break; 20 | case "retraction": 21 | contentStr = '[消息被撤回]'; 22 | break; 23 | case "voice": 24 | var content = jsonDecode(msgContent['content']); 25 | contentStr = '[语音] ${content['time']}"'; 26 | break; 27 | case "call": 28 | var content = jsonDecode(msgContent['content']); 29 | contentStr = 30 | '[通话] ${content['time'] > 0 ? DateUtil.formatTimingTime(content['time']) : "未接通"}'; 31 | break; 32 | case "system": 33 | contentStr = '[系统消息]'; 34 | break; 35 | case "quit": 36 | contentStr = '[系统消息]'; 37 | break; 38 | } 39 | } catch (e) { 40 | return ''; 41 | } 42 | return contentStr; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /lib/utils/permission_handler.dart: -------------------------------------------------------------------------------- 1 | import 'package:permission_handler/permission_handler.dart'; 2 | 3 | class PermissionHandler { 4 | static Future permissionRequest() async { 5 | await requestNotificationPermission(); 6 | } 7 | 8 | static Future requestNotificationPermission() async { 9 | var status = await Permission.notification.status; 10 | if (status.isDenied) { 11 | await Permission.notification.request(); 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /test/widget_test.dart: -------------------------------------------------------------------------------- 1 | // This is a basic Flutter widget test. 2 | // 3 | // To perform an interaction with a widget in your test, use the WidgetTester 4 | // utility in the flutter_test package. For example, you can send tap and scroll 5 | // gestures. You can also use WidgetTester to find child widgets in the widget 6 | // tree, read text, and verify that the values of widget properties are correct. 7 | 8 | import 'package:flutter/material.dart'; 9 | import 'package:flutter_test/flutter_test.dart'; 10 | 11 | import 'package:linyu_mobile/main.dart'; 12 | import 'package:linyu_mobile/pages/login/index.dart'; 13 | 14 | void main() { 15 | testWidgets('Counter increments smoke test', (WidgetTester tester) async { 16 | // Build our app and trigger a frame. 17 | await tester.pumpWidget(MyApp(initialPage: LoginPage())); 18 | 19 | // Verify that our counter starts at 0. 20 | expect(find.text('0'), findsOneWidget); 21 | expect(find.text('1'), findsNothing); 22 | 23 | // Tap the '+' icon and trigger a frame. 24 | await tester.tap(find.byIcon(Icons.add)); 25 | await tester.pump(); 26 | 27 | // Verify that our counter has incremented. 28 | expect(find.text('0'), findsNothing); 29 | expect(find.text('1'), findsOneWidget); 30 | }); 31 | } 32 | --------------------------------------------------------------------------------